fix(site-builder): final whole-branch review fixes
C1: HtmlBlock's PURIFY_CONFIG omitted 'style' from ALLOWED_ATTR, so the toolbar colour picker added in this branch was silently deleted by DOMPurify -- issue #2 was regressed, not fixed. Adds style/id plus table tags, with tests pinning the markup path in both render and toHtml. I3: PagesPanel's three confirmation states were not mutually exclusive; cancelling delete revealed an unbidden reset prompt on a destructive action. I5: orphan repair logged at console.warn, which the new console buffer cannot see -- the reporter would never capture the most diagnostic signal for the still-unreproduced drop bug. Also aligns useWhpApi's initial-load failure handling with loadState's fallback. I7: corrects comments (and the design spec) that asserted an orphan "renders somewhere on the canvas", which a mid-plan audit disproved. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -122,7 +122,12 @@ describe('loadState (via switchPage) repairs an orphaned node before handing it
|
||||
),
|
||||
);
|
||||
|
||||
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
// I5 (review): the orphan-repair log must be console.error, not
|
||||
// console.warn -- console-buffer.ts (feeding the in-builder issue
|
||||
// reporter) only patches console.error, and this reattach signal is the
|
||||
// single most diagnostic clue for the still-unreproduced "elements drop
|
||||
// off the canvas" report.
|
||||
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
deserializeMock.mockClear();
|
||||
|
||||
// The real switch-into-a-stored-page path.
|
||||
@@ -137,11 +142,12 @@ describe('loadState (via switchPage) repairs an orphaned node before handing it
|
||||
expect(parsed.stray.parent).toBe('ROOT');
|
||||
|
||||
// The observable signal that repair actually ran, not just that the
|
||||
// orphan happened to be absent for some unrelated reason.
|
||||
expect(warnSpy).toHaveBeenCalled();
|
||||
expect(warnSpy.mock.calls[0][0]).toContain('reattached');
|
||||
// orphan happened to be absent for some unrelated reason. (React's own
|
||||
// act()-environment warnings also go through console.error in this
|
||||
// harness, so search all calls rather than assuming index 0.)
|
||||
expect(errorSpy.mock.calls.some((call) => String(call[0]).includes('reattached'))).toBe(true);
|
||||
|
||||
warnSpy.mockRestore();
|
||||
errorSpy.mockRestore();
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -371,19 +371,30 @@ export const PageProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||
|
||||
/** Load a craft state into the Frame.
|
||||
*
|
||||
* Every state goes through `repairOrphanNodes` first: a node that no
|
||||
* parent lists is invisible to Layers and unselectable on the canvas, so
|
||||
* it can neither be moved nor deleted. Reattaching it to the end of ROOT
|
||||
* makes it an ordinary child the user can select and delete. Cheap
|
||||
* (single JSON round-trip) and a no-op -- returning the identical string
|
||||
* -- for the overwhelmingly common healthy case. */
|
||||
* Every state goes through `repairOrphanNodes` first: a node present in
|
||||
* the serialized state but not reachable from ROOT via `nodes`/
|
||||
* `linkedNodes` is never instantiated by Craft.js's `<Frame>` at all --
|
||||
* it doesn't render, so it isn't merely unselectable, it's invisible and
|
||||
* otherwise unrecoverable. Reattaching it to the end of ROOT makes it an
|
||||
* ordinary child the user can see, select and delete. Cheap (single JSON
|
||||
* round-trip) and a no-op -- returning the identical string -- for the
|
||||
* overwhelmingly common healthy case.
|
||||
*
|
||||
* Note this orphan-node repair is a different mechanism from the
|
||||
* originally-reported symptom (a *visible* element on the canvas that
|
||||
* can't be selected or deleted) -- that report is still unreproduced;
|
||||
* see `orphan-repair.ts` for detail. */
|
||||
const loadState = useCallback(
|
||||
(craftState: string | null, fallback: string) => {
|
||||
setTimeout(() => {
|
||||
const source = craftState || fallback;
|
||||
const { state, repaired } = repairOrphanNodes(source);
|
||||
if (repaired.length > 0) {
|
||||
console.warn(
|
||||
// I5: console-buffer.ts only patches console.error, and this
|
||||
// reattach signal is the single most diagnostic clue for the
|
||||
// still-unreproduced "elements drop off the canvas" report -- it
|
||||
// must reach the in-builder issue reporter's console buffer.
|
||||
console.error(
|
||||
`[site-builder] reattached ${repaired.length} unreachable node(s) to the page root:`,
|
||||
repaired.join(', '),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user