fix(ai): restore section-fill strip coverage for non-streaming apply paths

Follow-up on 320ba10. Narrowing the scope of stripRedundantSectionFills
correctly took it out of sanitizeNodesForInsert/Upsert (which operate on
arbitrary cloned nodes that may be cards or components). But the batch
and non-streaming apply paths that DO land on the page root —
applyNodesToCanvas, upsertNodesToCanvas, upsertPreparedNodes,
extractAndApplyDesignModification — were left without any strip
coverage, silently regressing MCP batch_design page inserts and any
other non-streaming generation flow back to the original "section root
hardcoded #0A0A0A covers up #1a1a2e page background" bug.

Fix:

- Add a finalizePageRootAfterApply() helper that reads DEFAULT_FRAME_ID
  from the store (the canonical page root), runs
  stripRedundantSectionFills on it, and forcePageResync-s when any fill
  was actually stripped.
- Call it at the end of every non-streaming apply path, including the
  replaceEmptyFrame branches so a single-frame batch insert is covered.
- No-op when no page root exists yet (safe on first insert).

This preserves the card/component safety of 320ba10 while restoring
strip coverage for legitimate full-page applies.
This commit is contained in:
Fini 2026-04-06 18:28:31 +08:00
parent 320ba104eb
commit 36eb7c0f7d

View file

@ -333,6 +333,29 @@ export function insertStreamingNode(node: PenNode, parentId: string | null): voi
// Canvas apply/upsert operations
// ---------------------------------------------------------------------------
/**
* Run the page-root post-pass cleanups after a non-streaming apply path
* inserts its nodes into the store. This mirrors part of what
* applyPostStreamingTreeHeuristics does for the streaming path:
* specifically, strip redundant "safe-dark" section fills that sub-agents
* or external MCP callers hedge with on section roots (they hide the real
* page background and break theming).
*
* This helper targets DEFAULT_FRAME_ID the canonical page root frame
* so it always honours the stripRedundantSectionFills scope contract
* regardless of what the caller actually inserted (a full page, a set of
* sections, or a single card). If the page root does not exist yet (first
* insert before any root frame was created), there is nothing to strip
* and the helper is a no-op.
*/
function finalizePageRootAfterApply(): void {
const pageRoot = useDocumentStore.getState().getNodeById(DEFAULT_FRAME_ID);
if (!pageRoot || pageRoot.type !== 'frame') return;
if (stripRedundantSectionFills(pageRoot)) {
forcePageResync();
}
}
export function applyNodesToCanvas(nodes: PenNode[]): void {
const { getFlatNodes } = useDocumentStore.getState();
const existingIds = new Set(getFlatNodes().map((n) => n.id));
@ -341,6 +364,7 @@ export function applyNodesToCanvas(nodes: PenNode[]): void {
// If canvas only has one empty frame, replace it with the generated content
if (isCanvasOnlyEmptyFrame() && preparedNodes.length === 1 && preparedNodes[0].type === 'frame') {
replaceEmptyFrame(preparedNodes[0]);
finalizePageRootAfterApply();
resolveAllPendingIcons().catch(console.warn);
const rootId = getGenerationRootFrameId();
if (rootId) scanAndFillImages(rootId).catch(() => {});
@ -355,6 +379,7 @@ export function applyNodesToCanvas(nodes: PenNode[]): void {
addNode(parentId, node, Infinity);
}
adjustRootFrameHeightToContent();
finalizePageRootAfterApply();
resolveAllPendingIcons().catch(console.warn);
const rootId = getGenerationRootFrameId();
if (rootId) scanAndFillImages(rootId).catch(() => {});
@ -365,6 +390,7 @@ export function upsertNodesToCanvas(nodes: PenNode[]): number {
if (isCanvasOnlyEmptyFrame() && preparedNodes.length === 1 && preparedNodes[0].type === 'frame') {
replaceEmptyFrame(preparedNodes[0]);
finalizePageRootAfterApply();
return 1;
}
@ -388,6 +414,7 @@ export function upsertNodesToCanvas(nodes: PenNode[]): number {
}
adjustRootFrameHeightToContent();
finalizePageRootAfterApply();
const rootId = getGenerationRootFrameId();
if (rootId) scanAndFillImages(rootId).catch(() => {});
return count;
@ -397,6 +424,7 @@ export function upsertNodesToCanvas(nodes: PenNode[]): number {
function upsertPreparedNodes(preparedNodes: PenNode[]): number {
if (isCanvasOnlyEmptyFrame() && preparedNodes.length === 1 && preparedNodes[0].type === 'frame') {
replaceEmptyFrame(preparedNodes[0]);
finalizePageRootAfterApply();
return 1;
}
@ -420,6 +448,7 @@ function upsertPreparedNodes(preparedNodes: PenNode[]): number {
}
adjustRootFrameHeightToContent();
finalizePageRootAfterApply();
return count;
}
@ -493,6 +522,7 @@ export function extractAndApplyDesignModification(responseText: string): number
count++;
}
}
finalizePageRootAfterApply();
} finally {
useHistoryStore.getState().endBatch(useDocumentStore.getState().document);
}