@bongos/core 1.19.459 → 1.19.461

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/.bongos-core.json +65 -35
  2. package/.claude/skills/design/SKILL.md +10 -3
  3. package/docs/adr/0220-an-in-house-rebuilt-skill-is-a-first-party-skill.md +46 -0
  4. package/docs/adr/README.md +1 -0
  5. package/docs/copy-inventory.md +52 -61
  6. package/docs/copy-registry.json +92 -203
  7. package/docs/file-map.md +1 -1
  8. package/docs/module-api-changelog.md +4 -0
  9. package/modules/hall-ui/public/collab-lib.js +40 -4
  10. package/modules/hall-ui/public/collab.css +18 -34
  11. package/modules/hall-ui/public/collab.html +17 -17
  12. package/modules/hall-ui/public/collab.js +75 -39
  13. package/modules/hall-ui/public/collab.states.json +2 -2
  14. package/modules/hall-ui/public/copy-desk.css +64 -109
  15. package/modules/hall-ui/public/copy-desk.html +76 -69
  16. package/modules/hall-ui/public/copy-desk.js +175 -80
  17. package/modules/hall-ui/public/copy-desk.states.json +59 -2
  18. package/modules/hall-ui/public/oversight.css +11 -2
  19. package/modules/lifecycle/help-requests.js +18 -8
  20. package/modules/ui-design/module.json +1 -1
  21. package/modules/ui-design/skills/PREAMBLE.md +3 -3
  22. package/modules/ui-design/skills/README.md +13 -0
  23. package/modules/ui-design/skills/design-taste-frontend/SKILL.md +136 -0
  24. package/modules/ui-design/skills/design-taste-frontend-v1/SKILL.md +125 -0
  25. package/modules/ui-design/skills/high-end-visual-design/SKILL.md +128 -0
  26. package/modules/ui-design/skills/redesign-existing-projects/SKILL.md +120 -0
  27. package/package-lock.json +2 -2
  28. package/package.json +1 -1
  29. package/scripts/gds/docs-entropy.js +5 -1
  30. package/src/module-api.js +1 -1
  31. package/tests/collab_page.mjs +13 -7
  32. package/tests/docs_entropy.mjs +29 -1
  33. package/tests/hall_collab_world.mjs +214 -0
  34. package/tests/ui_design_skills.mjs +99 -9
@@ -1,12 +1,12 @@
1
1
  {
2
- "_": "Copy desk — the one-voice review queue. Its confirm dialog is a real <dialog>, so its ::backdrop is the sheet's single sanctioned literal (black with alpha as a scrim). Written with the re-skin (task 1003307) so this page is MEASURED at three widths in both modes rather than looked at once.",
2
+ "_": "Copy desk — the one-voice review queue. Its two dialogs are real <dialog>s, so their ::backdrop is the sheet's single sanctioned literal (black with alpha as a scrim). Written with the re-skin (task 1003307) so this page is MEASURED at three widths in both modes rather than looked at once; the redesign (task 1003471) put a queue, ten duplicates and a search's hits in the harness so the rows and both dialogs can be looked at.",
3
3
  "page": "/builders/copy-desk",
4
4
  "surface": "hall-ui",
5
5
  "stub": {
6
6
  "prefix": "/builders/"
7
7
  },
8
8
  "modeQuery": false,
9
- "_feeds": "The kit's own stub carries no feed for this page, so against it the page renders its EMPTY and SKELETON states — which is the point: those are states the floors must hold in. Against the hall-preview harness the same states render populated. Every declared 404 below is a fixture the harness does not carry, named rather than hidden.",
9
+ "_feeds": "The kit's own stub carries no feed for this page, so against it the page renders its EMPTY state — which is the point: that is a state the floors must hold in. Against the hall-preview harness the same states render populated: copy-desk__queue.json (six flags across open, resolved and dismissed, with a gone target, a moved one and a whole-surface one; ten real duplicates) and copy-desk__strings.json (a search's hits). Every declared 404 below is a feed the kit's stub does not carry, named rather than hidden. The composer and closer states use clickIfVisible because against the stub the buttons do not exist.",
10
10
  "ignoreRequests": [
11
11
  "/api/(gds|bongos)/me$",
12
12
  "/api/(gds|bongos)/me/",
@@ -45,6 +45,63 @@
45
45
  },
46
46
  "out": {
47
47
  "auth": false
48
+ },
49
+ "search": {
50
+ "auth": true,
51
+ "actions": [
52
+ [
53
+ "wait",
54
+ 700
55
+ ],
56
+ [
57
+ "fill",
58
+ "#cd-search",
59
+ "save"
60
+ ],
61
+ [
62
+ "wait",
63
+ 600
64
+ ]
65
+ ],
66
+ "expect": {
67
+ "visible": [
68
+ "#cd-results"
69
+ ]
70
+ }
71
+ },
72
+ "composer": {
73
+ "auth": true,
74
+ "actions": [
75
+ [
76
+ "wait",
77
+ 700
78
+ ],
79
+ [
80
+ "clickIfVisible",
81
+ ".cd-flag-btn"
82
+ ],
83
+ [
84
+ "wait",
85
+ 250
86
+ ]
87
+ ]
88
+ },
89
+ "closer": {
90
+ "auth": true,
91
+ "actions": [
92
+ [
93
+ "wait",
94
+ 700
95
+ ],
96
+ [
97
+ "clickIfVisible",
98
+ ".cd-close-btn"
99
+ ],
100
+ [
101
+ "wait",
102
+ 250
103
+ ]
104
+ ]
48
105
  }
49
106
  },
