# Design System (Phase 1b) Compose `design.html` + split it into `chunks/`. This is a two-step deterministic script flow (site DNA reads Phase 1 `capture/` directly). The agent has two decision points: **preset selection** (required, §3) and **brand color adjudication** (only when `inference.json.brand.needs_review=true`, §3b — inspect the screenshot and choose from the candidates). ## 1. Command Template ```bash mkdir -p design-system # Step 1 - use the already generated inference.json # (Phase 1 capture already deterministically ran --no-emit). # Normally the orchestrator has inlined the inference.json body into # the dispatch `## Inference decision inputs`, so you do not need to # Read it or rerun the command below. Only Read/rerun when dispatch did # not inline it, inference.json is missing, or capability auto-install # requires candidate revalidation. # build-design.mjs defaults to reading /../capture/, # aligned with Phase 1 hyperframes capture output. node /phases/design-system/scripts/build-design.mjs ./design-system --no-emit # Step 2 - preset decision (§3) # Step 2b - brand color adjudication (§3b): only when inference.json.brand.needs_review=true # Step 3 - write design.html with the chosen preset # (if adjudicated, add --brand-primary ) node /phases/design-system/scripts/build-design.mjs ./design-system --style [--brand-primary ] # Step 4 - split into chunks/ node /phases/design-system/scripts/emit-chunks.mjs ./design-system ``` > **Captions (automatic, no judgment needed):** if the selected preset has `style-presets//caption-skin.html`, `build-design` embeds it into design.html as **§C live preview** (looping), and `emit-chunks` copies it to `chunks/caption-skin.html` (also recording it in `index.json.caption_skin_file`). Step 5.5 `captions.mjs html` then prefers it; otherwise it falls back to registry skins. The agent makes no decision here. Optional flags: - `build-design --capture ` - override the default `/../capture/` path - `build-design --out-scores ` - change where `inference.json` is written (default `/inference.json`) - `build-design --brand-primary ` - override the automatically inferred brand primary color (used after the agent adjudicates from screenshot; see §3b). The hex must come from `inference.json.brand.candidates[]` ## 2. `inference.json` Fields (agent reads these) ```jsonc { "confidence": "high" | "medium" | "low" | "forced", "brand": { // automatically inferred brand color (for §3b adjudication) "primary": "#XXXXXX", "secondary": "#XXXXXX", "accent": "#XXXXXX", "source": "signals" | "agent-override" | "legacy", "confidence": "high" | "medium" | "low" | "agent-override" | "legacy", "needs_review": true | false, // true = automatic choice is uncertain; agent must inspect screenshot "screenshot": "capture/screenshots/scroll-0.png", // relative to PROJECT_DIR "candidates": [ // descending by score; --brand-primary must choose from here { "hex": "#XXXXXX", "score": 0.X, "bgCount": N, "interactiveBg": N, "on_button": true } ] }, "baseline_winner": { "name": "...", "combined": 0.XX }, "top_candidates": [ // capability-satisfied candidates, descending by combined { "name": "...", "combined": 0.XX, "delta_from_winner": 0.XX, "matched_signals": [...], "best_for": [...], "avoid_for": [...], "sectionA_excerpt": "..." } ], "site_dna": { "material": "flat|paper|glass|...", "imagery": "photography|flat-illustration|...", "page_intent": "landing|pricing|blog|...", "section_role_counts": { "feature-grid": N, ... }, "voice_tone": "neutral|warm|formal|...", "voice_heading_style": "Title Case|UPPERCASE|...", "voice_heading_length": "tight|loose" }, "capability_gated_presets": [ // scored 0 because runtime/env capability is missing; can return to top_candidates after capability is installed { "name": "liquid-glass", "combined_pre_capability": 0.42, "capabilities_missing": [ { "kind": "block_installed", "auto_install": "npx hyperframes add liquid-glass-widgets" // agent may run when non-null }, { "kind": "env_var_set", "var": "PRODUCER_HEADLESS_SHELL_PATH", "auto_install": null // null = cannot be auto-installed } ] } ] } ``` ## 3. Decision Rules (Step 2) Decide by `confidence`: | confidence | Action | | ---------------- | -------------------------------------- | | `high` | Run Step 3 with `baseline_winner.name` | | `medium` / `low` | Enter the override criteria below | | `forced` | `--style` was passed; skip review | **Override criteria** (in priority order): 1. **Avoid pitfalls:** if any preset in `top_candidates` has `avoid_for` matching `site_dna` keywords, remove it from consideration. 2. **best_for match:** from the remaining candidates, choose the preset whose `best_for` overlaps the most with `site_dna` keywords. 3. **capability_gated preference:** if a gated preset's `combined_pre_capability` would place it in the top 3 and `site_dna` clearly fits it (typical `material: "glass"` -> liquid-glass), handle `capabilities_missing[]`: - `auto_install` non-null -> run that command -> rerun `build-design.mjs --no-emit` to verify it entered `top_candidates` -> select it with `--style` - `auto_install: null` -> include in anomaly report and **choose a different preset** 4. **No clear winner** -> keep `baseline_winner` **Forbidden:** the choice must come from `top_candidates[]` or `capability_gated_presets[]`; do not invent a new name. ## 3b. Brand Color Adjudication (Step 2b - only when `brand.needs_review=true`) `brand.primary` defaults to a deterministic signal-scored choice (the color used for interactive / repeated fills), and works directly for most sites. However, for **multicolor brands** and sites where the **brand color is hidden inside a large color block/logo**, CSS statistics cannot distinguish "brand hero" from "section ground". In those cases `needs_review=true`, and you **must inspect the screenshot to adjudicate**: 1. Use Read to open `brand.screenshot` (first viewport screenshot). 2. Compare against `brand.candidates[]`: determine which candidate hex is the **true brand color**, excluding these false positives: - background color of the top announcement bar / banner (thin horizontal strip = ground color, not brand) - background color of a large section panel / card container (large color block = ground color) - pale section background colors (light wash = ground color) - brand color usually appears in: **logo, primary CTA button, emphasis elements** (`on_button:true` is a strong signal) 3. After choosing -> rerun Step 3 with `--brand-primary ` (hex **must** be one of the `candidates[].hex` values). 4. If the true brand color visible in the screenshot is **not in** `candidates[]` (typical: brand color only appears in logo SVG, e.g. reddit/gitlab orange) -> no valid candidate can be chosen -> record this in anomaly report, keep the automatically selected primary, and do not force an override. When `needs_review=false` (`confidence=high`), **skip this step** and use `brand.primary` directly. ## 4. Report Template ``` preset review: baseline: (combined=, confidence=<...>) chosen: reason: alternates_considered: [, ] capability_actions: [] # write none if no action brand review: primary: (source=, confidence=<...>) reviewed: ``` Also paste the two stdout sections from build-design and emit-chunks verbatim. ## 5. Hard Contracts - **`chunks/` and `design.html` must share the same source** - after rerunning Step 3, rerun Step 4, otherwise downstream reads stale chunks. - **Read scope:** `./design-system/inference.json` + `./design-system/design.html` when verifying the composed result + **`brand.screenshot` (screenshot) when adjudicating brand color**. **Do not read raw JSON under capture extracted/** - `inference.json` already summarizes the key signals as `site_dna`. - **Do not design palette / typography / fonts / decoration yourself** - build-design writes all of it. Brand color adjudication is only **choosing from `candidates[]`** (`--brand-primary`), not inventing a hex. - `--style` is a deliberate override; the agent must satisfy capability before running it. In forced mode, build-design no longer gates. ## 6. Troubleshooting | Symptom | Fix | | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Captured non-English hero | Rerun Phase 1 capture | | Captured blank hero / placeholder text | Rerun capture with `--timeout 60000`; or check whether capture/BLOCKED.md reports anti-bot protection | | Step 4 reports missing anchors | Rerun Step 3 and confirm design.html contains ROOT-START / MOTION-START / VOICE-START / COMPONENT comments; do not fix emit-chunks | | `chunks/index.json` missing `components[]` | Check build-design stdout `components: N paste-ready`; N=0 is acceptable (downstream degrades to tokens + easings) | | Inferred brand primary is wrong | build-design scores brand color from capture `colorStats` signals (the color most often used for interactive/repeated fills = primary; see script comments). Usually this is accurate. If still wrong: 1. brand color appears only in logo SVG -> capture cannot catch it as a fill color yet; 2. multicolor brands have ambiguous primary. In both cases, you may force the preset with `--style`; brand color falls back through design.html handling |