From 6bcdd922134681bf2e83148156a475bec1a7a582 Mon Sep 17 00:00:00 2001 From: Fini Date: Wed, 29 Apr 2026 09:50:36 +0800 Subject: [PATCH] fix(ai): seedDocVariablesFromStyleGuide overwrites stale plan-derived keys Prior version skipped seeding when doc.variables had any entry, which left the second brief in a session resolving `$color-*` refs to the FIRST brief's palette while the prompt's ref + (hex) instruction advertised the new one. Fix: - Define PLAN_DERIVED_VARIABLE_NAMES (the 7 v1 token names this function manages) and always (re-)write them from the current plan. - Stale-key guard: if the new palette doesn't carry a name (e.g. ai-generated plan.styleGuide has no textMuted but a prior catalog brief seeded one), remove the stale entry so refs fall back to the default palette consistently with the new prompt. - design.md path bypasses this function entirely; user-set variables outside the 7-key set are never touched. --- apps/web/src/services/ai/orchestrator.ts | 44 +++++++++++++++++++----- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/apps/web/src/services/ai/orchestrator.ts b/apps/web/src/services/ai/orchestrator.ts index 430210aad..1e42ec887 100644 --- a/apps/web/src/services/ai/orchestrator.ts +++ b/apps/web/src/services/ai/orchestrator.ts @@ -482,6 +482,22 @@ function reorderDashboardMainChildren(plan: OrchestratorPlan, mainParentId: stri }); } +/** + * Plan-derived v1 token names that this function manages. Keep in sync with + * the mappings below — these are the keys we (re-)write on every plan run. + * User-owned variables outside this set (custom names, themed values) are + * never touched. + */ +const PLAN_DERIVED_VARIABLE_NAMES = [ + 'color-bg-deep', + 'color-surface', + 'color-text-primary', + 'color-text-body', + 'color-text-muted', + 'color-accent', + 'color-border', +] as const; + /** * Seed `doc.variables` with the plan's style-guide palette (mapped to v1 * semantic token names) so sub-agent JSONL output containing `$color-*` refs @@ -494,14 +510,18 @@ function reorderDashboardMainChildren(plan: OrchestratorPlan, mainParentId: stri * the default `#2563EB` blue — completely overriding any warm/orange/etc * style guide picked by the planner. * - * Skipped when `doc.variables` is already non-empty: respects user-seeded - * tokens (design.md, custom palette) without overwriting. + * Always overwrites the plan-derived keys: every brief gets a fresh plan, and + * the prompt's ref + (hex) instruction must match the resolved palette. + * design.md flows bypass this function entirely (sub-agent uses designMd + * policy instead of styleGuide injection). User-set variables outside the + * plan-derived key set are left untouched. + * + * Stale-key guard: also clears any plan-derived key the new palette doesn't + * carry (e.g. ai-generated `plan.styleGuide` has no `textMuted`, but a prior + * brief may have seeded one from a catalog guide). Without this, the prior + * brief's textMuted lingers and refs in this brief resolve to the wrong color. */ export function seedDocVariablesFromStyleGuide(plan: OrchestratorPlan): void { - const store = useDocumentStore.getState(); - const existing = store.document.variables ?? {}; - if (Object.keys(existing).length > 0) return; - const palette: Record = {}; if (plan.styleGuide?.palette) { @@ -525,8 +545,16 @@ export function seedDocVariablesFromStyleGuide(plan: OrchestratorPlan): void { if (Object.keys(palette).length === 0) return; - for (const [name, value] of Object.entries(palette)) { - store.setVariable(name, { type: 'color', value }); + const store = useDocumentStore.getState(); + const existing = store.document.variables ?? {}; + + for (const name of PLAN_DERIVED_VARIABLE_NAMES) { + const next = palette[name]; + if (next !== undefined) { + store.setVariable(name, { type: 'color', value: next }); + } else if (name in existing) { + store.removeVariable(name); + } } }