mirror of
https://github.com/nexu-io/open-design.git
synced 2026-09-20 06:15:06 +08:00
4b660237c0
* test(daemon): freeze scenario x section gating matrix for composeSystemPrompt
composeSystemPrompt splices ~30 conditional sections whose gates form an
implicit matrix (session mode x design system x skill surface x memory x
execution profile). Before any prompt-slimming refactor, this golden
snapshot freezes which sections each representative scenario receives,
so a re-gating regression surfaces as a snapshot diff instead of a
silent production behavior change.
Detection is line-anchored heading markers: discovery quotes headings
like "## Active plugin" mid-sentence, so bare substring matching would
false-positive on sections that were never spliced in. A companion
assertion fails when a marker goes stale (no scenario produces it), and
position invariants pin injection-resistance first / role-marker guard
last across every scenario.
* perf(prompts): stop shipping rare-path media/deck payloads on every run
Langfuse telemetry (24h, 2k-trace sample) puts the composed charter at
p50 96KB (~24K tokens) with a 21% stable-cache hit rate — most turns
resend it whole. Two always-on blocks serve paths most runs never take:
- MEDIA_DISPATCH_HINT (2.8KB into every non-media project) embedded a
40-line worked bash polling loop. The loop moves to 'od media help'
(the CLI already prints the exact next wait command on handoff), and
the hint keeps only the dispatch rules agents historically fumbled:
no provider REST calls, no API-key asks, POSIX not PowerShell,
python3 not jq, fal-ai/* passthrough. 2,771 -> 1,547 chars.
- The freeform maybe-deck branch injected the full 20.7KB deck
framework into every kind=other / no-metadata run 'just in case'.
New detectDeckIntentSignal() scans the same request text the agent
will see (transcript included, so a deck mention anywhere in the
conversation stays sticky) for generous EN/CJK deck vocabulary and
skips the framework when nothing matches: 78.5K -> 57.7K chars on
non-deck freeform runs. False positives fall back to today's
behavior; deck-kind projects stay unconditional. An absent signal
(contracts/BYOK composer, tests) preserves legacy always-inject.
Also relieves the POSIX argv budget: DS-active runs sat within 4KB of
the 100KB hard cap that fails argv-bound adapters (aider/deepseek).
Baseline check: tests/media/policy-routes.test.ts (7) and
tests/connection-test.test.ts (1, local Kimi CLI version) fail
identically on the unmodified tree — no new failures from this change.
* test(daemon): refresh golden matrix snapshot for upstream memory prompt hardening
Rebase onto main picked up #4913, which reworded the memory
rule-proposal block (+230 chars in memory-bearing scenarios). Section
gating is unchanged — size-only refresh.
* feat(prompts): rewritten slim core charter behind OD_PROMPT_CORE=slim
The always-on doctrine layer (DISCOVERY_AND_PHILOSOPHY ~28K chars +
designer charter ~14K + three tail overrides that re-assert rules the
layered stack states in softer or contradictory forms) is rewritten as
ONE ~15.5K-char document: core-slim.ts. Same composed inputs drop from
65.7K to 36.0K chars (-45%); freeform non-deck runs from 57.7K to 28.9K.
Design rules of the rewrite:
- Every rule stated exactly once, under an explicit precedence ladder
(user request > active skill > design system > memory > charter).
- Contradictions resolved instead of stacked: one verification budget
(static pass + P0 checklist + craft scan + at most ONE render; the
'two passes is normal' re-score loop is gone), one turn-1 form
decision path (four skip conditions listed in one place, including
the memory task-brief replacement), Tweaks reconciled (iteration
surface on request, never demo controls in product files).
- Protocol markers are frozen API and survive verbatim: question-form
shapes/ids, pick_direction/brand_spec/reference_match branch values,
task-type routing options, data-od-id, EDITMODE markers, pinned
React/Babel/Motion tags, artifact handoff shapes.
- Emphasis quota: no IMPORTANT/CRITICAL inflation; imperatives carry
the weight.
Rollout: ComposeInput.promptCoreVariant ('classic' default | 'slim'),
daemon flips via OD_PROMPT_CORE=slim — zero behavior change until set,
A/B-able per daemon. Ask mode keeps the clarifying-questions tail (it
composes no core charter). Guards in tests/prompts/core-slim.test.ts:
a 16KB byte budget that fails any silent regrowth, frozen-marker
assertions, handoff profile switch, and classic-parity checks; matrix
snapshot gains slim scenarios.
Not yet covered (follow-ups): the packages/contracts BYOK composer
still ships only the classic stack; instruction-following A/B against
classic is the sign-off gate before changing the default.
* feat(prompts): shrink the slim core to an 8KB budget by evicting what a system prompt shouldn't carry
The 15.5K v1 slim charter still carried content that has no claim to an
always-on seat. v2 evicts it by category:
- Already owned elsewhere: the od-default task-type router form ships in
plugins/_official/scenarios/od-default/SKILL.md and reaches the prompt
as '## Active skill'; the charter now just defers to skill-owned
turn-1 forms. An ownership test pins the form to the skill file.
- Conditional-path content: per-platform delivery contracts (breakpoint
matrix, per-target files, native chrome) move to
PLATFORM_CONTRACTS_BLOCK, injected only when the project declares a
platform or multiple targets. Deck rules were already duplicated by
the deck-gated directive - dropped from the charter entirely.
- Model-competence content: PDF extraction how-tos, attached-image
explanations, JSON syntax reminders - deleted.
- Recital: the default discovery form example keeps only the brand
question verbatim (its pick_direction/brand_spec/reference_match
values are the one protocol-matched part); the other six questions
become a one-line recipe. Option labels were always model-localized
per language, so exact English menus were never stable anyway.
- React pins keep versions (the actual bug fix: 18.3.1 pairing +
framer-motion React build) but drop the SRI hashes and script-tag
boilerplate - reviewers should weigh the integrity-attribute loss.
Charter now renders at 8,187 bytes (budget test tightened 16KB -> 8KB).
Composed prompt for the common design path: 65.7K classic -> 29.6K slim
(-55%); freeform non-deck: 57.7K -> 21.5K (-63%).
* perf(prompts): evict the remaining always-on payloads from the slim stack
Follow-up to the 8KB charter: the composed slim prompt still carried
~13K of non-charter payload. Four evictions, all slim-only (classic
stays byte-stable for the A/B):
- Project metadata becomes a fact sheet ('facts' style). The classic
block had grown seven doctrine rules plus a responsive contract and
cross-platform rule - prose owned by the slim charter's Craft section
and PLATFORM_CONTRACTS_BLOCK. facts keeps key-value fields, media
workflow data, and two compact delivery lines (screen files, product
depth). kind=other: 3.6K -> ~1.1K.
- Memory two-loop scaffolding compressed ~5.5K -> ~2.9K: identical
headings and od-card shapes (web parses the card types; the daemon
checks the verify-scorecard), repeated rationale prose cut.
- zh-CN locale override drops the worked quick-brief sample copy under
slim - it mirrored the classic form verbatim, which slim recipes
instead; the form contract already mandates localization. 2K -> 0.85K.
- MEDIA_DISPATCH_HINT gates on detectMediaIntentSignal (same
transcript-sticky pattern as the deck signal): most conversations
never mention media, and the hint returns for the rest of the session
the moment one does.
- Direction library becomes a pull layer: slim composes a ~1.3K
id+label index and the agent fetches the chosen direction's full
palette/posture spec via the new 'od tools directions --id <id>'
CLI (also --label / --json / bare list). One tool call on the
direction-pick path, which runs at most once per project, replaces
6.7K of always-on palette data. Classic keeps the inline library.
Composed slim sizes: freeform non-deck 21.5K -> 12.6K; design full
stack 29.6K -> 22.2K (fixture DS; media hint drops another 1.4K on
non-media conversations). Baseline classic: 57.7K / 65.7K.
* refactor(prompts): restructure the slim prompt as static-first layers for cache and hierarchy
Two structural defects in the slim composition, both flagged against
prompt best-practice guidance:
1. Heading hierarchy was inverted: the document opened with an H2
('## Security: prompt injection resistance') and the H1 charter
appeared six sections later. The security section now lives INSIDE
the charter as an H2 right after Precedence (single source moved to
core-slim.ts; the classic stack imports it for its unchanged
standalone-first position), so the composed document reads
identity-first with a coherent H1 > H2 tree.
2. Ordering was cache-hostile: conditional blocks (mode override,
locale, direction index, platform contracts) preceded the static
charter — the exact 'conditional prefix sections' anti-pattern from
the prompt-caching guidance, where every flag combination becomes a
distinct prefix and nothing is shared. Slim now composes in
variability tiers:
static charter (identical across all runs — shared cacheable
prefix) -> conversation-stable (mode, locale) -> project-stable
(direction index, frames, platform, memory, instructions, DS,
skill, metadata, MCP) -> turn-variable (maybe-deck and media-hint
signal blocks, deferred to after MCP so a mid-conversation signal
flip only invalidates the suffix) -> recency-pinned role guard.
The charter's Precedence section now states that a session-mode
directive appearing after it takes precedence, replacing the
positional 'read first' authority the old ordering relied on.
Classic composition is byte-untouched (A/B baseline); ask mode keeps
the classic head. Charter budget 8KB -> 9.25KB (absorbed the ~1KB
security section; composed totals unchanged: freeform 12.7K,
full-stack 22.4K). New layered-ordering test pins the tier sequence
with line-anchored markers; the matrix position invariant now asserts
the per-variant head.
* fix(prompts): resolve five structure/expression defects from the charter review
Findings from the senior-review pass, each with a concrete failure mode:
1. Turn-ordinal section names misfire mid-session. 'Turn 1/2/3+' made
the model treat 'now is not turn 1' as license to skip the fresh-
brief flow when a NEW artifact is requested at turn 8 — and,
conversely, an event-based fix alone would have re-asked questions
the session already answered. Sections are now event-named ('On a
fresh brief' / 'When the brand answer arrives' / 'Once direction is
locked') AND a session-inheritance rule makes the mid-session case
explicit: a fresh brief inherits the conversation's locked
direction/brand/audience, asks only genuinely new unknowns, and
builds directly when there are none — matching current production
behavior with zero extra rounds or cache cost.
2. The handoff rule — the single highest-cost-if-violated output
protocol (never emit <artifact> in filesystem runs) — was the only
rule block without a heading. Promoted to '## Handoff'.
3. The form example read backwards: it showed the LAST question
(brand) and described the earlier six in reverse ('preceded
in-form by'), the only place in the prompt requiring the model to
assemble structure in reverse. Now forward-order: output question
shown in full, brand question shown in full (its values are the
protocol-matched part), the middle questions listed as
insert-between recipes.
4. Dangling reference: Craft said 'single family only for
tech/utility' — a direction name defined only in the Direction
index, which is absent whenever a design system is active.
Reworded direction-independent.
5. direction-cards was dead vocabulary: listed as a legal control but
every mention forbade it. Its one legitimate path is now named —
only when the user explicitly asks to see direction options.
Charter budget 9.25KB -> 10KB: fix #3 cost ~0.7KB (restoring the full
output-question JSON), accepted deliberately — it also restores
few-shot density for weaker fleet models, and the review's own
conclusion was that the last byte-squeezing round had negative ROI.
* fix(prompts): identity-first head for slim ask mode
The last remaining hierarchy inversion: slim ask still opened with the
H2 security block before the H1 ask override. Ask now matches design
mode's structure — the ask override (the turn's whole charter) opens
the document and the security section reads as its first subsection.
Both blocks are static, so the swap is cache-neutral. Classic remains
byte-untouched.
* fix(prompts): repair five slim-vs-classic regressions from the second audit
Second senior review against the shipping classic stack surfaced two real
bugs and four wrongly-compressed classic behaviors in the slim variant:
- text_artifact/BYOK runs got the direction INDEX plus an instruction to
run the `od tools directions` CLI they do not have; classic inlined the
palettes. Slim now profile-switches: filesystem keeps the 1.3K pull
index, text_artifact gets the full inline library back (+1.35K tokens
on that path only; still ~68% below classic, and now under deepseek's
30KB Windows argv cap, which classic never was).
- API_MODE_OVERRIDE's literal scope ('every LATER instruction … is
overridden') inverted when slim moved the charter ahead of it: the
charter's TodoWrite/render-check instructions escaped the override.
Plain-stream slim runs now compose the override first again. Pure
reorder, zero bytes.
- PLATFORM_CONTRACTS_BLOCK gated only on project metadata, missing
freeform briefs that name a platform in text ('做个 iOS app 原型').
New detectPlatformIntentSignal (same generous EN/CJK shape as the
deck/media signals) ORs into the gate; server wires it.
- media-contract's 'explicit layer overrides' paragraph cross-referenced
the classic-only layer names; reworded layer-agnostic (classic +19B).
- Restored four classic rules the compression pass misfiled as
model-competence filler: never hot-link user-attached images by URL
(product constraint), skill/DS per-domain precedence clause, expressive
form-control guidance, and the modern-CSS encouragement line. Charter
budget 10,240 -> 10,752 with rationale in the test comment.
Classic bytes untouched except the shared media-contract rewording.
Golden matrix: gating unchanged; sizes only (classic +19, slim +537).
Pre-existing failures unrelated to this change and failing on the
unmodified baseline: tests/media/policy-routes.test.ts (7), Kimi
connection test (local CLI version mismatch).
* fix(prompts): defer signal-triggered platform contracts to the turn-variable suffix
Two follow-ups from the third structure audit:
- PLATFORM_CONTRACTS_BLOCK sat in the project-stable zone while its gate
now includes the turn-variable conversation-text signal. A mid-session
'make it an iOS app' would insert the block early and break the cached
prefix for every section after it (DS, skill, memory, metadata, MCP).
The gate is now split by trigger stability: metadata triggers keep the
early slot (fixed at project creation), signal-only triggers defer to
slimTurnVariableParts like the deck/media signal blocks. The
slimTurnVariableParts declaration hoists above the design section so
the gate can reach it; no byte or ordering change for any existing
gating combination.
- Slim ask mode on a plain stream composed CHAT -> injection -> API
override, inverting classic's authority order (API before CHAT, so
'overrides every rule below' covers the chat charter). The ask head
now leads with the API override on plain streams; the later push is
gated on !isSlimCore. Zero-byte reorder; the two overrides do not
conflict in content, this restores parity with the shipping order.
* fix(prompts): restore form-tailoring mandate and first-message form scope
The slim-vs-classic blind A/B eval (2026-07-04, 17 pairs, double-order
opus judging) surfaced two form regressions with converging judge
rationales:
- Form quality lost 12:1:4 to classic. Mechanism: classic's discovery
layer carries extensive per-brief form-tailoring guidance; slim
compressed it to the single word 'tailor'. Models shipped the default
recipe verbatim — app-platform checkboxes on landing-page briefs, the
fundraising ask/traction buried in one textarea placeholder. Restored
as an explicit mandate with concrete per-brief examples, a
plausible-targets-only rule for the platform options, and a
translate-the-labels note on the tone recipe.
- DS + detailed-brief projects skipped the turn-1 form 3/6 vs classic
1/6: the mid-session inheritance clause generalized to first
messages. Added the explicit scope limit: inheritance never applies
to a project's first message.
Charter budget 10,752 -> 11,264 with the eval citation in the test
comment. Golden matrix: sizes only.
* fix(prompts): template tokens in the seed must be replaced before handoff
The 40-task x 5-turn A/B eval (2026-07-05) found slim's remaining T2
quality gap is driven by catastrophic failures, not taste: 3 of 39
pairs shipped the live-artifact skill seed with unrendered {{mustache}}
tokens (classic did this once). Mechanism: slim compressed classic's
seed-handling guidance to 'copy the seed, paste layouts', losing the
explicit fill-with-real-content step.
- Read-before-write bullet now ends with: replace EVERY template token
with real content; a {{placeholder}} or empty section reaching the
user is a failed delivery. (Also covers the eval's 4 thin-content
failures.)
- Self-check static pass adds 'leftover template tokens' to its scan.
Charter 11,155B, within the existing 11,264 budget — no bump needed.
* fix(prompts): first build ships complete, no stub sections
Clean-scope T2 analysis of the 40-task eval (excluding the 10 tasks
confounded by the live-artifact skill protocol) still shows a first
build completeness gap: main 7.34 vs slim 6.10, driven by thin-content
deliveries (empty canvases, placeholder-looking blocks) on 4 of 29
pairs. The charter's 'show something visible early' line, uncushioned,
reads as license to hand off the early skeleton.
- Qualify it: '…but the turn ships the COMPLETE artifact: no stub
sections.'
- Self-check static pass adds 'empty or stub sections' to its scan.
11,215B, within the 11,264 budget.
* refactor(prompts): split the overloaded turn-1 form section by concern
The slim charter's '## On a fresh brief' H2 bundled six distinct
concerns under one heading — trigger, default action, mid-session
inheritance, skip conditions, form-shape tailoring, and the form JSON
contract — so a maintainer could not scan the outline to find any one
rule, and the cross-cutting form contract (valid on ANY turn) was buried
under a heading scoped to fresh briefs.
Behavior-equivalent restructure (every directive's wording preserved;
only headings added and the form contract's 'any turn' note moved from a
parenthetical to the section lead):
- ## Turn 1 — the discovery form (trigger + default action)
- ### When to skip or inherit the form (inheritance + skip conditions)
- ## Writing a `<question-form>` (cross-cutting: shape + tailoring + example + ordering)
- ### Form contract (JSON validity + type enum + localization)
The charter outline now reads as a per-concern document. Frozen protocol
markers (form JSON, brand values, type enum, allowCustom) unchanged.
Charter 11,242 -> 11,322B; budget raised to 12,288 for readability
headroom at the maintainer's direction (budget explicitly deprioritized
for this change). Golden snapshot regenerated; 113 prompt tests green.
* refactor(prompts): readability second pass + multi-turn DS/constraint adherence
Two changes, from a critical re-read plus production feedback.
A) Readability / logic second pass (behavior-equivalent, wording preserved):
- Heading style: the two remaining imperative-tail H2s became event
labels with the directive moved to the body lead ('## When the brand
answer arrives', '## Once direction is locked'), so the outline reads in
one consistent grammar. Dropping '…hand off' also fixes a heading that
promised handoff while the rules live in the next section.
- The self-check bullet's inline (1)(2)(3)(4) run-on became a four-item
sub-list — the least-scannable line is now the most scannable.
- The 70-word 'Read before you write' sentence split into shorter units;
'show early / ship complete' separated into its own bullet so the two
ideas stop reading as contradictory.
- Precedence: skill and DS collapsed into one rank split by domain (was
numbered 2 vs 3, contradicting the 'each highest in its own domain'
clause) — removes the only real logical seam in the ladder.
B) Multi-turn edit adherence (new '## On an edit or tweak' section):
Production feedback reports the design system and explicit user
constraints frequently drift during multi-turn edits. The charter was
turn-1/first-build heavy and had no standing edit-mode invariant. Added,
in the lifecycle between build and handoff:
- an edit touches ONLY what the user named; untouched sections/values
stay as they were; re-read the file and edit in place.
- the design system binds on EVERY turn, not just first build — no
drift, no raw hex, no palette re-pick because this turn was about
something else.
- every hard constraint stated this session persists across later turns
until the user relaxes it; a new request adds to constraints, never
silently resets them.
Charter 11,322 -> 12,029B, within the 12,288 budget (no raise). New
frozen assertion pins the adherence invariants against future
compression. 114 prompt tests + guard + typecheck green; golden snapshot
regenerated.
* refactor(prompts): two-tier charter — lifecycle spine, grouped pillars, precedence precision
From a senior word-by-word re-read. The charter was 13 flat H2s spanning
three conceptual tiers (foundations / lifecycle / reference) with no
visible spine, and the precedence wording could invert authority.
Structure (behavior-equivalent, rule wording preserved):
- 13 flat H2 -> 5 H2: two foundations (Precedence, Security) + three
pillars, each grouping its H3s —
## Discovery — ask before you build (Turn 1, skip/inherit, writing, contract)
## Delivery — brand → build → iterate (brand answer, build, edit, handoff)
## Craft & contracts (craft, technical, conduct)
The delivery lifecycle is now one connected, ordered spine — 'Editing
an existing artifact' reads as a named phase of it, which should help
the multi-turn drift the edit-adherence rules target.
Precision fixes:
- Precedence: 'On conflict, higher wins: 1…2…3…4' was ambiguous (which
number is higher?) — reworded to an explicit list, 'the one higher on
this list wins — the user's request is the highest authority, this
charter the lowest', and the trailing pronoun tightened to 'overrides
it wherever the two conflict'.
- 'no tool calls or file reads first' -> 'before it'; 'Resolve the
source' -> 'Resolve the brand source'; 'restyle the whole thing' ->
'restyle beyond the request'.
- Trimmed one of three localization restatements (kept the authoritative
one in Form contract, now naming labels/options/placeholders) and the
'however complete the brief reads' duplicate.
Handoff/Craft/Technical/Conduct headings demote to H3 under their
pillars (local constants, not shared with classic). Charter 12,029 ->
12,294B; budget 12,288 -> 13,312. Ordering/adherence test anchors and
golden snapshot updated. 114 tests + guard + typecheck green.
* fix(prompts): make edit-adherence forceful — do exactly what was asked, verify it landed
Production feedback and maintainer review: the edit section addressed
only scope (don't over-change) and persistence (DS/constraints), but not
the core instruction-following failure — 'the user asked for A and the
result isn't A'. The whole section was defensive; the positive imperative
that the requested change must actually happen, completely, was missing.
Strengthened '### Editing an existing artifact':
- Leads with the imperative: the delivered file must actually be A — do
exactly what was asked, in full, in every place it applies, with
complete-vs-partial examples ('dark green' = every element, not one).
- Forbids reinterpreting, 'improving on', partially applying, or
substituting own taste for the user's literal words — ties to
Precedence #1 (user's request is highest authority).
- New 'Verify before you hand off' bullet: open the file, confirm the
change is actually present and complete and no in-force constraint
broke; never report a change you did not make (the claim-vs-reality
failure).
- Kept the scope / DS-binding / constraint-persistence bullets.
Charter 12,294 -> 13,122B, within the 13,312 budget. Frozen assertion
updated to pin the positive imperative and the verify bullet.
* fix(prompts): a later explicit request overrides a conflicting earlier constraint
Edge case raised in review: turn 2 'fix the color to blue, don't change
it', turn 4 'change everything to yellow'. Correct behavior is turn 4
wins (the user is changing their own mind; yellow becomes the new
standing constraint), which Precedence #1 + 'persist until the user
relaxes it' already imply — but the old bullet also said 'a new request
ADDS to those constraints', which a literal model could read as
additive-only and stall on the turn-2 'don't touch it'.
Rewrote the constraint-persistence bullet to make the override explicit:
constraints carry forward, only the user can lift or change one, and a
later explicit request overrides a conflicting earlier constraint — with
the blue->yellow turns as the in-prompt example. The anti-silent-drop
guard (model must never drop a standing constraint on its own) is kept.
Charter 13,122 -> 13,412B; budget 13,312 -> 14,336.
* fix(prompts): make the plan step agent-agnostic — TodoWrite is Claude-only
Open Design drives many local code agents; the real session mix is ~65%
non-Claude (codex, opencode, gemini, ACP family, …). The slim charter's
plan step hardcoded 'first tool call is TodoWrite' + in_progress/completed
— a Claude Code tool those agents don't have. For ~2/3 of traffic that
instruction is dead: the agent either skips planning (losing the exact
lever that helps multi-turn adherence) or hallucinates a tool it lacks.
Everything else in the slim charter was already agent-agnostic (render
checks and direction pulls go through the shell OD CLI wrapper; protocol
markup is assistant text), so TodoWrite was the one hardcoding.
- Charter plan step → generic: 'lay out a short, updatable plan; if your
runtime has a structured plan/todo/task tool, use it, else a numbered
list; never call a tool you don't have.'
- Composer injects a concrete note ('Your plan tool is TodoWrite; the
host renders it as a live Todos card; mark in_progress/completed') only
for Claude-family runs (streamFormat claude-stream-json: claude,
codebuddy, amp) — the only agents with TodoWrite and the host's live
Todos card. Agent-specifics live in the composer, portable doctrine in
the charter.
- API_MODE_OVERRIDE reframed from 'TodoWrite/Read/… are unavailable' to
'any tool call — TodoWrite/…/or whatever your runtime exposes — will
not execute'. Keeps the specific names (issue #313 anti-pseudo-tool,
test-pinned) while covering non-Claude runtimes.
Two frozen tests pin: charter has no TodoWrite + generic wording;
Claude-family gets the note, json-event-stream doesn't. 116 tests +
guard + typecheck green. Charter 13412 to 13543B (budget 14336).
* feat(prompts,live-artifacts): render one-level repeats, tighten slim charter craft guards
- live-artifacts/render: implement the documented `data-od-repeat="item in data.path"`
one-level repeat directive (specified in the schema but never rendered), with
loop-scoped `{{item.*}}` bindings, single-pass expansion so data values are never
re-interpolated, and explicit rejection of nested repeats / non-array sources.
- live-artifact SKILL.md: state the html_template_v1 binding contract in the workflow
(flat data.* scalars + data-od-repeat lists; no hand-loops, no bare loop vars) so
templates stay within the renderer's capability.
- core-slim charter: "replace every template token" now yields to a skill's
template-plus-data protocol; add an objective Layout-integrity craft rule (no
overlap, no clipping, oversized display type must fit its column, charts use fills
not outlines) and name those failure modes in the self-check.
* feat(prompts): add agent-agnostic production-value craft rule to slim charter
Visual-first pages regressed on slim: hero images were hand-drawn CSS boxes
instead of generated art (P1), and controls read flat/greyscale (P5). The slim
charter had dropped all of main's imagery + visual-system guidance.
Add one "Production value — feel shipped, not greyscale" craft rule: generate a
real image via the Open Design media tool (referenced through the injected
`"$OD_NODE_BIN" "$OD_BIN"` wrapper, so it is runtime-agnostic — not tied to any
one code agent) when a hero/product shot would carry the page; ship a real
product palette, colored interaction states, and elevated primary controls.
Bump SLIM_CORE_BYTE_BUDGET 14_336 -> 15_360 to seat this load-bearing guidance.
* feat(prompts): generalize the production-value imagery rule beyond hero shots
40-task eval + user review (C3 tea gift-box, P1) showed slim under-generates real
imagery on visual-first work, defaulting to hand-drawn schematic graphics. Reframe
the rule from a narrow "hero/product shot" trigger to a general default: whenever a
real picture would lift the artifact (product, place, food, person, hero, texture),
generate it via the media tool; drop to a diagram/UI mock only when that genuinely
serves the content better. Still agent-agnostic (media tool via the injected
"$OD_NODE_BIN" "$OD_BIN" wrapper).
* fix(prompts): let the production-value rule use the run's own image generation
Hard-pointing imagery at `od media generate` only works when a daemon spawned the
agent (it injects OD_PROJECT_ID); a standalone agent (eval harness, external
runtime) can't reach it, so slim silently fell back to hand-drawn placeholders
while codex-run peers used their native imagegen. Broaden the rule: generate through
whatever image generation the run offers — the Open Design media tool when wired up,
otherwise the runtime's own native image generation. Verified: C3 slim now emits a
real generated product photo instead of a schematic box.
* test(daemon): drop connected-external-mcp from the golden matrix
Upstream moved the connected-external-MCP directive out of
composeSystemPrompt into the per-turn slice (live OAuth token state must
stay out of the cached stable prefix), so it is no longer a compose
input or a gateable section.
* chore(prompts): default to the slim charter on the validation branch
VALIDATION-ONLY commit for feat/system-prompt: packaged beta builds must
exercise the rewritten slim charter, and the packaged sidecar env
allowlist does not forward OD_PROMPT_CORE, so the default flips to slim
here. OD_PROMPT_CORE=classic restores the classic stack. Do not carry
this default into main.
* perf(prompts): batch slim tool work, with quality guards restored (#5555)
* perf(prompts): batch slim tool work
* fix(prompts): restore seed-copy rule and unconditional own-browser ban
The tool-economy pass (02923153a) dropped two quality instructions as
collateral while compressing the read/preview bullets:
- 'Copy the seed and paste its layouts — don't write CSS from scratch'
guards the visual floor when a skill ships a seed template.
- The hard 'never your own browser' ban was softened to 'no browser
probes first', which let the candidate arm reach for Playwright after
an export failure (2026-07-13 slim-tool-economy eval, v1_001 turn 3).
Restore both inside the batched tool-economy wording, raise the charter
byte budget 15_360 -> 15_616 following the existing restore-precedent,
and freeze both lines as regression markers.
---------
Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>
* feat(prompts): prefill question forms with a recommended default per question (#5556)
Every <question-form> the agent emits now ships a brief-inferred
recommended `default` on each question — an option value for
radio/select, an array of values for checkbox, concrete text for
free-text fields — so the user can review and submit the form unchanged
instead of answering from scratch. Applied across the slim charter form
contract, the full discovery rules, and the od-default task-type router
(whose options stay verbatim; prefill is the one permitted tailoring).
Both prompt mirrors (daemon + contracts) updated in sync, with the
example forms anchoring the pattern via "default": "pick_direction".
The web renderer already honoured defaults, with one gap: the `default`
alias only accepted scalars, so a checkbox array default was silently
dropped (parseDefaultValue). Fixed alongside, with parser coverage for
the radio/checkbox/text shapes the prompt now instructs.
Charter byte budget raised 15_616 -> 15_872 for the new bullet +
example anchors; prefill markers frozen in core-slim and
discovery-form tests.
Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>
* fix(prompts): make form prefill survive streaming key order; add web search/fetch imagery fallback (#5558)
* fix(web): adopt streamed question-form defaults that arrive after the question renders
The #5556 prefill contract asks the model to ship a recommended `default`
on every <question-form> question, but whether the prefill actually showed
up depended on JSON key order: the partial-JSON parser reveals a question
as soon as its label lands, and a `default` emitted after `options`
(observed in production run fca86faa) arrived only after the reveal pass
had already parked the question on its auto-assigned empty value — which
the backfill effect then treated as "answered" and never touched again.
Fix on two sides:
- Web: QuestionFormView now tracks which questions the user actually
touched (seeded from submitted/draft snapshots) and adopts a
late-arriving streamed default via shouldAdoptStreamedDefault — only
while the question is untouched AND still holds its auto-assigned empty
value, so a late default can never clobber a real answer or an
intentional clear (checking then unchecking a box stays cleared).
- Prompts (daemon + contracts mirrors, slim charter + full discovery
rules): instruct models to write `default` before `options`, as the
example forms already anchor, so the recommendation streams in with the
question instead of trailing a long options array.
Red spec: "adopts a default that streams in after the question was
revealed" went red on the pre-fix tree and green with this change; a
companion guard pins that "untouched" means never-interacted, not
currently-empty.
* feat(prompts): fall back to web search/fetch imagery when image generation is unavailable
The production-value craft rule tells runs to reach for real imagery
through the OD media tool or the runtime's native image generation — but
a run with neither (BYOK agents without media config, runtimes without
imagegen) had no sanctioned next step and shipped the exact fallbacks the
rule bans: empty slots, schematic boxes, flat icons.
The slim charter's imagery guidance is now an explicit resolution chain:
OD media tool → native image generation → web search / web fetch. The new
final step has the run find a fitting real photo, pull it into the
project (e.g. curl), and reference it by relative path — never hot-link
the remote URL, matching the existing no-hot-link file rule so artifacts
don't rot with dead image links.
Charter byte budget raised 15_872 -> 16_128 for the fallback sentence,
documented in the budget history; a new frozen-marker test pins the three
chain steps and the no-hot-link wording so a future tool-economy pass
can't silently drop them.
---------
Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>
* chore(prompts): land #5573 form refinements on the system-prompt validation line (#5575)
* fix(web): adopt streamed question-form defaults that arrive after the question renders
The #5556 prefill contract asks the model to ship a recommended `default`
on every <question-form> question, but whether the prefill actually showed
up depended on JSON key order: the partial-JSON parser reveals a question
as soon as its label lands, and a `default` emitted after `options`
(observed in production run fca86faa) arrived only after the reveal pass
had already parked the question on its auto-assigned empty value — which
the backfill effect then treated as "answered" and never touched again.
Fix on two sides:
- Web: QuestionFormView now tracks which questions the user actually
touched (seeded from submitted/draft snapshots) and adopts a
late-arriving streamed default via shouldAdoptStreamedDefault — only
while the question is untouched AND still holds its auto-assigned empty
value, so a late default can never clobber a real answer or an
intentional clear (checking then unchecking a box stays cleared).
- Prompts (daemon + contracts mirrors, slim charter + full discovery
rules): instruct models to write `default` before `options`, as the
example forms already anchor, so the recommendation streams in with the
question instead of trailing a long options array.
Red spec: "adopts a default that streams in after the question was
revealed" went red on the pre-fix tree and green with this change; a
companion guard pins that "untouched" means never-interacted, not
currently-empty.
* feat(prompts): fall back to web search/fetch imagery when image generation is unavailable
The production-value craft rule tells runs to reach for real imagery
through the OD media tool or the runtime's native image generation — but
a run with neither (BYOK agents without media config, runtimes without
imagegen) had no sanctioned next step and shipped the exact fallbacks the
rule bans: empty slots, schematic boxes, flat icons.
The slim charter's imagery guidance is now an explicit resolution chain:
OD media tool → native image generation → web search / web fetch. The new
final step has the run find a fitting real photo, pull it into the
project (e.g. curl), and reference it by relative path — never hot-link
the remote URL, matching the existing no-hot-link file rule so artifacts
don't rot with dead image links.
Charter byte budget raised 15_872 -> 16_128 for the fallback sentence,
documented in the budget history; a new frozen-marker test pins the three
chain steps and the no-hot-link wording so a future tool-economy pass
can't silently drop them.
* feat(forms): Other-chip escape hatch, 5-question cap, form-language copy, and streamed-default truncation fix (#5573)
* feat(forms): host-owned Other chip, 5-question hard cap, and form-language host strings
Three form-UX upgrades that shipped together on the validation branch:
1. Host-owned "Other" escape hatch. Finite-choice questions no longer show
an always-on "Custom answer" input; the host injects a localized,
dash-bordered "Other" chip (an "Other…" entry for selects) that expands
into the type-in field via the shared accordion motion. Opening it on a
single-choice question deselects the fixed options; picking a fixed
option collapses a still-empty field; checkbox custom entries coexist.
Prompts (slim charter + discovery mirrors) now ban model-authored
catch-all "Other …" options — they would duplicate the host's — and the
od-default router's taskType question pins allowCustom: false in all
three copies since its own "Other" option IS the route.
2. Hard cap: 5 questions per form. The old "keep ≤7" guidance let the
model pad forms to the example's length. The rule is now an explicit
count-then-cut step ("count your draft; delete the least build-critical
until 5 remain"), and the discovery example itself was trimmed from 7
to 5 questions so the anchor matches the cap.
3. Copy + language. Form copy anchors lead with the benefit ("Prefilled
for you — send as is, or tweak anything first"); the localization rule
demands native phrasing with a concrete wrong-vs-right anchor (快速确认,
not the literal 快速简报); and forms now declare a top-level "lang"
BCP-47 tag that the web card uses to render its own strings (Other
chip, custom-answer copy, submit CTA) in the form's language instead of
the UI locale — no more English "Other" chips inside a Chinese form.
New i18n key qf.otherOption ships in all 19 locales.
Charter byte budget 15_872 → 16_896 across the two documented bumps, with
frozen-marker tests pinning the escape-hatch delegation, the hard cap, the
native-localization rule, and the lang anchor.
* fix(web): never surface a mid-stream question-form default
The streaming shaper reveals a question the moment its label parses, and
runs every question object — including the trailing, still-open one —
through mapRawQuestion. That let a MID-STREAM `default` reach the card:
the partial-JSON repair terminates it early, so "单位教育" surfaces as
"单位教" and ["历史背景与经过", "抗战精神与当代意义"] as ["历史背景与经过",
"抗战精"]. The card adopts a streamed default only while the answer is
still empty, so a truncated adoption freezes garbage that the completed
value can never overwrite — checkbox defaults (long arrays, near-certain
to be cut across delta chunks) effectively never prefilled, while short
radio scalars usually survived inside one chunk. Observed in production
run beaf2da0 (抗日主题汇报PPT): both maxSelections checkboxes rendered
with nothing selected despite the model emitting complete defaults.
Fix at the source: the trailing in-flight question exposes NO
defaultValue until its braces close. A closed object's braces are
balanced, so its default is complete; the card's empty-and-untouched
adoption rule then fills it exactly once, correctly.
Red spec reproduces the production body cut mid-default (went red on the
pre-fix tree) and pins that a closed question's default surfaces while
the next in-flight question's stays hidden.
* fix(prompts): align task-type form contract across all three copies; fit the slot menu inside the 5-question cap
Review follow-ups from PR #5573 (nettee):
- The od-default skill's task-type form now carries the same top-level
"lang" tag as the daemon/contracts prompt copies, so host-rendered
controls on that path key off the form language instead of falling back
to the app locale. A new parity check reads all three copies and locks
both contract keys ("lang" + taskType allowCustom: false) so the skill
copy cannot drift again.
- The slim charter's default-shape recipe contradicted the new hard cap:
"Between output and brand, in this order: platform, audience, tone.
After brand: scale, then constraints" prescribed 7 questions in the
same breath as "Hard cap: 5". The recipe is now 2 fixed slots plus a
pick-AT-MOST-3 menu (same option vocabulary, same order), and the
frozen-marker test asserts the menu wording is present while the old
prescriptive sequence is gone — the charter can no longer carry both.
---------
Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>
---------
Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>
Co-authored-by: open-design-crew[bot] <299007234+open-design-crew[bot]@users.noreply.github.com>
* chore(prompts): land #5586 photo-overlay discipline on the system-prompt validation line (#5600)
* fix(web): adopt streamed question-form defaults that arrive after the question renders
The #5556 prefill contract asks the model to ship a recommended `default`
on every <question-form> question, but whether the prefill actually showed
up depended on JSON key order: the partial-JSON parser reveals a question
as soon as its label lands, and a `default` emitted after `options`
(observed in production run fca86faa) arrived only after the reveal pass
had already parked the question on its auto-assigned empty value — which
the backfill effect then treated as "answered" and never touched again.
Fix on two sides:
- Web: QuestionFormView now tracks which questions the user actually
touched (seeded from submitted/draft snapshots) and adopts a
late-arriving streamed default via shouldAdoptStreamedDefault — only
while the question is untouched AND still holds its auto-assigned empty
value, so a late default can never clobber a real answer or an
intentional clear (checking then unchecking a box stays cleared).
- Prompts (daemon + contracts mirrors, slim charter + full discovery
rules): instruct models to write `default` before `options`, as the
example forms already anchor, so the recommendation streams in with the
question instead of trailing a long options array.
Red spec: "adopts a default that streams in after the question was
revealed" went red on the pre-fix tree and green with this change; a
companion guard pins that "untouched" means never-interacted, not
currently-empty.
* feat(prompts): fall back to web search/fetch imagery when image generation is unavailable
The production-value craft rule tells runs to reach for real imagery
through the OD media tool or the runtime's native image generation — but
a run with neither (BYOK agents without media config, runtimes without
imagegen) had no sanctioned next step and shipped the exact fallbacks the
rule bans: empty slots, schematic boxes, flat icons.
The slim charter's imagery guidance is now an explicit resolution chain:
OD media tool → native image generation → web search / web fetch. The new
final step has the run find a fitting real photo, pull it into the
project (e.g. curl), and reference it by relative path — never hot-link
the remote URL, matching the existing no-hot-link file rule so artifacts
don't rot with dead image links.
Charter byte budget raised 15_872 -> 16_128 for the fallback sentence,
documented in the budget history; a new frozen-marker test pins the three
chain steps and the no-hot-link wording so a future tool-economy pass
can't silently drop them.
* feat(forms): Other-chip escape hatch, 5-question cap, form-language copy, and streamed-default truncation fix (#5573)
* feat(forms): host-owned Other chip, 5-question hard cap, and form-language host strings
Three form-UX upgrades that shipped together on the validation branch:
1. Host-owned "Other" escape hatch. Finite-choice questions no longer show
an always-on "Custom answer" input; the host injects a localized,
dash-bordered "Other" chip (an "Other…" entry for selects) that expands
into the type-in field via the shared accordion motion. Opening it on a
single-choice question deselects the fixed options; picking a fixed
option collapses a still-empty field; checkbox custom entries coexist.
Prompts (slim charter + discovery mirrors) now ban model-authored
catch-all "Other …" options — they would duplicate the host's — and the
od-default router's taskType question pins allowCustom: false in all
three copies since its own "Other" option IS the route.
2. Hard cap: 5 questions per form. The old "keep ≤7" guidance let the
model pad forms to the example's length. The rule is now an explicit
count-then-cut step ("count your draft; delete the least build-critical
until 5 remain"), and the discovery example itself was trimmed from 7
to 5 questions so the anchor matches the cap.
3. Copy + language. Form copy anchors lead with the benefit ("Prefilled
for you — send as is, or tweak anything first"); the localization rule
demands native phrasing with a concrete wrong-vs-right anchor (快速确认,
not the literal 快速简报); and forms now declare a top-level "lang"
BCP-47 tag that the web card uses to render its own strings (Other
chip, custom-answer copy, submit CTA) in the form's language instead of
the UI locale — no more English "Other" chips inside a Chinese form.
New i18n key qf.otherOption ships in all 19 locales.
Charter byte budget 15_872 → 16_896 across the two documented bumps, with
frozen-marker tests pinning the escape-hatch delegation, the hard cap, the
native-localization rule, and the lang anchor.
* fix(web): never surface a mid-stream question-form default
The streaming shaper reveals a question the moment its label parses, and
runs every question object — including the trailing, still-open one —
through mapRawQuestion. That let a MID-STREAM `default` reach the card:
the partial-JSON repair terminates it early, so "单位教育" surfaces as
"单位教" and ["历史背景与经过", "抗战精神与当代意义"] as ["历史背景与经过",
"抗战精"]. The card adopts a streamed default only while the answer is
still empty, so a truncated adoption freezes garbage that the completed
value can never overwrite — checkbox defaults (long arrays, near-certain
to be cut across delta chunks) effectively never prefilled, while short
radio scalars usually survived inside one chunk. Observed in production
run beaf2da0 (抗日主题汇报PPT): both maxSelections checkboxes rendered
with nothing selected despite the model emitting complete defaults.
Fix at the source: the trailing in-flight question exposes NO
defaultValue until its braces close. A closed object's braces are
balanced, so its default is complete; the card's empty-and-untouched
adoption rule then fills it exactly once, correctly.
Red spec reproduces the production body cut mid-default (went red on the
pre-fix tree) and pins that a closed question's default surfaces while
the next in-flight question's stays hidden.
* fix(prompts): align task-type form contract across all three copies; fit the slot menu inside the 5-question cap
Review follow-ups from PR #5573 (nettee):
- The od-default skill's task-type form now carries the same top-level
"lang" tag as the daemon/contracts prompt copies, so host-rendered
controls on that path key off the form language instead of falling back
to the app locale. A new parity check reads all three copies and locks
both contract keys ("lang" + taskType allowCustom: false) so the skill
copy cannot drift again.
- The slim charter's default-shape recipe contradicted the new hard cap:
"Between output and brand, in this order: platform, audience, tone.
After brand: scale, then constraints" prescribed 7 questions in the
same breath as "Hard cap: 5". The recipe is now 2 fixed slots plus a
pick-AT-MOST-3 menu (same option vocabulary, same order), and the
frozen-marker test asserts the menu wording is present while the old
prescriptive sequence is gone — the charter can no longer carry both.
---------
Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>
* feat(prompts): pin photo-overlay placement discipline in the slim charter
Real-imagery production value (the strongest-texture-lever rule) kept
shipping badges and caption cards that straddle the hero image's edge or
sit on the photo's focal subject — 2026-07-14 beta feedback on a
campus-open-day landing hero showed a session-type chip half-off the
photo, covering the graduates.
New Craft bullet: an overlay on a photo pins to ONE corner with a
consistent inset, sits fully inside the image bounds, stays clear of
faces/subjects, and reads on a legible surface with separating shadow;
with no safe corner it moves beside the image. Byte budget
16_896 -> 17_408 per the in-file bump trail; markers frozen.
* chore: retrigger ci
UI P0 (entry-settings) failed on 'Use with query carries the hydrated
starter prompt' — orthogonal to this PR's prompt-text diff, green on the
base branch's identical run, and passes locally on this branch (35s).
Empty commit to re-run the shard.
* chore: drop local-build churn in next-env.d.ts
A local 'next build' rewrote the generated routes.d.ts reference path and
the merge commit swept it in via add -A; restore the mainline version —
this PR's intent touches prompts/tests only.
---------
Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>
Co-authored-by: open-design-crew[bot] <299007234+open-design-crew[bot]@users.noreply.github.com>
* fix: address review findings — repeat-directive false positives, directions flag validation, router-form cap exemption
1. live-artifacts/render.ts: a data-od-repeat match now counts as a
directive only inside an element's open tag; literal mentions in prose
or comments render as inert content per html_template_v1 (red spec in
tests/live-artifacts/render.test.ts).
2. cli.ts tools directions: --id/--label now fail fast on a missing
value, a flag posing as the value, duplicates, or both flags at once
(red spec in tests/directions-cli.test.ts) — agents call this straight
from the prompt contract.
3. prompts/discovery.ts (+contracts mirror): the 5-question hard cap now
names the verbatim six-field task-type router form as its one
sanctioned exception, resolving the contradiction between two locked
instructions; the cap itself is unchanged for authored/tailored forms.
Marker frozen in discovery-form.test.ts; matrix snapshot regenerated.
* fix: second-round review — nested literal repeat false positive, keyboard-accessible Other chip
1. live-artifacts/render.ts: the nested-repeat guard now uses the same
inside-open-tag detection as the top-level scan (extracted
findRepeatDirective helper), so a literal data-od-repeat mention inside
a repeated element's body renders inert instead of throwing; a REAL
nested directive still throws. Red spec: reviewer's exact repro added,
red on the previous head.
2. web QuestionForm Other chip: replaced the display:none checkbox-in-label
with a real <button aria-pressed aria-expanded> plus focus-visible ring
and UA-chrome reset, restoring Tab reachability for keyboard and
screen-reader users. Existing chip tests migrated to the button role;
new focus/toggle spec added.
* chore: retrigger ci
Web workspace tests failed on HomeView.community-filter-decouple
(aria-selected timing) — a main-side test untouched by this PR; the full
web suite passes locally on this exact head (4351/4351). Empty commit to
re-run the shard.
* fix: third-round review — media runs skip the slim design charter; token-aware repeat-directive scan
1. prompts/system.ts: resolve the media surface (image/video/audio via
skillMode or metadata.kind) BEFORE building the prompt head, and gate
the slim design charter on it — the media-generation contract is again
the sole workflow authority on media runs, matching classic. The
mid-conversation clarifying-questions block now keys off the charter's
actual presence so media runs keep classic's clarify guidance. New spec
proves image/video/audio slim prompts contain no discovery-form or
HTML-handoff rules while design runs keep the charter.
2. live-artifacts/render.ts: findRepeatDirective is now quote- and
comment-aware — a directive-looking string inside another attribute's
quoted value or inside an HTML comment is authored text. Reviewer
repros added as regressions (attribute-quoted literal, tag-like
comment text); nested detection shares the same scanner.
Red-checked: 3 new specs fail with the two source files reverted.
* fix: fourth-round review — slim media head drops the Ask charter; comment-aware element-boundary scan
1. prompts/system.ts: the non-charter slim fallback head applied
CHAT_MODE_OVERRIDE to every slim run that was not the design charter —
including media runs, whose Ask charter ('do not create images/video/
audio') contradicted the media contract. The head now branches
explicitly: ask -> Ask charter, slim media (non-ask) -> classic skeleton
(plain-stream API override + injection resistance) with the media
contract as sole authority. Media spec extended to assert '# Ask mode'
is absent.
2. live-artifacts/render.ts: extracted insideComment() and applied it to
findElementEnd(), so same-tag tokens inside comments no longer move the
depth counter (<!-- <div> --> threw 'unbalanced'; <!-- </div> --> closed
the repeat early). findRepeatDirective shares the helper. Reviewer
repros frozen as regressions.
Red-checked: both new specs fail with the two source files reverted.
---------
Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>
Co-authored-by: Mason <jinmeihong0201@gmail.com>
Co-authored-by: open-design-crew[bot] <299007234+open-design-crew[bot]@users.noreply.github.com>