From cfb5b70e151efea649dc10dd9dfd76d6664da332 Mon Sep 17 00:00:00 2001 From: Fini Date: Tue, 5 May 2026 12:22:42 +0800 Subject: [PATCH] fix(ai): dispatcher path runs post-pass cleanups (nav fill, role resolve, etc) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Visible regression in the GPT-5.5 food-app run: the bottom nav shipped with no surface fill (floating icons on the cream root background), even though `injectMissingNavSurfaceFill` was wired in and verified to add a fill on top-level nav-role frames. Live doc inspection showed `bottom-tab-bar` carrying `fill: undefined` post-generation — the inject pass simply never ran. Root cause: `orchestrator-sub-agent.ts` runs the dispatcher branch (Strategy A `` element-tools AND Strategy B JSONL-in- batch_design) and **early-returns before** reaching `applyPostStreamingTreeHeuristics(rootId)` further down in the function. That post-pass is what runs: - normalizeStrokeFillSchema - unwrapFakePhoneMockups - resolveTreeRoles + resolveTreePostPass - normalizeTreeLayout - stripRedundantSectionFills - injectMissingNavSurfaceFill - publish (forcePageResync) Skipping it on the dispatcher path means EVERY sub-agent that emits via element tools or JSONL fallback bypasses role resolution, layout normalization, redundant-fill stripping, AND nav-surface injection. The streaming path was the only branch that fired the cleanup. Fix: call `applyPostStreamingTreeHeuristics(subtask.parentFrameId ?? plan.rootFrame.id)` right before the dispatcher branch returns, when at least one node was inserted. The post-pass walks up to the page root via `getParentOf()` for the inject step, so passing the section root that the dispatcher inserted into is correct. This also un-blocks several heuristics that depend on the full subtree being in the store: button width / frame height equalization, clipContent on cards-with-image-children, and theme detection on the sub-agent's root (which feeds icon/text color defaults). --- .../src/services/ai/orchestrator-sub-agent.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/apps/web/src/services/ai/orchestrator-sub-agent.ts b/apps/web/src/services/ai/orchestrator-sub-agent.ts index 35b5a0079..896dba586 100644 --- a/apps/web/src/services/ai/orchestrator-sub-agent.ts +++ b/apps/web/src/services/ai/orchestrator-sub-agent.ts @@ -570,6 +570,22 @@ async function executeSubAgent( .filter((r) => r.status !== 'applied') .map((r) => `[${r.toolName}] ${r.message}`) .join('; ') || 'element-tool dispatch produced no nodes'; + // Run the same tree-aware post-pass the streaming path runs + // below at line ~630. Without this, dispatcher-path inserts + // (Strategy A `` chains AND Strategy B JSONL-in- + // batch_design fallback) skip role resolution, layout + // normalization, redundant-fill stripping, AND nav-surface fill + // injection. The visible regression: sub-agents that emit a + // `bottom-tab-bar` / `top-app-bar` / etc. without an explicit + // fill ship floating-on-cream nav rows, and banner sections + // miss the height/clipContent fix-ups that depend on + // role-resolved children. `subtask.parentFrameId` is the + // section root the dispatcher inserted into; the post-pass + // walks up to the actual page root for the inject pass. + if (inserted.length > 0) { + const postPassRootId = subtask.parentFrameId ?? plan.rootFrame.id; + applyPostStreamingTreeHeuristics(postPassRootId); + } return { subtaskId: subtask.id, nodes: inserted.length > 0 ? inserted : renderer.getInsertedNodes(),