Fix I-1: landing-page edits lost when activePageId goes dangling

buildSavePayload matched the active page by page.id === activePageId,
but activePageId defaults to the hardcoded 'home' and load() never
updated it. Deleting the original Home page (its replacement gets id
page_<ts>), reloading, editing, and saving would then match no page --
the live edit only reached the legacy top-level craft_state, while the
authoritative pages[]/pages_craft_state[0] for index.html fell back to
stale state.

Belt-and-suspenders fix:
- buildSavePayload now falls back to pages[0] as the effective active
  page when a real page is active but activePageId matches nothing.
- useWhpApi's load() now points activePageId at the freshly restored
  first page via a new PageContext setActivePageIdDirect setter
  (bookkeeping only -- no re-serialize/deserialize), so activePageId
  stays valid after every load.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-12 17:29:32 -07:00
co-authored by Claude Opus 4.8
parent e5f30a4a56
commit 25e674badd
3 changed files with 81 additions and 3 deletions
+29
View File
@@ -87,4 +87,33 @@ describe('buildSavePayload', () => {
expect(payload.header_craft_state).toBe('STALE_HEADER');
expect(payload.footer_craft_state).toBe('STALE_FOOTER');
});
test('I-1: activePageId matches no page (dangling, e.g. original Home deleted then reload reset it to "home") -- live serialize still lands in pages_craft_state[0] / index.html, not lost', () => {
// pages[0] has id 'page_x' (the replacement landing page after the
// original 'home' was deleted); activePageId is stuck at the stale
// default 'home', which matches no entry in `pages`.
const pageX: PageData = { id: 'page_x', name: 'Home', slug: 'index', craftState: 'STORED_X' };
const p2: PageData = { id: 'p2', name: 'About', slug: 'about', craftState: 'STORED_ABOUT_2' };
const payload = buildSavePayload({
siteId: 1,
siteName: 'Test Site',
liveCraftState: 'LIVE_EDIT',
pages: [pageX, p2],
headerPage,
footerPage,
activePageId: 'home',
isEditingHeader: false,
isEditingFooter: false,
});
// The live edit must land in pages_craft_state[0] (index.html slot),
// not be silently dropped to only the legacy top-level fields.
expect(payload.pages_craft_state[0].craftState).toBe('LIVE_EDIT');
expect(payload.pages[0].filename).toBe('index.html');
// Top-level fields (legacy) should also reflect the live edit.
expect(payload.craft_state).toBe('LIVE_EDIT');
// The other page is untouched.
expect(payload.pages_craft_state.find((p) => p.id === 'p2')?.craftState).toBe('STORED_ABOUT_2');
});
});