50
107
  "reducedMotion": {
@@ -1,9 +1,12 @@
1
1
  /* oversight.css — the oversight family's archetype (task 1003469, goal
2
- 1000081): what a oversight ROW is in the chrome world, and the pieces the
2
+ 1000081): what an oversight ROW is in the chrome world, and the pieces the
3
3
  four pages (government, watch, harbor, gate) share around it — the figure
4
4
  band inside a panel, the group head, the inline form, the grant toggle, the
5
5
  confirm modal. The third family sheet after record.css and room.css: a page
6
- sheet holds only what one page has and the others do not.
6
+ sheet holds only what one page has and the others do not. The collaboration
7
+ pages (collab, copy desk — task 1003471) ride the same row rather than a
8
+ fourth: a page whose repeated thing is a titled row with facts and actions
9
+ links this sheet before its own.
7
10
 
8
11
  Every panel is the shell's .scroll; every heading its .scroll__h; every state
9
12
  pill its .fact-pill; every table its .ledger. This sheet adds nothing the
@@ -150,6 +153,12 @@ button.ov-row[aria-pressed="true"] .ov-row__title { color: var(--accent-ink); }
150
153
  The shell's glass .stat-row, given the air a panel would have under it. */
151
154
  .ov-summary { margin: 0 0 var(--sp-md); }
152
155
 
156
+ /* A chip that is a door (the task or goal a row is about) keeps the chip's
157
+ faint ink at rest — a coloured chip would read as a second status — and
158
+ takes the link ink on hover, the way the row's title link does. */
159
+ a.fact-chip { color: var(--ink-faint); }
160
+ a.fact-chip:hover { color: var(--accent-ink); border-color: var(--accent-ink); text-decoration: none; }
161
+
153
162
  /* ---- plain facts and the machine's words ------------------------------------ */
154
163
  .ov-fact { font-size: 12.5px; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
155
164
  .ov-fact b { color: var(--ink-soft); font-weight: 500; }
@@ -49,6 +49,16 @@ async function craftsForBuilder(builderId, deps = {}) {
49
49
  }
50
50
 
51
51
  const COLS = 'id, requested_by, needs_craft, needs_builder_id, what_is_stuck, task_id, goal_id, status, created_at, updated_at';
52
+ // The two list reads also NAME the people on a row — who is asking (the fact
53
+ // the collab page owes a helper: "who to answer", task 1003471) and who a
54
+ // by-name ask is addressed to — through two LEFT JOINs on builders. COLS stays
55
+ // bare for the writes, whose RETURNING cannot reach a joined alias.
56
+ const LIST_COLS = `${COLS.split(', ').map((c) => `h.${c}`).join(', ')}, `
57
+ + 'a.github_login AS requested_by_login, a.display_name AS requested_by_name, '
58
+ + 'n.github_login AS needs_builder_login, n.display_name AS needs_builder_name';
59
+ const LIST_FROM = 'FROM lifecycle_help_requests h '
60
+ + 'LEFT JOIN builders a ON a.id = h.requested_by '
61
+ + 'LEFT JOIN builders n ON n.id = h.needs_builder_id';
52
62
  const MAX_STUCK = 4000;
53
63
  // Defence in depth: both reads are bounded today (open-status churn, per-builder
54
64
  // scope), but an unsettled pile should degrade the page, not the server.
@@ -130,11 +140,11 @@ async function listOpenForBuilder({ builderId, crafts = [], limit = MAX_ROWS },
130
140
  const activePool = deps.pool || pool;
131
141
  const safeCrafts = (Array.isArray(crafts) ? crafts : []).filter((c) => HELP_CRAFTS.includes(c));
132
142
  const { rows } = await activePool.query(
133
- `SELECT ${COLS} FROM lifecycle_help_requests
134
- WHERE status = 'open'
135
- AND requested_by <> $1
136
- AND (needs_builder_id = $1 OR needs_craft = ANY($2::text[]))
137
- ORDER BY created_at DESC, id DESC
143
+ `SELECT ${LIST_COLS} ${LIST_FROM}
144
+ WHERE h.status = 'open'
145
+ AND h.requested_by <> $1
146
+ AND (h.needs_builder_id = $1 OR h.needs_craft = ANY($2::text[]))
147
+ ORDER BY h.created_at DESC, h.id DESC
138
148
  LIMIT $3`,
139
149
  [builderId, safeCrafts, capRows(limit)]
140
150
  );
@@ -181,9 +191,9 @@ function craftMapSql(col) {
181
191
  async function listMine({ builderId, status = null, limit = MAX_ROWS }, deps = {}) {
182
192
  const activePool = deps.pool || pool;
183
193
  const { rows } = await activePool.query(
184
- `SELECT ${COLS} FROM lifecycle_help_requests
185
- WHERE requested_by = $1 AND ($2::text IS NULL OR status = $2)
186
- ORDER BY created_at DESC, id DESC
194
+ `SELECT ${LIST_COLS} ${LIST_FROM}
195
+ WHERE h.requested_by = $1 AND ($2::text IS NULL OR h.status = $2)
196
+ ORDER BY h.created_at DESC, h.id DESC
187
197
  LIMIT $3`,
188
198
  [builderId, status, capRows(limit)]
189
199
  );
@@ -5,7 +5,7 @@
5
5
  "coreVersion": "^1.19.0",
6
6
  "default": true,
7
7
  "maintenance": { "status": "core-maintained" },
8
- "contributes": { "skills": ["design", "design-sync", "figma-design-sync"] },
8
+ "contributes": { "skills": ["design", "design-sync", "figma-design-sync", "design-taste-frontend", "design-taste-frontend-v1", "high-end-visual-design", "redesign-existing-projects"] },
9
9
  "provides": [],
10
10
  "consumes": []
11
11
  }
@@ -1,5 +1,5 @@
1
- <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every vendored skill so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
2
- > **Platform preamble — read before any rule below.** This skill is vendored by the `ui-design` module and runs INSIDE an instance's world, which it reads first and never overrides.
1
+ <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
2
+ > **Platform preamble — read before any rule below.** This skill ships with the `ui-design` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
3
3
  >
4
4
  > 1. **Load the world before any taste rule.** `config/branding.json` over `config/branding.neutral.json` (`theme.ui` is the fifteen tokens: <redacted> colours and two font stacks), `config/design-tokens.json` over `config/design-tokens.neutral.json`, and `DESIGN.md` at the repo root: the instance's palette and derived tiers, faces, materials, motion grammar, components, and its Do's and Don'ts. Where an instance has no `DESIGN.md`, the neutral pack is the world and the platform floors in item 6 are the whole rulebook; say so and build to them. Never invent a palette.
5
5
  > 2. **The fifteen-token contract is the only colour source.** Every colour is a `var()` or a `color-mix()` of the fifteen; the only legal literal is pure black or white with alpha as a scrim, mask or halo. No page `:root` (page tokens live on `body`), and every dark rule is written twice: `:root[data-mode="dark"]` and its `prefers-color-scheme` twin.
@@ -9,5 +9,5 @@
9
9
  > 6. **The platform floors hold on every instance** and a `DESIGN.md` may tighten them, never loosen them: every control clears 24px on both sides (primary pills 46–48px); AA in both modes (4.5:1 body text, 3:1 large text and non-text) measured from the real stylesheet; and the Kill Switch, `@media (prefers-reduced-motion: reduce)` at the foot of the token layer killing every animation and transition, no motion authored in JavaScript, and a rest frame that is a complete composition on its own.
10
10
  > 7. **Look before you ship.** Render the real surface through the module's kit before and after (`/design` Step 2 and `docs/recipes/ui-look-before-you-ship.md`: every state at 1440 / 390 / 320 in dark and light, audited against the floors); a page with no states file gets one first, and new interactive code ships with a pin in the page's own test file.
11
11
  >
12
- > The rules below are the upstream skill's, unchanged. `PROVENANCE.md` beside this file names the origin, the pinned commit, the licence and the scan verdict it was vendored under.
12
+ > The rules below are the skill's own. A vendored copy carries a `PROVENANCE.md` beside this file naming the origin, the pinned commit, the licence and the scan verdict it was vendored under; an in-house rebuild names its rebuild spec and its ADR in the section right after this block.
13
13
  <!-- END PLATFORM PREAMBLE -->
@@ -25,6 +25,19 @@ Two origins were scanned for the set scoped with the owner (impeccable; the elev
25
25
 
26
26
  The eleven taste skills are single-file prose; the floor's one critical is a false positive in the human sense and a true positive in the scanner's — and the rule that makes the scanner worth having is that a session does not get to tell the difference. Vendoring any of them anyway is an **owner override recorded in an ADR**, not a judgement a task makes.
27
27
 
28
+ ## The in-house rebuilds (2026-09-01)
29
+
30
+ A rebuild spec becomes a **first-party skill** at `<name>/` — no `vendor/` segment, so the ratchets count it; no `PROVENANCE.md` or `LICENSE`, because an in-house skill claims no upstream; the same PLATFORM PREAMBLE first after the frontmatter, byte-identical; then a **`## Rebuilt from`** section naming the origin the *spec* was derived from (documentation only), the scan verdict, the spec task, the statement that no upstream bytes were consulted or copied, and what changed in translation ([ADR 0220](../../../docs/adr/<redacted>.md)). Declared in `module.json` like any other; `claude-materialize.js` step 1b lands it through its own-dir branch, which no sidecar gate touches. `tests/ui_design_skills.mjs` pins all of it.
31
+
32
+ | Skill | Rebuilt from (spec) | Task | What it is |
33
+ |---|---|---|---|
34
+ | `design-taste-frontend` | `Leonxlnx/taste-skill` `taste-skill` (v2) | [task 1003325](https://cloudbongos.com/builders#/task/1003325) | the default taste skill: the brief read in one line, three dials from presets, one world locked, hero and layout discipline, rationed decoration, a pre-flight the kit measures; declines dashboards, tables, wizards, editors |
35
+ | `design-taste-frontend-v1` | `Leonxlnx/taste-skill` `taste-skill-v1` | [task 1003325](https://cloudbongos.com/builders#/task/1003325) | the earlier generation, kept distinct: a fixed baseline triple adapted in conversation, stack conventions for a served surface, the layout bans, full interaction cycles, the pattern vocabulary, the tile-grid archetypes |
36
+ | `high-end-visual-design` | `Leonxlnx/taste-skill` `soft-skill` | [task 1003325](https://cloudbongos.com/builders#/task/1003325) | the agency-build polish pass: one texture archetype (the style library's looks) and one layout archetype per project, the nested container anatomy where the world allows a container, the pill with its nested arrow disc, rhythm, choreography and scroll entry inside the Kill Switch |
37
+ | `redesign-existing-projects` | `Leonxlnx/taste-skill` `redesign-skill` | [task 1003325](https://cloudbongos.com/builders#/task/1003325) | the in-place upgrade: scan the stack, diagnose against the seven-group audit with the instance's world as the answer key, fix in impact-per-risk order, one group per commit; keeps the stack, never migrates frameworks |
38
+
39
+ What every rebuild does with the upstream's opinions, in the same words: the palette is the pack and a variant is a look from [`../styles/`](../styles/README.md); the design system is the instance's `DESIGN.md`; images are hero plates and hero objects by token and never a step; every motion rule lives inside the Kill Switch (springs, magnetic hover, pointer-tracked borders are named *not available*); the upstream's bans are defaults an instance's `DESIGN.md` overrides; the kit's renders are the pre-flight. Still specs, not skills: the impeccable method (task 1003326), the image family (1003328), the detector (1003329), the four looks (1003327).
40
+
28
41
  **The playwright plugin** (`claude-plugins-official`, an MCP plugin) is the LIVE browser-driving loop, where the kit is the headless one. It is a prerequisite, documented — `claude plugin install playwright@claude-plugins-official` — never vendored.
29
42
 
30
43
  ## How to land one (when a scan says it may)
@@ -0,0 +1,136 @@
1
+ ---
2
+ name: design-taste-frontend
3
+ description: The default taste skill for a landing page, a portfolio, a marketing surface or a redesign of one, on ANY Cloud Bongos instance. Reads the brief and says the design read in one line, sets three dials (variance, motion, density) from named presets, locks one world for the whole page, keeps the hero and the layout disciplined, rations decoration, and ends on a pre-flight the kit measures. Triggers on "design a landing page", "make this not look templated", "taste pass", "anti-slop", "this looks like every AI site", or when /design hands off a marketing surface. Not for dashboards, data tables, wizards, code editors, native mobile or realtime collaboration (those go to /design and the hall's archetypes). Rebuilt in-house from the taste-skill rebuild spec (task 1003325, ADR 0198); the instance's world is the palette and images are hero plates only.
4
+ ---
5
+
6
+ <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
7
+ > **Platform preamble — read before any rule below.** This skill ships with the `ui-design` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
8
+ >
9
+ > 1. **Load the world before any taste rule.** `config/branding.json` over `config/branding.neutral.json` (`theme.ui` is the fifteen tokens: <redacted> colours and two font stacks), `config/design-tokens.json` over `config/design-tokens.neutral.json`, and `DESIGN.md` at the repo root: the instance's palette and derived tiers, faces, materials, motion grammar, components, and its Do's and Don'ts. Where an instance has no `DESIGN.md`, the neutral pack is the world and the platform floors in item 6 are the whole rulebook; say so and build to them. Never invent a palette.
10
+ > 2. **The fifteen-token contract is the only colour source.** Every colour is a `var()` or a `color-mix()` of the fifteen; the only legal literal is pure black or white with alpha as a scrim, mask or halo. No page `:root` (page tokens live on `body`), and every dark rule is written twice: `:root[data-mode="dark"]` and its `prefers-color-scheme` twin.
11
+ > 3. **A style skill produces variants INSIDE that world, never a second world.** The look this skill carries (its palette, faces, materials, mood boards) is a reference for composition and craft; the instance's tokens and faces replace it. Changing the world is the owner's decision and an ADR, not a session's taste.
12
+ > 4. **Images are for hero plates and hero objects only.** UI is built from templates and the instance's world, not painted; any image-generation step below applies to text-free plates and hero objects, never to controls, cards, text, or a screenshot of a screen.
13
+ > 5. **The instance's taste bans apply and win.** Read them from its `DESIGN.md`. Cloud Bongos's own (no em dash or en dash in visible copy, no emoji, no gradient text, no three-equal-card row, no fake screenshot built from divs) are that instance's, not the module's; where this skill's rules conflict with an instance's bans, the instance wins.
14
+ > 6. **The platform floors hold on every instance** and a `DESIGN.md` may tighten them, never loosen them: every control clears 24px on both sides (primary pills 46–48px); AA in both modes (4.5:1 body text, 3:1 large text and non-text) measured from the real stylesheet; and the Kill Switch, `@media (prefers-reduced-motion: reduce)` at the foot of the token layer killing every animation and transition, no motion authored in JavaScript, and a rest frame that is a complete composition on its own.
15
+ > 7. **Look before you ship.** Render the real surface through the module's kit before and after (`/design` Step 2 and `docs/recipes/ui-look-before-you-ship.md`: every state at 1440 / 390 / 320 in dark and light, audited against the floors); a page with no states file gets one first, and new interactive code ships with a pin in the page's own test file.
16
+ >
17
+ > The rules below are the skill's own. A vendored copy carries a `PROVENANCE.md` beside this file naming the origin, the pinned commit, the licence and the scan verdict it was vendored under; an in-house rebuild names its rebuild spec and its ADR in the section right after this block.
18
+ <!-- END PLATFORM PREAMBLE -->
19
+
20
+ ## Rebuilt from
21
+
22
+ This skill is an **in-house rebuild**, not a vendored copy. Its documented functionality comes from `Leonxlnx/taste-skill` @ `<redacted>` (MIT; the `taste-skill` entry, "design-taste-frontend v2"), which `/scan-before-install` reduced to `dangerous` at the default tier on 2026-08-28, so under [ADR 0198](../../../../docs/adr/<redacted>.md) it could land only as a REBUILD-SPEC. The spec is the body of [task 1003325](https://cloudbongos.com/builders#/task/1003325); this file implements its requirements from scratch, **no upstream bytes consulted or copied**, and the landing shape (the in-house `skills/<name>/` home, this section in place of a `PROVENANCE.md`) is [ADR 0220](../../../../docs/adr/<redacted>.md).
23
+
24
+ What changed in translation, on purpose: the upstream skill mandated image generation as a first step and accepted seeded placeholder photography; here **imagery is hero plates and hero objects only, by token, and never a step**. Its "foundation" step installed a vendor's design-system package; here **the foundation is the instance's world** (`DESIGN.md` and its pack), and the style library is where variants come from. Its motion rules assumed a script-driven animation stack; here **every rule lives inside the Kill Switch** and nothing moves in JavaScript. Its bans (the long dash, decorative labels) are kept as defaults that an instance's own `DESIGN.md` bans override.
25
+
26
+ ## Scope: when this skill fires, and when it declines
27
+
28
+ **Fires** on a landing page, a portfolio, a marketing site, a product story page, a pricing page, and on a redesign of any of those (for the audit-first version of a redesign, `redesign-existing-projects` runs first and hands back here).
29
+
30
+ **Declines, and says so instead of trying anyway:** dashboards and ledgers, data tables, multi-step wizards, editors, native mobile, realtime collaboration surfaces. Those have their own archetypes on this platform (the hall's row and panel archetypes, the hub's card) and the plain `/design` loop; a marketing taste applied to a working surface is how a ledger gets a hero.
31
+
32
+ ## Step 1: read the brief, then say the read in one line
33
+
34
+ Read, in this order, before any opinion: the page kind; the audience; the vibe words (verbatim, they are the brief's only taste signal); linked references (open them; extract composition, rhythm, density, never a palette); existing brand assets the instance already ships (its plates under `assets/world/`, its mark); the hard constraints (the world file is one: everything the instance's `DESIGN.md` names is a constraint, not a suggestion).
35
+
36
+ Then write **one line**: `Design read: <page kind> for <audience>, <the one word the vibe reduces to>, <variance/motion/density>.` That line is the contract for the session and goes in the ship notes.
37
+
38
+ Ask **at most one** clarifying question, and only when the read genuinely forks (two page kinds, two audiences, a vibe word that contradicts the world). A brief that does not fork gets the read and a first render, not a questionnaire.
39
+
40
+ ## Step 2: set the three dials from a preset, never silently
41
+
42
+ Three numbers, 0 to 10, stated in the read line and restated in a comment at the top of the page's sheet:
43
+
44
+ | Preset | Variance | Motion | Density | What it means |
45
+ |---|---|---|---|---|
46
+ | Landing / marketing | 6 | 4 | 3 | asymmetric composition, one authored arrival plus slow ambient loops, a lot of air |
47
+ | Portfolio | 7 | 3 | 2 | the work is the composition; the chrome recedes |
48
+ | Product story / pricing | 5 | 3 | 5 | more content per band; still one accent, still hairlines |
49
+ | Reading page (docs, terms) | 2 | 1 | 6 | a measure, a rhythm, almost no motion |
50
+ | Redesign in place | inherit | inherit | inherit | the existing page's numbers, read from the audit, then moved one step at most |
51
+
52
+ - **Variance** is how far the layout may leave the centred single column: at 0 everything is centred and stacked; from 5 the hero is asymmetric and sections may offset; at 8+ elements may overlap and break the container.
53
+ - **Motion** is capped by the world's grammar before it is capped by the dial: under the chrome world an ambient loop is 20s or slower and there is exactly one authored arrival; a dial above the world's ceiling is clamped to it. Motion 0 is what every page becomes under `prefers-reduced-motion`, so the rest frame at motion 0 is designed first.
54
+ - **Density** is content per band, not padding: it changes how many facts a band carries, never whether the 24px floor holds.
55
+
56
+ Never run a baseline silently. If the brief gives no signal, pick the preset that matches the page kind, say so in the read line, and move on.
57
+
58
+ ## Step 3: the foundation is the world
59
+
60
+ The design system for every surface on an instance is that instance's `DESIGN.md` plus its branding pack. Map the brief onto it:
61
+
62
+ - **The palette** is the fifteen. A variant of the look comes from the style library, `modules/ui-design/styles/<name>/` (render the surface under one with `--style <name>` on the kit to see it); the palette source for a variant is a look's `pack.json`, never a hex chosen for this page. If the brief's vibe wants a colour the world does not have, the answer is a look from the library or an owner decision, not a private token.
63
+ - **The faces** are the pack's two stacks. A brief that names a typeface it wants gets the nearest face the pack already carries, and the ship notes say so.
64
+ - **When the brief names a third-party design system** ("make it feel like <vendor>"), do not install anything: this skill adds no dependency, and a dependency is a task decision the claim's owner takes. Build the direction from web standards inside the world, and label the approximation in a comment in the sheet (`/* an approximation of <vendor>'s rhythm inside the chrome world; not official material */`), never as official.
65
+ - **Where the instance has no `DESIGN.md`**, the neutral pack is the world and the platform floors are the rulebook. Say so in the read line.
66
+
67
+ ## Step 4: lock consistency for the whole page
68
+
69
+ One world, one accent, one radius scale (the world's `rounded` tier: pill, panel, tile), one mode pair (the pack's light and the sheet's dark twin), one label face. Locked at the top of the sheet in a comment, then held:
70
+
71
+ - No mid-page inversion. A dark band on a light page, or a light card on a dark page, is the copy-paste accident the redesign audit flags; the chrome world bans a black panel on a light page by name.
72
+ - No accent swap. The accent is the pack's `accent`; a second voice takes `sea` before anything is invented; the hall's fixed verification vocabulary (green, amber, red) is not a palette this skill may borrow.
73
+ - No radius drift. Every rounded thing on the page is one of the three tokens.
74
+ - No face drift. Display, body, label: three roles, two stacks plus the world's label face.
75
+
76
+ ## Step 5: hero discipline
77
+
78
+ The hero is where a page becomes generic. Hold these:
79
+
80
+ - **The headline is at most two lines** at 1440 and at 390 (the chrome world's Two-Line Rule). If it wraps to three, the copy is too long or the container is too narrow; widen the container first, then shorten the copy with `copy-desk` (the copy half is theirs; flag, do not rewrite the voice).
81
+ - **The subtext is capped**: two lines, about twenty-four words.
82
+ - **The primary action is visible without scrolling** at 1440x900 and at 390x844. The kit's first shot of every render is that test.
83
+ - **Top padding is capped** at the world's band clamp; a hero that starts below the fold is not a hero.
84
+ - **At most four text elements** in the hero: eyebrow if the world allows one (the chrome world does not, above the h1), headline, subtext, the action pair. Nothing else. A stat, a badge, a pill row and a scroll cue are all the fifth element.
85
+ - **Trust logos leave the hero** and get their own band below it, and only where the instance's `DESIGN.md` does not ban a trust strip outright (the chrome world does).
86
+
87
+ ## Step 6: layout discipline
88
+
89
+ - **No layout family reused across adjacent sections.** Two-column split, full-bleed band, hairline ledger, tile grid, single measure: each appears once before any repeats, and never twice in a row.
90
+ - **At most two consecutive image-plus-text zigzags.** A third is a template.
91
+ - **A bento grid has exactly as many cells as it has items.** No empty cell, no filler card, no "coming soon" tile. Count them in the sheet comment.
92
+ - **One-line desktop navigation under a height cap**: the world's bar height (64 to 72px in the chrome world; 60 under its breakpoint) and never a second row on desktop.
93
+ - **The container is the world's** (`spacing.container`); the breakpoint is the world's (one, at 900px in the chrome world); the widths the kit renders are the truth (1440 / 390 / 320).
94
+
95
+ ## Step 7: ration the decoration
96
+
97
+ Small uppercase labels are seasoning. **One eyebrow per three sections**, at most, in the world's label face, and never above the h1 where the world bans it.
98
+
99
+ Banned outright, as defaults an instance's `DESIGN.md` may only tighten: enumerated section labels ("01 / Features"), scroll cues, locale and time strips, version stamps, decorative status dots, floating badges over hero text, pill tags under the hero, raw statistics inside the hero. The chrome world bans most of these by name; where a world is silent, this list holds.
100
+
101
+ ## Step 8: copy hygiene inside the taste
102
+
103
+ - **The long dash and its separator variant do not appear in visible copy** where the instance bans them (Cloud Bongos does): not in headlines, captions, buttons, quotes or alt text. The plain hyphen and the minus sign survive. The ledger's quoted titles are normalised at render time rather than edited.
104
+ - No emoji, no exclamation mark in success copy, no placeholder names, no fake round numbers, no invented brands. Fixtures come from `modules/ui-design/kit/fixtures/`, which is a neutral builder and real feed shapes.
105
+ - The voice belongs to `copy-desk`. Flag a string in the ship notes; do not rewrite the product's voice in passing.
106
+
107
+ ## Step 9: imagery, under the owner's rule
108
+
109
+ Imagery is optional and, where it exists, it is **hero plates and hero objects by token**: the world's rendered plates (`--plate-*` in the chrome world), an owner-supplied `art_url`, a project's own cover. This skill never generates an image as a step, never proposes placeholder photography, and never asks for an image budget.
110
+
111
+ - A slot with no plate is not a slot: the composition is redesigned without it. An empty labelled slot ("image here") is the generic tell this skill exists to remove.
112
+ - **No product screenshot faked out of styled divs** (the chrome world's ban, and the module's default). A real screenshot is content an owner supplies; a fake one is decoration.
113
+ - No hand-drawn icon paths, no icon set introduced for one page. Where an icon is needed, the world's inline mark or a hairline glyph.
114
+ - A plate is the one place the world allows atmosphere; nothing else is painted.
115
+
116
+ ## Step 10: motion honesty
117
+
118
+ Every animation on the page is **justifiable in one sentence, written as a CSS comment above it**. If the sentence is "it looks nice", the animation goes.
119
+
120
+ - Everything moves in CSS, on `transform` and `opacity` (and `filter` on plates where the world does), on the world's curves: an exponential ease-out for entrances, the state curve at 200 to 400ms for hover and press. No spring library, no scroll listener, no component state for a continuous value: motion authored in JavaScript escapes the Kill Switch and is banned on every instance.
121
+ - The two scroll skeletons, where a dial above 5 asks for them, are CSS: a **pinned stack** is `position: sticky` panels in a tall band, and a **horizontal pan** is a sticky band with `animation-timeline: scroll()` behind `@supports`, falling back to a static row. Both are complete compositions at rest.
122
+ - Above motion 2 the page honours `prefers-reduced-motion` by design, not by exception: the Kill Switch at the foot of the token layer kills every animation and transition, and the rest frame you designed at Step 2 is what remains.
123
+ - Ambient loops respect the world's period floor (20s in the chrome world) and move only the plates.
124
+
125
+ ## Pre-flight (a single unticked box means the work is not done)
126
+
127
+ Run the kit, not your eyes: `node modules/ui-design/kit/render.js --page <path>` (every state, 1440 / 390 / 320, the modes the states file names, audited against the floors, a reduced-motion pass) and `node modules/ui-design/kit/probe.js --page <path>` for the interaction contract. Then tick:
128
+
129
+ - [ ] **Contrast** measured, not eyeballed: ALL CLEAN in every mode the page serves; the pack floor via `contrast.js`.
130
+ - [ ] **Copy self-audit**: no long dash in visible copy where banned, no emoji, no placeholder names or fake numbers, two-line headline at 1440 and 390.
131
+ - [ ] **Theme and shape locks** held: one accent, one radius scale, one mode pair, no inverted band, no private hex, no page `:root`.
132
+ - [ ] **Imagery sourced by token**: every image is a plate token, an owner asset or absent; no faked screenshot, no labelled empty slot.
133
+ - [ ] **Motion honesty**: every animation has its one-sentence comment; nothing moves in JavaScript; the reduced-motion render is a complete composition.
134
+ - [ ] **Mobile collapse** looked at: the 390 and 320 shots first (the fold, the bar, the bands); nothing scrolls sideways; the 24px floor holds on every control.
135
+ - [ ] **Web vitals by construction**: the LCP element is a plate or the headline, never a script-built node; fonts load with `font-display: swap` and a sized fallback so nothing shifts; live DOM is `createElement` and `textContent`; no layout property animates.
136
+ - [ ] **The read line** from Step 1 and the dials from Step 2 are in the ship notes with the shots you looked at, in which modes.
@@ -0,0 +1,125 @@
1
+ ---
2
+ name: design-taste-frontend-v1
3
+ description: The earlier generation of the taste skill, preserved under its own name so a task or a builder that names it gets exactly this behaviour while the default (design-taste-frontend) evolves separately. A fixed baseline triple of dials adapted in conversation, stack conventions for a served surface, typography and colour rules read against the instance's pack, the layout bans, full interaction cycles, motion rules inside the Kill Switch, performance guardrails, a named vocabulary of premium patterns and a tile-grid archetype set. Triggers on "design-taste-frontend-v1", "use the v1 taste skill", "the fixed dials", or "the pattern vocabulary". Rebuilt in-house from the taste-skill rebuild spec (task 1003325, ADR 0198); nothing here auto-updates from v2.
4
+ ---
5
+
6
+ <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
7
+ > **Platform preamble — read before any rule below.** This skill ships with the `ui-design` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
8
+ >
9
+ > 1. **Load the world before any taste rule.** `config/branding.json` over `config/branding.neutral.json` (`theme.ui` is the fifteen tokens: <redacted> colours and two font stacks), `config/design-tokens.json` over `config/design-tokens.neutral.json`, and `DESIGN.md` at the repo root: the instance's palette and derived tiers, faces, materials, motion grammar, components, and its Do's and Don'ts. Where an instance has no `DESIGN.md`, the neutral pack is the world and the platform floors in item 6 are the whole rulebook; say so and build to them. Never invent a palette.
10
+ > 2. **The fifteen-token contract is the only colour source.** Every colour is a `var()` or a `color-mix()` of the fifteen; the only legal literal is pure black or white with alpha as a scrim, mask or halo. No page `:root` (page tokens live on `body`), and every dark rule is written twice: `:root[data-mode="dark"]` and its `prefers-color-scheme` twin.
11
+ > 3. **A style skill produces variants INSIDE that world, never a second world.** The look this skill carries (its palette, faces, materials, mood boards) is a reference for composition and craft; the instance's tokens and faces replace it. Changing the world is the owner's decision and an ADR, not a session's taste.
12
+ > 4. **Images are for hero plates and hero objects only.** UI is built from templates and the instance's world, not painted; any image-generation step below applies to text-free plates and hero objects, never to controls, cards, text, or a screenshot of a screen.
13
+ > 5. **The instance's taste bans apply and win.** Read them from its `DESIGN.md`. Cloud Bongos's own (no em dash or en dash in visible copy, no emoji, no gradient text, no three-equal-card row, no fake screenshot built from divs) are that instance's, not the module's; where this skill's rules conflict with an instance's bans, the instance wins.
14
+ > 6. **The platform floors hold on every instance** and a `DESIGN.md` may tighten them, never loosen them: every control clears 24px on both sides (primary pills 46–48px); AA in both modes (4.5:1 body text, 3:1 large text and non-text) measured from the real stylesheet; and the Kill Switch, `@media (prefers-reduced-motion: reduce)` at the foot of the token layer killing every animation and transition, no motion authored in JavaScript, and a rest frame that is a complete composition on its own.
15
+ > 7. **Look before you ship.** Render the real surface through the module's kit before and after (`/design` Step 2 and `docs/recipes/ui-look-before-you-ship.md`: every state at 1440 / 390 / 320 in dark and light, audited against the floors); a page with no states file gets one first, and new interactive code ships with a pin in the page's own test file.
16
+ >
17
+ > The rules below are the skill's own. A vendored copy carries a `PROVENANCE.md` beside this file naming the origin, the pinned commit, the licence and the scan verdict it was vendored under; an in-house rebuild names its rebuild spec and its ADR in the section right after this block.
18
+ <!-- END PLATFORM PREAMBLE -->
19
+
20
+ ## Rebuilt from
21
+
22
+ This skill is an **in-house rebuild**, not a vendored copy. Its documented functionality comes from `Leonxlnx/taste-skill` @ `<redacted>` (MIT; the `taste-skill-v1` entry), which `/scan-before-install` reduced to `dangerous` at the default tier on 2026-08-28, so under [ADR 0198](../../../../docs/adr/<redacted>.md) it could land only as a REBUILD-SPEC. The spec is the body of [task 1003325](https://cloudbongos.com/builders#/task/1003325); this file implements its requirements from scratch, **no upstream bytes consulted or copied**; the landing shape is [ADR 0220](../../../../docs/adr/<redacted>.md).
23
+
24
+ What changed in translation, on purpose: the upstream generation was written for a component framework with a motion library; a platform surface here is static markup, one sheet and the instance's transforms, so the stack conventions are restated for that and **every motion rule lives inside the Kill Switch** (the spring, the magnetic hover and the shared-element transition, which need script, are named as not available rather than quietly kept). The typography and colour rules are read **against the instance's pack**: the pack's faces are the faces and the pack's accent is the accent, whatever this skill would have preferred. Imagery follows the owner's rule (hero plates and hero objects only).
25
+
26
+ ## Why v1 is kept
27
+
28
+ The default skill (`design-taste-frontend`) reads the brief, states a design read and sets its dials from presets. This generation does something narrower and more predictable: it starts from a **fixed baseline** and adapts it in conversation, and it carries a **vocabulary** the default folded into its steps. A task that says "v1" means this file, and this file does not change when the default does. If both are in front of you and the task names neither, use the default.
29
+
30
+ ## The baseline triple
31
+
32
+ Variance **6**, motion **4**, density **5**. Fixed. Adapt them **conversationally**: when the person you are working with says "calmer", motion drops one; "busier" raises density one; "more editorial" raises variance one. Say the new triple each time it moves. Do not ask anyone to edit this file to change a dial, and do not edit it yourself for a session: the triple is the skill's baseline, the conversation is the override.
33
+
34
+ The motion value is clamped by the world before the dial applies (the chrome world: ambient loops at 20s or slower, one authored arrival, everything dead under reduced motion), and motion 0 is what every page becomes under `prefers-reduced-motion`, so the rest frame is designed first.
35
+
36
+ ## Stack conventions for a served surface
37
+
38
+ - **Check the surface's stack before importing anything**: the instance's `package.json`, the directory's nested `CLAUDE.md`, the sheet the page already loads. A platform surface is static HTML served through the instance's transforms, one sheet, and script only for live data (`createElement` / `textContent`, never `innerHTML`). This skill introduces no framework and no library.
39
+ - **If a package is missing, print the install command and stop.** Adding a dependency is a task decision, not a taste decision; the ship notes carry the command, the claim's owner takes it.
40
+ - **Static markup by default; script isolated to leaf behaviour** (a disclosure, a live refresh) and never to motion, because script motion escapes the Kill Switch.
41
+ - **Utility CSS is not assumed.** If a surface already uses one, read its version from the manifest before writing a class; if it does not, write the world's tokens straight.
42
+ - **Breakpoints are the world's** (one, at 900px in the chrome world) and the kit's three widths (1440 / 390 / 320) are the proof; the container is `spacing.container`; a full-height section is the world's floored stage or `100dvh`, never `100vh`.
43
+
44
+ ## Typography rules
45
+
46
+ - **Display type is large and tight-tracked**: the pack's display face at the world's display weight and tracking (the chrome world: Manrope 300, `-0.042em`, lowercase). A display line is drawing, not a slogan.
47
+ - **Body measure is capped near 65 characters** (`max-width: 62ch` on prose; the chrome world's bands cap at 56 to 62ch).
48
+ - **The pack's body face is the UI face.** A default UI sans is discouraged for premium work only where the pack does not name it; where the pack does, the pack wins.
49
+ - **Serif is not a UI face** on a working surface (a ledger, a settings page, a form). A look from `modules/ui-design/styles/` that carries a serif carries it for display, and the body stack decides the rest.
50
+ - Every real number is `font-variant-numeric: tabular-nums`; a figure that refreshes must not reflow the composition.
51
+
52
+ ## Colour rules
53
+
54
+ - **One accent, the pack's,** spent only on the pressable and the live (the chrome world's One Warm Thing Rule). Under 80% saturation is the pack author's business, not this page's: the accent is not adjusted per page.
55
+ - **No purple-to-blue glow aesthetic**, no neon-on-near-black HUD, no gradient text. These are bans whichever pack is on.
56
+ - **One palette per project** means the pack, and the pack's neutral ramp is the only ramp: no warm grey on one band and cool grey on the next, because there is only `bg`, `bg-card`, `bg-deep`, `bg-frame` and the inks.
57
+ - Every colour is a `var()` or a `color-mix()` of the fifteen; the dark twin is written twice; a literal is pure black or white with alpha as a scrim, mask or halo, and nothing else.
58
+
59
+ ## Emoji and icons
60
+
61
+ - **No emoji** in code, markup, copy or alt text. Not as a bullet, not as a status glyph, not in a button label. Cloud Bongos's `DESIGN.md` bans it by name; the default holds on every instance.
62
+ - **Icons come from one small allowed set of families, at one standardised stroke width.** The allowed set on a platform surface is: the world's inline mark (the chrome world's bongo-pair SVG), and one hairline glyph family drawn at a single stroke (`1.5px`, `stroke-linecap: round`) for the few glyphs a page needs (a chevron, an arrow, a magnifier, a close mark). Never two families on one page, never a thick default icon set, never a hand-drawn path introduced for one page. Where a world names its own family, that family is the set.
63
+ - An icon is never the only carrier of meaning: the label is the text, the glyph is the pointer.
64
+
65
+ ## Layout bans
66
+
67
+ - **No centred hero above variance 5.** At the baseline the hero is asymmetric: the type on one side, the object (a plate, by token) or air on the other.
68
+ - **No three equal feature cards.** Three unequal objects at three sizes read as a constellation; three equal boxes read as a rank, and the chrome world bans the row by name.
69
+ - **No boxed card containers above density 6.** Content is separated by hairlines (`rule-soft`) and negative space; a card is for a surface that carries its own elevation vocabulary, and the chrome world has none.
70
+
71
+ ## Full interaction cycles, not the successful state only
72
+
73
+ Every interactive component ships all of its states, and the page's states file (`<page>.states.json`) names them so the kit renders each:
74
+
75
+ - **Loading**: a skeleton shaped like the layout it replaces (the same columns, the same row heights, the ground tone `bg-deep`), never a spinner.
76
+ - **Empty**: a composed empty state that says what will be here and offers the one next action; a feed with nothing collapses its rows, its meter and its sub-line rather than promising six rows that never come.
77
+ - **Error**: inline, beside the thing that failed, in the world's `err` state colour on text, never a modal for a field.
78
+ - **Press**: tactile feedback on `:active` (`transform: scale(.98)` on the state curve); **focus** with the world's two-tone ring; **hover** on anything pressable.
79
+ - Every control clears the 24px floor in both axes, footer links included; primary pills sit at 46 to 48px.
80
+
81
+ ## Motion rules
82
+
83
+ - **The world's curves are the easing.** Where a world names an entrance curve (the chrome world's exponential ease-out, `cubic-bezier(.16, 1, .3, 1)`) and a state curve, use them; where it names none, a curve with a little overshoot for entrances and a plain ease-out for states. No spring library: a spring is script, and script motion is banned.
84
+ - **Staggered list reveals** are CSS: `animation-delay` from an `--i` custom property set in the markup (`style="--i: 3"`), never a script loop.
85
+ - **Shared-element layout transitions and magnetic hover are not available on this platform.** Both need script driving continuous values; the first is replaced by an instant state change under a 200ms crossfade, the second by the world's hover lift (a 6 to 8px `translateY` over 400ms).
86
+ - Everything is dead under the Kill Switch and the rest frame is the composition.
87
+
88
+ ## Performance guardrails
89
+
90
+ - Animate **`transform` and `opacity` only** (and `filter` on a plate where the world does). Never a layout property.
91
+ - **Grain and noise overlays** exist only where the world has that material (the expedition look's halftone screen is a look's material, not a default); when they do, they sit on a fixed, `pointer-events: none` pseudo-element.
92
+ - **No arbitrary `z-index`.** A named scale on `body` (`--z-bar`, `--z-band`, `--z-dialog`), reserved for systemic layers.
93
+ - **Full-height sections use dynamic viewport height** (`100dvh`, or the world's floored stage `max(100dvh, 900px)`), never `100vh`.
94
+ - `backdrop-filter` only on a fixed or sticky layer, and only where the world's glass has a blur (light glass over paper is a tint, blur 0).
95
+
96
+ ## The vocabulary of premium patterns
97
+
98
+ Named so a brief can ask for one and a review can name what is missing. Each is a pattern inside the world, not a component to import:
99
+
100
+ - **The two-line display**: the h1 as two lines, the second indented, lowercase where the world's display voice is.
101
+ - **The tabular figure**: a real number from the ledger at display weight, tabular, with a caption.
102
+ - **The hairline ledger**: rows built from 1px top rules, baseline-aligned columns, no borders, no boxes.
103
+ - **The door pill**: two equal actions joined in one glass bar, the primary carrying the accent with the world's `accent-on` ink.
104
+ - **The lit rail step**: one step of a pipeline lit in the accent (hairline, dot, name), the rest in ink; exactly one is live.
105
+ - **The leader-line callout**: a 1px diagonal line, a figure, a caption; real numbers only, at most two per surface.
106
+ - **The feathered patch**: a soft patch of the ground behind glyphs that sit on art, so a ground never cuts a hard-edged box out of a plate.
107
+ - **The contact shadow**: the one shadow in a world, and it belongs to the object, not to a component.
108
+ - **The hairline band menu**: a disclosure that unfolds as a full-width band under the bar, over the page, closed by one hairline.
109
+ - **The poster card**: a modal on the card ground closed by one hairline, its regions flat children in the brief's order, every region hiding on null.
110
+ - **Pill or nothing**: every control a full pill (999px), and the one exception the world names.
111
+
112
+ ## The tile-grid archetypes
113
+
114
+ Motion-first tile grids, each with its collapse under the world's breakpoint:
115
+
116
+ - **The constellation**: three different objects at deliberately unequal sizes and baseline drops; hover lifts one. Collapses to a vertical stack at unequal sizes, never to three equal circles.
117
+ - **The dense auto-flow**: `grid-auto-flow: dense` with interlocking spans and **zero empty cells**, three to five intentional tiles over eight cluttered ones. Collapses to one column; spans reset.
118
+ - **The hairline ledger grid**: fixed columns (`88px 1fr 152px 96px` in the chrome world), 1px top rules, tabular ids. Collapses to one column per row, the id and the title first.
119
+ - **The five-column rail**: five beats on one rail, one lit; collapses to one column with the hairlines kept.
120
+
121
+ ## Pre-flight (short)
122
+
123
+ - [ ] **Mobile collapse** looked at in the 390 and 320 shots; nothing scrolls sideways; every archetype collapsed the way its entry says.
124
+ - [ ] **Effect cleanup**: nothing to clean, because nothing moves in script; any live-refresh timer is cleared on navigation.
125
+ - [ ] **State coverage**: every state in the page's states file rendered by `node modules/ui-design/kit/render.js --page <path>` and ALL CLEAN; the interaction contract PASS under `probe.js`.