feat(builder): send + restore site head code in save/load

Extend the save payload with head_code + design so the backend can
inject SiteDesign.headCode into published pages, and restore design
tokens on load() so the editor reflects the last-saved state.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-12 18:22:38 -07:00
co-authored by Claude Opus 4.8
parent e892ee0e53
commit 92841e3f35
2 changed files with 56 additions and 2 deletions
+31
View File
@@ -1,6 +1,7 @@
import { describe, test, expect } from 'vitest';
import { buildSavePayload } from './useWhpApi';
import { PageData } from '../types';
import { DEFAULT_SITE_DESIGN } from '../state/SiteDesignContext';
const pageA: PageData = { id: 'home', name: 'Home', slug: 'index', craftState: 'STORED_HOME' };
const pageB: PageData = { id: 'page_2', name: 'About', slug: 'about', craftState: 'STORED_ABOUT' };
@@ -19,6 +20,8 @@ describe('buildSavePayload', () => {
activePageId: '__header__',
isEditingHeader: true,
isEditingFooter: false,
headCode: DEFAULT_SITE_DESIGN.headCode,
design: DEFAULT_SITE_DESIGN,
});
// The fresh live canvas must be reflected in header_craft_state, not the stale stored one.
@@ -52,6 +55,8 @@ describe('buildSavePayload', () => {
activePageId: '__footer__',
isEditingHeader: false,
isEditingFooter: true,
headCode: DEFAULT_SITE_DESIGN.headCode,
design: DEFAULT_SITE_DESIGN,
});
expect(payload.footer_craft_state).toBe('LIVE_FOOTER');
@@ -75,6 +80,8 @@ describe('buildSavePayload', () => {
activePageId: 'home',
isEditingHeader: false,
isEditingFooter: false,
headCode: DEFAULT_SITE_DESIGN.headCode,
design: DEFAULT_SITE_DESIGN,
});
// Live canvas goes to the active page and top-level slots.
@@ -105,6 +112,8 @@ describe('buildSavePayload', () => {
activePageId: 'home',
isEditingHeader: false,
isEditingFooter: false,
headCode: DEFAULT_SITE_DESIGN.headCode,
design: DEFAULT_SITE_DESIGN,
});
// The live edit must land in pages_craft_state[0] (index.html slot),
@@ -116,4 +125,26 @@ describe('buildSavePayload', () => {
// The other page is untouched.
expect(payload.pages_craft_state.find((p) => p.id === 'p2')?.craftState).toBe('STORED_ABOUT_2');
});
test('head code + design tokens are included in the save payload', () => {
const design = { ...DEFAULT_SITE_DESIGN, headCode: '<meta name="x">' };
const payload = buildSavePayload({
siteId: 1,
siteName: 'Test Site',
liveCraftState: 'LIVE_PAGE_HOME',
pages: [pageA, pageB],
headerPage,
footerPage,
activePageId: 'home',
isEditingHeader: false,
isEditingFooter: false,
headCode: '<meta name="x">',
design,
});
expect(payload.head_code).toBe('<meta name="x">');
expect(payload.design).toEqual(design);
expect(payload.design.headCode).toBe('<meta name="x">');
});
});