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:
@@ -49,6 +49,18 @@ export function buildSavePayload(input: BuildSavePayloadInput) {
|
||||
|
||||
const isPageActive = !isEditingHeader && !isEditingFooter;
|
||||
|
||||
// I-1 (data-loss): `activePageId` can go dangling -- e.g. the original
|
||||
// Home page (id 'home') is deleted, its replacement gets a fresh id
|
||||
// (`page_<ts>`), and a reload re-initializes `activePageId` back to the
|
||||
// hardcoded default `'home'` (see PageContext's `useState('home')`) before
|
||||
// `load()` has a chance to point it at the actually-restored page. If a
|
||||
// real page IS active but matches no entry in `pages`, treat `pages[0]`
|
||||
// (the landing page) as the active one so the live canvas serialization
|
||||
// still reaches the index.html page slot / `pages_craft_state[0]` instead
|
||||
// of only the legacy top-level `craft_state`/`html` fields.
|
||||
const activePageIndex = isPageActive ? pages.findIndex((p) => p.id === activePageId) : -1;
|
||||
const effectiveActivePageId = activePageIndex !== -1 ? activePageId : pages[0]?.id;
|
||||
|
||||
// Fresh header/footer state: the live canvas wins when that zone is the
|
||||
// one currently being edited; otherwise fall back to the last-committed
|
||||
// stored state (updated on zone switch by PageContext's saveCurrentState).
|
||||
@@ -115,7 +127,7 @@ export function buildSavePayload(input: BuildSavePayloadInput) {
|
||||
const filename = i === 0 ? 'index.html' : page.slug + '.html';
|
||||
let pageHtml = '';
|
||||
|
||||
if (isPageActive && page.id === activePageId) {
|
||||
if (isPageActive && page.id === effectiveActivePageId) {
|
||||
// Active page: use the current canvas HTML (already exported above)
|
||||
pageHtml = currentHtml;
|
||||
} else if (page.craftState) {
|
||||
@@ -146,7 +158,7 @@ export function buildSavePayload(input: BuildSavePayloadInput) {
|
||||
// reload the editor's clean-URL routing (.htaccess rewrite of /name →
|
||||
// name.html) lines up with the file we just wrote (index.html).
|
||||
slug: i === 0 ? 'index' : page.slug,
|
||||
craftState: (isPageActive && page.id === activePageId) ? liveCraftState : (page.craftState || null),
|
||||
craftState: (isPageActive && page.id === effectiveActivePageId) ? liveCraftState : (page.craftState || null),
|
||||
}));
|
||||
|
||||
return {
|
||||
@@ -177,6 +189,7 @@ export function useWhpApi() {
|
||||
setHeaderCraftState,
|
||||
setFooterCraftState,
|
||||
setPagesCraftState,
|
||||
setActivePageIdDirect,
|
||||
} = usePages();
|
||||
|
||||
const save = useCallback(async () => {
|
||||
@@ -271,10 +284,23 @@ export function useWhpApi() {
|
||||
console.warn('Failed to load page state:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// I-1 (data-loss): point activePageId at the page we just loaded
|
||||
// into the canvas. Without this, activePageId stays at whatever it
|
||||
// was initialized to (the hardcoded default 'home'), which goes
|
||||
// dangling the moment the original Home page has been deleted and
|
||||
// replaced (its replacement gets a fresh `page_<ts>` id) -- the next
|
||||
// edit+save would then only reach the legacy top-level fields
|
||||
// instead of the actual page slot. Only do this when a real page is
|
||||
// being loaded, i.e. we're not currently mid-edit of the header/
|
||||
// footer zone (switching zones is handled separately by switchPage).
|
||||
if (!isEditingHeader && !isEditingFooter) {
|
||||
setActivePageIdDirect(firstPage.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}, [isWHP, whpConfig, actions, setHeaderCraftState, setFooterCraftState, setPagesCraftState]);
|
||||
}, [isWHP, whpConfig, actions, setHeaderCraftState, setFooterCraftState, setPagesCraftState, setActivePageIdDirect, isEditingHeader, isEditingFooter]);
|
||||
|
||||
const uploadAsset = useCallback(
|
||||
async (file: File) => {
|
||||
|
||||
Reference in New Issue
Block a user