@bongos/core 1.19.477 → 1.19.479

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 (38) hide show
  1. package/.bongos-core.json +73 -38
  2. package/.claude/skills/design/SKILL.md +6 -2
  3. package/clients/bongos-client/README.md +1 -1
  4. package/clients/bongos-client/bongos-client.global.js +4 -0
  5. package/clients/bongos-client/index.cjs +4 -0
  6. package/clients/bongos-client/index.d.ts +5 -1
  7. package/clients/bongos-client/index.mjs +4 -0
  8. package/docs/adr/0230-a-look-skill-is-a-composition-grammar-the-palette-is-the-pack.md +36 -0
  9. package/docs/adr/README.md +1 -0
  10. package/docs/api/openapi.json +84 -3
  11. package/docs/api-reference.md +5 -3
  12. package/docs/copy-inventory.md +6 -6
  13. package/docs/copy-registry.json +6 -6
  14. package/docs/file-map.md +1 -1
  15. package/docs/module-api-changelog.md +4 -0
  16. package/modules/dev-box/app/src/vendor/bongos-client.cjs +4 -0
  17. package/modules/economy/credits.js +78 -0
  18. package/modules/economy/module.json +1 -1
  19. package/modules/economy/reward.js +4 -0
  20. package/modules/economy/routes/credits.js +151 -0
  21. package/modules/hall-ui/public/md-reader.js +24 -1
  22. package/modules/lifecycle/closeness.js +87 -37
  23. package/modules/lifecycle/routes/closeness.js +17 -8
  24. package/modules/platform-identity/platform-identity.js +1 -1
  25. package/modules/ui-design/module.json +1 -1
  26. package/modules/ui-design/skills/README.md +6 -2
  27. package/modules/ui-design/skills/gpt-taste/SKILL.md +110 -0
  28. package/modules/ui-design/skills/industrial-brutalist-ui/SKILL.md +110 -0
  29. package/modules/ui-design/skills/minimalist-ui/SKILL.md +103 -0
  30. package/modules/ui-design/skills/stitch-design-taste/SKILL.md +91 -0
  31. package/package-lock.json +2 -2
  32. package/package.json +1 -1
  33. package/src/module-api.js +1 -1
  34. package/tests/closeness.mjs +151 -31
  35. package/tests/credit_ledger_read.mjs +347 -0
  36. package/tests/hall_reading_world.mjs +80 -0
  37. package/tests/own_scope_guard.mjs +24 -2
  38. package/tests/ui_design_skills.mjs +56 -1
@@ -24,13 +24,18 @@
24
24
  // dropped and the remaining weights are renormalised, and the breakdown names what
25
25
  // was excluded and why.
26
26
  //
27
- // CONSENT. ADR 0171's posture is fail-closed account state with the hide flag
28
- // enforced in the READ path (task 1002277) — a builder who hid their profile must
29
- // not reappear as a node in someone else's graph. This file routes EVERY graph
30
- // read through one gate, `applyConsent`, rather than filtering at each call site,
31
- // so C05 (task 1003175, "consent is the read path, not a filter bolted on after")
32
- // has a single structural place to make it so. The gate FAILS CLOSED on error:
33
- // a consent read that throws drops the graph to empty rather than serving it.
27
+ // CONSENT (task 1003175 / C05). ADR 0171's posture is INHERITED, not reinvented:
28
+ // fail-closed account state, the hide flag enforced in the READ path (task
29
+ // 1002277) — a builder who hid their profile must not reappear as a node in
30
+ // someone else's graph. What C05 adds is WHERE the gate sits. `consentedIds` gates
31
+ // candidate IDS, and `readConsentedSignals` applies it to the signal maps
32
+ // themselves, so the graph is ASSEMBLED FROM CONSENTED DATA rather than assembled
33
+ // and then filtered. Nothing downstream — score, rank, total, page, hydrate — can
34
+ // see a hidden builder, because they were never in the set those are derived from.
35
+ // That is the difference the criterion asks for: an assembled-then-filtered graph
36
+ // still leaks through counts, ranks and totals even when the row itself is gone.
37
+ // The gate FAILS CLOSED: a consent read that throws or returns null yields the
38
+ // empty set, and so the empty graph.
34
39
  //
35
40
  // A KNOWN PERFORMANCE GAP, measured rather than assumed. The signal joins key on
36
41
  // tasks.goal_id (NO index), claims.builder_id and claims.task_id (indexed only
@@ -243,11 +248,21 @@ function toCountMap(rows) {
243
248
  }
244
249
 
245
250
  // ---------------------------------------------------------------------------
246
- // Consent — ONE gate, fail-closed
251
+ // Consent — ONE gate, fail-closed, and UPSTREAM of everything derived
247
252
  // ---------------------------------------------------------------------------
248
253
  //
249
- // Every graph read passes through here, so C05 has a single structural place to
250
- // make consent the read path rather than a filter bolted on at each call site.
254
+ // C05 (task 1003175) is "consent is the READ PATH, not a filter bolted on after",
255
+ // and the distinction is structural rather than stylistic. The gate takes a set of
256
+ // candidate builder IDS and returns the ones that may be seen — it never takes
257
+ // assembled rows. Consent is therefore resolved before a score, a rank, a total or
258
+ // a page exists, so there is no derived quantity that could have been computed
259
+ // while a hidden builder was still in the set.
260
+ //
261
+ // A gate that filtered assembled entries leaves every present and future aggregate
262
+ // one forgetful line away from leaking: "you have 4 close collaborators" over a
263
+ // list of 3 is a leak by subtraction even though the node itself is absent, and so
264
+ // is a page that comes back one short, and so is a rank with a gap in it. Removing
265
+ // the row is the easy half; never having counted it is the guarantee.
251
266
  //
252
267
  // `hiddenIdsFor` is injected: the platform-identity hub owns the hide flag
253
268
  // (ADR 0171 D2/D4) and lifecycle must not reach across a module boundary to read
@@ -255,19 +270,49 @@ function toCountMap(rows) {
255
270
  // is enabled there is no hub-level hide flag to honour, and NOTHING is suppressed
256
271
  // by this mechanism — which is correct, not a hole: on such an instance nobody has
257
272
  // been offered the switch. What is NOT tolerated is an ERROR being read as
258
- // "nobody is hidden", so a throwing reader empties the graph.
259
- async function applyConsent(entries, hiddenIdsFor) {
260
- if (typeof hiddenIdsFor !== 'function') return entries;
273
+ // "nobody is hidden", so a throwing reader yields the empty set.
274
+ async function consentedIds(candidateIds, hiddenIdsFor) {
275
+ const ids = [...candidateIds].map(String);
276
+ if (typeof hiddenIdsFor !== 'function') return new Set(ids);
277
+ if (!ids.length) return new Set();
261
278
  let hidden;
262
279
  try {
263
- hidden = await hiddenIdsFor(entries.map((e) => e.builder_id));
280
+ hidden = await hiddenIdsFor(ids);
264
281
  } catch (_) {
265
282
  // FAIL CLOSED. An unreadable consent state is not permission.
266
- return [];
283
+ return new Set();
267
284
  }
268
- if (!hidden) return []; // ditto: null is not "nobody"
285
+ if (!hidden) return new Set(); // ditto: null is not "nobody"
269
286
  const set = hidden instanceof Set ? hidden : new Set((hidden || []).map(String));
270
- return entries.filter((e) => !set.has(String(e.builder_id)));
287
+ return new Set(ids.filter((id) => !set.has(id)));
288
+ }
289
+
290
+ // THE READ PATH. Reads the three signals and hands back count maps that have
291
+ // ALREADY passed the consent gate — a hidden builder is not a key in any of them.
292
+ // Every graph read goes through this one function, so consent is not a step a
293
+ // caller performs but a property of the data a caller is given.
294
+ async function readConsentedSignals(builderId, { otherId = null, hiddenIdsFor } = {}, deps = {}) {
295
+ const activePool = deps.pool || pool;
296
+ const [coClaim, coSession, shipped] = await Promise.all([
297
+ coClaimCounts(builderId, { otherId }, { pool: activePool }),
298
+ coSessionCounts(builderId, { otherId }, { pool: activePool }),
299
+ shippedTogetherCounts(builderId, { otherId }, { pool: activePool }),
300
+ ]);
301
+
302
+ const candidates = new Set([...coClaim.keys(), ...coSession.keys(), ...shipped.keys()]);
303
+ const allowed = await consentedIds(candidates, hiddenIdsFor);
304
+ return {
305
+ ids: allowed,
306
+ co_claim: keepOnly(coClaim, allowed),
307
+ co_build_session: keepOnly(coSession, allowed),
308
+ shipped_together_goal: keepOnly(shipped, allowed),
309
+ };
310
+ }
311
+
312
+ function keepOnly(counts, allowed) {
313
+ const m = new Map();
314
+ for (const id of allowed) if (counts.has(id)) m.set(id, counts.get(id));
315
+ return m;
271
316
  }
272
317
 
273
318
  // ---------------------------------------------------------------------------
@@ -275,28 +320,30 @@ async function applyConsent(entries, hiddenIdsFor) {
275
320
  // ---------------------------------------------------------------------------
276
321
 
277
322
  // Everyone this builder has any measurable closeness to, scored and explained,
278
- // strongest first. `hiddenIdsFor` is the consent gate above.
279
- // `otherId` narrows every signal query to one pair at the DATABASE, so the pair
280
- // read does not fetch a hundred rows to keep one. It is threaded through rather
281
- // than filtered afterwards — see the note above the signal reads.
323
+ // strongest first. `otherId` narrows every signal query to one pair at the
324
+ // DATABASE, so the pair read does not fetch a hundred rows to keep one.
325
+ //
326
+ // Returns `{ entries, total }`. `total` is the size of the CONSENTED graph before
327
+ // paging — the honest answer to "how many people am I close to", counted here,
328
+ // downstream of the gate. It is returned rather than left to the caller because
329
+ // both ways a caller could work it out are wrong: counting pre-gate rows is the
330
+ // "you have 4 close collaborators" leak itself, and counting the page answers 100
331
+ // for a builder with 300.
282
332
  async function closenessFor(builderId, { limit = MAX_ROWS, hiddenIdsFor, otherId = null } = {}, deps = {}) {
283
333
  const activePool = deps.pool || pool;
284
- const [coClaim, coSession, shipped] = await Promise.all([
285
- coClaimCounts(builderId, { otherId }, { pool: activePool }),
286
- coSessionCounts(builderId, { otherId }, { pool: activePool }),
287
- shippedTogetherCounts(builderId, { otherId }, { pool: activePool }),
288
- ]);
334
+ // Nothing below this line can see a builder who hid: they are not a key in any
335
+ // of these maps, so no score, rank, total or page is ever computed with them in.
336
+ const signals = await readConsentedSignals(builderId, { otherId, hiddenIdsFor }, { pool: activePool });
289
337
 
290
- const ids = new Set([...coClaim.keys(), ...coSession.keys(), ...shipped.keys()]);
291
- let entries = [...ids].map((otherId) => {
338
+ const entries = [...signals.ids].map((id) => {
292
339
  const counts = {
293
- co_claim: coClaim.get(otherId) || 0,
294
- co_build_session: coSession.get(otherId) || 0,
295
- shipped_together_goal: shipped.get(otherId) || 0,
340
+ co_claim: signals.co_claim.get(id) || 0,
341
+ co_build_session: signals.co_build_session.get(id) || 0,
342
+ shipped_together_goal: signals.shipped_together_goal.get(id) || 0,
296
343
  };
297
344
  const scored = scoreCloseness(counts);
298
345
  return {
299
- builder_id: otherId,
346
+ builder_id: id,
300
347
  score: scored.score,
301
348
  // The explanation travels WITH the score, on every surface, rather than
302
349
  // behind a second call a caller may not make. That is the decision.
@@ -304,11 +351,14 @@ async function closenessFor(builderId, { limit = MAX_ROWS, hiddenIdsFor, otherId
304
351
  };
305
352
  }).filter((e) => e.score > 0);
306
353
 
307
- entries = await applyConsent(entries, hiddenIdsFor);
354
+ // Ranking and paging run over the consented set, so positions are dense — a
355
+ // hidden builder leaves no gap at the top of someone's graph — and the page is
356
+ // filled from the rest rather than coming back short. A short page announces a
357
+ // removal as loudly as the row would have.
308
358
  entries.sort((a, b) => b.score - a.score || String(a.builder_id).localeCompare(String(b.builder_id)));
309
- entries = entries.slice(0, capRows(limit));
359
+ const total = entries.length;
310
360
 
311
- return hydrate(entries, activePool);
361
+ return { entries: await hydrate(entries.slice(0, capRows(limit)), activePool), total };
312
362
  }
313
363
 
314
364
  // One pair, with the same breakdown the graph carries. Separate from closenessFor
@@ -323,7 +373,7 @@ async function closenessBetween(builderId, otherId, { hiddenIdsFor } = {}, deps
323
373
  // into the same queries, so there is still exactly one definition of each
324
374
  // signal and the pair read costs a pair.
325
375
  const graph = await closenessFor(builderId, { limit: 1, hiddenIdsFor, otherId }, { pool: activePool });
326
- const found = graph.find((e) => String(e.builder_id) === String(otherId));
376
+ const found = graph.entries.find((e) => String(e.builder_id) === String(otherId));
327
377
  if (found) return found;
328
378
 
329
379
  // Not in the graph. That is either "no measurable closeness" or "consent says
@@ -359,5 +409,5 @@ module.exports = {
359
409
  weightsModel, availableSignals, unavailableSignals,
360
410
  scoreCloseness, explainInWords,
361
411
  coClaimCounts, coSessionCounts, shippedTogetherCounts,
362
- applyConsent, closenessFor, closenessBetween,
412
+ consentedIds, readConsentedSignals, closenessFor, closenessBetween,
363
413
  };
@@ -15,10 +15,13 @@
15
15
  // a caller may simply not make is how a score ends up on a page with nothing to
16
16
  // point at. So `why` rides in the same payload as the number, always.
17
17
  //
18
- // CONSENT is resolved here and passed INTO the read as a gate (see
19
- // closeness.js applyConsent), never applied to the output afterwards. The hub owns
20
- // the hide flag (ADR 0171 D2/D4) and lifecycle must not read another module's
21
- // table (ADR 0083), so the port is resolved optionally and a reader is handed down.
18
+ // CONSENT is resolved here and passed INTO the read as a gate (closeness.js
19
+ // `consentedIds`, applied by `readConsentedSignals` to the signal maps), never to
20
+ // the output afterwards — these handlers receive a graph that was assembled from
21
+ // consented data, and so have no filtering left to forget. That is C05 (task
22
+ // 1003175). The hub owns the hide flag (ADR 0171 D2/D4) and lifecycle must not
23
+ // read another module's table (ADR 0083), so the port is resolved optionally and a
24
+ // reader is handed down.
22
25
  //
23
26
  // Reaches core only through the doorway (ADR 0083).
24
27
 
@@ -36,8 +39,9 @@ const { asyncHandler, parseId } = api;
36
39
  // hub-level hide flag to honour, because nobody on that instance has been offered
37
40
  // the switch — suppressing everyone would not be consent, it would be an outage.
38
41
  // What must never happen is an ERROR being read as "nobody is hidden", and that is
39
- // why the reader THROWS rather than swallowing: applyConsent catches and empties
40
- // the graph, which is the fail-closed direction ADR 0171 asks for.
42
+ // why the reader THROWS rather than swallowing: consentedIds catches and yields
43
+ // the empty set, so the graph is empty — the fail-closed direction ADR 0171 asks
44
+ // for.
41
45
  function consentReader(pool) {
42
46
  const hub = api.resolveOptional('platform-identity.hub');
43
47
  if (!hub || typeof hub.hiddenGithubIds !== 'function') return null;
@@ -85,10 +89,15 @@ module.exports = function buildClosenessRouter() {
85
89
  // Reading another builder's graph would expose who THEY are close to, which is
86
90
  // a fact about third parties they did not consent to share.
87
91
  router.get('/closeness/for-me', api.requireBuilder, asyncHandler('GET /closeness/for-me', async (req, res) => {
88
- const rows = await closeness.closenessFor(req.builder.id, {
92
+ const { entries, total } = await closeness.closenessFor(req.builder.id, {
89
93
  hiddenIdsFor: consentReader(api.pool),
90
94
  });
91
- return res.json({ closeness: rows, count: rows.length });
95
+ // `count` is the CONSENTED total the read counted for us, never `entries.length`.
96
+ // Counting the page here would be wrong twice over: it is the surface that says
97
+ // "you have N close collaborators", so counting rows that were assembled before
98
+ // the gate is the leak this criterion is about, and counting the page after it
99
+ // answers 100 for a builder with 300.
100
+ return res.json({ closeness: entries, count: total, returned: entries.length });
92
101
  }, { errorCode: 'closeness_failed' }));
93
102
 
94
103
  // One pair — "why am I close to THEM", asked directly rather than by paging the
@@ -886,7 +886,7 @@ async function getPublicProfileExtras(githubId, { pool = defaultPool } = {}) {
886
886
  // FAIL-CLOSED is the CALLER's job here, and deliberately so: this function
887
887
  // THROWS on a bad read rather than returning an empty set, because an empty set
888
888
  // means "nobody is hidden" and a database error must never be able to say that.
889
- // modules/lifecycle/closeness.js applyConsent catches and empties the graph.
889
+ // modules/lifecycle/closeness.js consentedIds catches and empties the graph.
890
890
  async function hiddenGithubIds(githubIds, { pool = defaultPool } = {}) {
891
891
  const ids = (Array.isArray(githubIds) ? githubIds : []).filter(isNumericGithubId).map(String);
892
892
  if (!ids.length) return new Set();
@@ -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", "design-taste-frontend", "design-taste-frontend-v1", "high-end-visual-design", "redesign-existing-projects", "impeccable", "image-to-code", "imagegen-frontend-web", "imagegen-frontend-mobile", "brandkit"] },
8
+ "contributes": { "skills": ["design", "design-sync", "figma-design-sync", "design-taste-frontend", "design-taste-frontend-v1", "high-end-visual-design", "redesign-existing-projects", "impeccable", "minimalist-ui", "industrial-brutalist-ui", "gpt-taste", "stitch-design-taste", "image-to-code", "imagegen-frontend-web", "imagegen-frontend-mobile", "brandkit"] },
9
9
  "provides": [],
10
10
  "consumes": []
11
11
  }
@@ -27,7 +27,7 @@ The eleven taste skills are single-file prose; the floor's one critical is a fal
27
27
 
28
28
  ## The in-house rebuilds (2026-09-01)
29
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. A rebuild whose method is a family of sub-commands ships as **one** skill dir with a `reference/<sub-command>.md` playbook beside the `SKILL.md`, exactly one loaded per request; the preamble is pinned once, in the `SKILL.md`, and governs every playbook ([ADR 0221](../../../docs/adr/<redacted>.md)).
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. A rebuild whose method is a family of sub-commands ships as **one** skill dir with a `reference/<sub-command>.md` playbook beside the `SKILL.md`, exactly one loaded per request; the preamble is pinned once, in the `SKILL.md`, and governs every playbook ([ADR 0221](../../../docs/adr/<redacted>.md)). A rebuild whose upstream shipped a **look** carries a composition grammar and no colours of its own — the palette is the instance's pack and the texture is a named entry in [`../styles/`](../styles/README.md) ([ADR 0230](../../../docs/adr/<redacted>.md)).
31
31
 
32
32
  | Skill | Rebuilt from (spec) | Task | What it is |
33
33
  |---|---|---|---|
@@ -35,13 +35,17 @@ A rebuild spec becomes a **first-party skill** at `<name>/` — no `vendor/` seg
35
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
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
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
+ | `minimalist-ui` | `Leonxlnx/taste-skill` `minimalist-skill` | [task 1003327](https://cloudbongos.com/builders#/task/1003327) | the quiet editorial look as a grammar and no colours: macro whitespace first, a capped measure, one display job and one body job, exactly one hairline weight in the sheet, washed accents measured against their own ink, flat tile cards, box-less accordions, keycaps, one depth treatment per band; declines dense surfaces |
39
+ | `industrial-brutalist-ui` | `Leonxlnx/taste-skill` `brutalist-skill` | [task 1003327](https://cloudbongos.com/builders#/task/1003327) | the technical-document look: one archetype per project (Swiss print or dark terminal, never mixed) with the other mode still complete, clamped uppercase display against fixed mono metadata, terracotta as the one hazard accent and sea as one terminal green, the hairline as a grid gap, zero rounding, static degradation |
40
+ | `gpt-taste` | `Leonxlnx/taste-skill` `gpt-tasteskill` | [task 1003327](https://cloudbongos.com/builders#/task/1003327) | the anti-default pass: a plan block before any markup with a selection derived from the route and bumped against siblings, the four-stage structure, the ultra-wide hero at two or three lines, a dense grid with zero empty cells, scroll-driven motion in CSS; pack-agnostic |
41
+ | `stitch-design-taste` | `Leonxlnx/taste-skill` `stitch-skill` | [task 1003327](https://cloudbongos.com/builders#/task/1003327) | the design contract document for an external screen-generating tool: four dials, eight fixed sections, descriptive language paired with exact values, the thirteen transcribed from the pack and diffed, motion as an intent brief inside the Kill Switch |
38
42
  | `impeccable` | `pbakaus/impeccable` @ `<redacted>` (skill v4.1.x) | [task 1003326](https://cloudbongos.com/builders#/task/1003326) | the design-craft method as one skill with sub-commands, one playbook loaded per request from `impeccable/reference/`: shape, critique, audit, polish, harden, onboard, animate, colorize, typeset, layout, adapt, distill, clarify, optimize, document, extract, bolder, quieter; the craft floor and the bounded finish live in its `SKILL.md`; the three persisted homes are the instance's own files; no hooks, no bypass agent, no telemetry, no live server (the playwright plugin and the kit are the loops) |
39
43
  | `image-to-code` | `Leonxlnx/taste-skill` @ `<redacted>` | [task 1003328](https://cloudbongos.com/builders#/task/1003328) | the analyse-then-implement loop with the owner's references and the world as the source of truth; the only image it may cause is the hero plate; a structured per-image analysis, the extraction spec, a faithful translation, drift named as the failure mode, a clarity checklist the kit measures |
40
44
  | `imagegen-frontend-web` | `Leonxlnx/taste-skill` @ `<redacted>` | [task 1003328](https://cloudbongos.com/builders#/task/1003328) | the plate-making skill: text-free hero plates, hero objects, backdrops and orbs for the slots a composition already has, a composition anchor per plate, one family per surface, the pack as the palette lock, the instance's art pipeline key, the pair-and-sidecar output contract; never a section image, a comp or a screen |
41
45
  | `imagegen-frontend-mobile` | `Leonxlnx/taste-skill` @ `<redacted>` | [task 1003328](https://cloudbongos.com/builders#/task/1003328) | portrait plates for the 390 and 320 compositions, clear of the regions a phone reserves, the first view held to one focal point, a scrim under text over a plate; a screen is the states file rendered by the kit; no phone mockups, no flows of screens; native declines |
42
46
  | `brandkit` | `Leonxlnx/taste-skill` @ `<redacted>` | [task 1003328](https://cloudbongos.com/builders#/task/1003328) | the identity system with the strategy inferred first and the logo standard kept: the mark drawn as SVG, the colour system as the pack, the type specimen as the faces, the applications as the kit's renders, the image direction as a hero-object family, the board as a contact sheet; never a generated logo, board or mockup |
43
47
 
44
- 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. The image family (task 1003328, [ADR 0222](../../../docs/adr/<redacted>.md)) carries the one rewrite the upstream never had: images are for text-free hero plates and hero objects only, so a screen is the surface itself, the mark is drawn, the board is a contact sheet, and what ships is the pair (a black-ground JPEG for dark, an alpha WebP cutout for light) with a provenance sidecar beside every file, through the instance's art pipeline key. Still specs, not skills: the detector (task 1003329), the four looks (1003327).
48
+ 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. The image family (task 1003328, [ADR 0222](../../../docs/adr/<redacted>.md)) carries the one rewrite the upstream never had: images are for text-free hero plates and hero objects only, so a screen is the surface itself, the mark is drawn, the board is a contact sheet, and what ships is the pair (a black-ground JPEG for dark, an alpha WebP cutout for light) with a provenance sidecar beside every file, through the instance's art pipeline key. The four looks (task 1003327, [ADR 0230](../../../docs/adr/<redacted>.md)) carry the other rewrite: a look skill ships structure, rhythm, type roles, component anatomy, motion and bans and **no colours**, so a substrate becomes an archetype that still ships both modes, a prompt-seeded roll becomes a deterministic selection derived from the route, a design document transcribes the world instead of authoring one, and the upstream's seeded placeholder photography is dropped rather than softened. The whole scoped set of thirteen is now rebuilt in-house; nothing from either origin is still waiting on a spec.
45
49
 
46
50
  **In the core checkout itself**, where these skills are written, Claude Code reads only `.claude/skills/` at the repo root and the materialiser's self-copy guard leaves it empty of module skills. `node scripts/gds/claude-materialize.js --module-skills-only` lands every enabled module's skills there as untracked, self-ignored copies (a generated `.claude/skills/.module-skills.json` manifest and a `.gitignore` beside them; `--clean` removes them; the file map skips them by the manifest) so a builder can invoke them here too ([task 1003330](https://cloudbongos.com/builders#/task/1003330), [ADR 0224](../../../docs/adr/<redacted>.md)). Edit the module's `skills/<name>/`, never the copy, and re-run to refresh.
47
51
 
@@ -0,0 +1,110 @@
1
+ ---
2
+ name: gpt-taste
3
+ description: Anti-default enforcement for a persuasive scrolling surface when the last attempt came out looking like every other one. A written plan block before any markup whose selection is derived deterministically from the route (a hero architecture, three component architectures, two CSS motion paradigms, checked against what sibling surfaces already used), the four-stage attention-interest-desire-action structure, an ultra-wide hero container that holds the headline to two or three lines, a dense tile grid with zero empty cells, real scroll-driven motion inside the Kill Switch, and a measured contrast sweep. Triggers on "it looks generic", "award-tier", "stop making the same page", "anti-default". Rebuilt in-house from the taste-skill rebuild spec (task 1003327, ADR 0198).
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 `gpt-tasteskill` 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) — the `[gpt-taste]` requirement group — and this file is its port under [task 1003327](https://cloudbongos.com/builders#/task/1003327); it implements those requirements from scratch, **no upstream bytes consulted or copied**. The landing shape is [ADR 0220](../../../../docs/adr/<redacted>.md); what a *look* skill is allowed to be is [ADR 0230](../../../../docs/adr/<redacted>.md).
23
+
24
+ What changed in translation, on purpose: the upstream asked the agent to **simulate a random number generator** seeded from the prompt and let the roll pick the design. A roll nobody can reproduce is not a decision, and the same roll on a sibling surface is a coincidence rather than a system — so the selection here is **deterministic and written down**: derived from the surface's own route, recorded in the plan block, and checked against what its siblings already used. Its "type stack" selection is gone; the pack carries two stacks and this skill does not add a face. Its ambient graded backgrounds are `color-mix()` radials of the fifteen, never a new colour. Its **filtered seeded placeholder photography is dropped entirely** — the only rasters this skill may ask for are text-free hero plates under the owner's rule, and a surface with no plate ships without one. Its scroll-motion paradigms survive only as CSS scroll-driven animation inside the Kill Switch: pinned titles, scrubbed reveals and stacking cards are available that way; pointer-tracked physics and shared-element transitions are named *not available* rather than faked in script.
25
+
26
+ ## Where this look lives
27
+
28
+ This skill is **not a texture**. It is an anti-default enforcement layer that runs over whatever look is on: the palette is the instance's pack, and a variant comes from the style library at `modules/ui-design/styles/` — it composes the same way on `chrome-world`, `expedition` or `grove`, and the plan block below names which one is in force. Where it disagrees with the instance's `DESIGN.md`, the instance wins.
29
+
30
+ ## Fires on
31
+
32
+ A persuasive, scrolling surface — a landing page, a launch page, a product marketing page — **and specifically when the previous attempt came out looking like every other one**: a centred hero, three equal cards, a two-column feature row, a footer. It does not fire on an application surface; density and repetition are virtues in a dashboard, and this skill's whole method is to break repetition.
33
+
34
+ ## Step 1 — the plan block, before any UI code
35
+
36
+ Write this block first, in the task's notes and as a comment at the head of the sheet. No markup until it exists.
37
+
38
+ ```
39
+ SURFACE /pricing (the seed: the route)
40
+ PACK expedition (the look in force)
41
+ HERO off-axis split (of the five below; siblings used: full-bleed type, stage)
42
+ COMPONENTS marquee band / inline-image headline / horizontal accordion
43
+ MOTION pinned title + scrubbed reveal (two paradigms, both CSS)
44
+ HERO MATH container 1180px, clamp(44px, 5.6vw, 88px), headline 47 chars -> 2 lines
45
+ GRID PROOF 9 items, dense auto-flow, spans 2/1/1/3/1/2/1/2/1 -> 0 empty cells
46
+ CONTRAST CTA accent-on over --accent = 8.1:1 · label ink-faint on bg-card = 4.9:1
47
+ ```
48
+
49
+ **The selection is deterministic, not random.** Take the surface's route, sum its character codes, and index the lists below. Then check the sibling surfaces of this instance: if the route's index lands on a hero architecture a sibling already uses, take the next one. Record both the index and the bump. The same route re-derives the same page a year later; two different routes cannot collide silently.
50
+
51
+ **Hero architectures** (pick one): full-bleed type · off-axis split · the stage (one object centred, type wrapped around it) · the ledger (type in a left rail, content scrolling beside it) · the masthead (a text-free hero plate behind the words, with a scrim under the text).
52
+
53
+ **Component architectures** (pick three, never the same three as a sibling): the marquee band · the inline-image headline · the horizontal accordion · the stacking card sequence · the offset testimonial rail · the numbered chapter list · the comparison lattice.
54
+
55
+ **Motion paradigms** (pick two, both CSS): pinned section title beside a scrolling gallery · scrubbed word-by-word reveal · scale-in and fade-out on exit · stacking cards · hover physics on a clickable card.
56
+
57
+ ## Step 2 — the four-stage structure
58
+
59
+ Always opening with a navigation bar that is **not** a centred logo with links either side.
60
+
61
+ 1. **Attention** — the hero, built to the architecture chosen above.
62
+ 2. **Interest** — the tile grid (below).
63
+ 3. **Desire** — the scroll sequence, carrying both motion paradigms.
64
+ 4. **Action** — the closer: one CTA, restated, with the single strongest reason beside it.
65
+
66
+ Sections read as chapters: the vertical padding between them is at or above twice the world's band clamp, and each one is either the widest thing on the page or the narrowest. Two adjacent bands never share a width.
67
+
68
+ ## The hero rule
69
+
70
+ The headline container is **ultra-wide** so the headline flows in **two to three lines**. Four or more lines is a failure with exactly two fixes: widen the container, or reduce the clamped font size. **Never rewrite the copy to fit the layout** — the sentence is the product's, the container is yours. Do the arithmetic in the plan block, then confirm it in the render.
71
+
72
+ Banned in the hero: a floating stamp badge over the headline, a row of pill tags beneath it, and raw statistics inside it. Statistics belong to the interest stage where they have room to be believed.
73
+
74
+ ## The tile grid
75
+
76
+ - `grid-auto-flow: dense` with interlocking spans and **mathematically zero empty cells** — prove the count in the plan block.
77
+ - Three to five intentional cards, not eight thin ones. A card that exists because the grid had a hole is deleted, and the spans are redistributed.
78
+ - Never three equal cards in a row. Never a grid whose every cell is the same size.
79
+
80
+ ## Motion, inside the Kill Switch
81
+
82
+ Everything below is CSS, on the world's curves, and dead under `@media (prefers-reduced-motion: reduce)` with the rest frame a complete composition.
83
+
84
+ - **Real scroll-driven motion** via `animation-timeline: view()` and `scroll()` behind `@supports`: the pinned section title, the image that scales in and fades on exit, the word-by-word scrubbed reveal (one span per word, stepped `animation-range`), the stacking card sequence (`position: sticky` plus a view timeline).
85
+ - **Hover physics** on a clickable card: a small lift and a hairline change on `transform` and border colour only, on the state curve.
86
+ - **Not available here**, and named as such rather than approximated: spring physics, magnetic or pointer-tracked hover, shared-element transitions, and any continuous value read from a scroll listener. If the composition depends on one of them, the composition changes.
87
+ - Nothing moves in JavaScript. At most one `IntersectionObserver` adding a single class, and only where a view timeline is unsupported.
88
+
89
+ ## The bans
90
+
91
+ Defaults an instance's `DESIGN.md` may only tighten:
92
+
93
+ - **Enumerated meta labels above sections** (`01 — FEATURES`). The chapter is legible from its own content.
94
+ - **Emoji**, anywhere: markup, copy, alt text, comments, commit message.
95
+ - **Low-contrast button text.** Every CTA is measured with `node modules/ui-design/kit/contrast.js` and pasted into the plan block; a CTA that fails is not a style choice.
96
+ - **Flat colour on a full band** where the world has an ambient tier: use a `color-mix()` radial of the fifteen instead, at low strength, behind the content layer.
97
+ - **Horizontal overflow, page-wide.** A guard on the page container, and the 320 shot is the proof.
98
+
99
+ ## Pre-flight, measured by the kit
100
+
101
+ Render the real surface — `node modules/ui-design/kit/render.js --page <path> --style <pack>` — and audit the shots against the plan block. The anti-pattern detector (`node modules/ui-design/kit/tells.js`, or `check-mock.js` which runs it) catches the mechanical half of this list; the rest you read.
102
+
103
+ - [ ] The plan block exists, its selection is derived from the route, and the sibling bump is recorded.
104
+ - [ ] Headline at two or three lines at 1440, and still at 390.
105
+ - [ ] Grid proof holds in the render: zero empty cells, no three-equal row.
106
+ - [ ] Two adjacent bands never share a width; section padding at or above twice the band clamp.
107
+ - [ ] Both motion paradigms are CSS; the reduced-motion shots are the rest frame and it is complete.
108
+ - [ ] Every CTA pair measured and pasted; no emoji anywhere in the diff.
109
+ - [ ] Zero horizontal overflow at 1440, 390 and 320.
110
+ - [ ] Where a **hero plate** is used it is text-free, a background token with its provenance sidecar, and it is the only raster on the page.
@@ -0,0 +1,110 @@
1
+ ---
2
+ name: industrial-brutalist-ui
3
+ description: Raw mechanical interfaces that read like a technical document, inside the instance's world. One archetype per project (Swiss industrial print or dark tactical terminal, never mixed), massive uppercase clamped display against fixed tracked-out mono metadata, the pack's terracotta as the single hazard accent and its sea as one terminal green, strict grid anchoring with the hairline as a one-pixel grid gap, zero corner rounding, bracket and crosshair framing glyphs, and static halftone, scanline and noise degradation inside the Kill Switch. Triggers on "brutalist", "industrial", "terminal", "make it raw", "like a technical document". Rebuilt in-house from the taste-skill rebuild spec (task 1003327, ADR 0198).
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 `brutalist-skill` 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) — the `[industrial-brutalist-ui]` requirement group — and this file is its port under [task 1003327](https://cloudbongos.com/builders#/task/1003327); it implements those requirements from scratch, **no upstream bytes consulted or copied**. The landing shape is [ADR 0220](../../../../docs/adr/<redacted>.md); what a *look* skill is allowed to be is [ADR 0230](../../../../docs/adr/<redacted>.md).
23
+
24
+ What changed in translation, on purpose: the upstream's central rule was **one substrate per project** — off-white document paper *or* near-black terminal, never both. A platform surface here ships **both modes on every page** (the preamble's dark twins), so the substrate choice becomes an **archetype** that decides which mode the surface is *composed for* while the other mode is still complete and still measured. Its hazard red and optional terminal green are not new colours: red is the pack's `terracotta`, green is the pack's `sea`, and neither may appear a third time. Its "zero corner rounding" is legal because a radius is a **derived tier**, not one of the fifteen — a brutalist sheet sets its own radii to 0 and says so in the header comment; where the instance's `DESIGN.md` names a radius as a signature, the instance wins. Its degradation effects (halftone, dithering, scanlines, noise) survive as CSS built from the one legal literal, pure black or white with alpha, and every one of them is static: a scanline that crawls is an animation, and it dies under the Kill Switch with everything else. Its "high-contrast serif after heavy degradation" is dropped unless the pack already carries a serif; this skill does not add a face.
25
+
26
+ ## Where this look lives
27
+
28
+ The **grammar** below is this skill's. The **palette** is not: it comes from the instance's pack, and a variant comes from the style library at `modules/ui-design/styles/`. The two archetypes map onto the library like this — the **terminal** archetype is at home on `chrome-world` (a black void, one warm accent, mono metadata), and the **print** archetype is at home on `expedition` (warm paper, halftone, heavy uppercase display). `grove` is too soft for either and this skill says so rather than trying. Render under one with `--style <name>` before committing.
29
+
30
+ ## Fires on
31
+
32
+ Data-heavy dashboards, telemetry and status surfaces, engineering portfolios, technical editorial — anything meant to read like a document produced by a machine rather than a brochure. It does **not** fire on a sign-up flow, a marketing landing page or an onboarding surface: the grammar reads as hostile where the user's job is to be persuaded or reassured.
33
+
34
+ ## Commit to one archetype, and write it down
35
+
36
+ Pick **one** before anything is drawn, name it in the sheet's header comment and in the ship notes. The two are never mixed inside one interface.
37
+
38
+ | Archetype | Composed for | The substrate | The metadata voice |
39
+ |---|---|---|---|
40
+ | **Swiss industrial print** | light | the pack's `bg` as document paper, `bg-frame` as the plate | mono, `ink-soft`, tracked out |
41
+ | **Dark tactical terminal** | dark | the sheet's black tier, `bg-card` as the compartment | mono, the pack's `sea` at label size |
42
+
43
+ The mode the archetype is *not* composed for still ships, still clears AA, and still gets its own shots. It is the same composition in the other tier, not a second design: the print archetype in dark is the same grid on the black tier with the rules inverted; the terminal archetype in light is the same grid on paper.
44
+
45
+ ## Typography
46
+
47
+ - **Macro: massive uppercase display.** The pack's `display` stack at its heaviest available weight, uppercase, negative tracking (about -0.03em), leading compressed to 0.9, and `clamp()`ed. Clamped sizing is **reserved for macro type**; nothing else on the page fluid-scales.
48
+ - **Micro: fixed mono, tracked out.** Every piece of metadata, every nav item, every unit identifier, coordinate, timestamp and count is mono at a fixed small size (12 to 13px), uppercase, `letter-spacing: .12em`. It never scales with the viewport; a technical readout that grows on a wide screen stops reading as a readout.
49
+ - **Nothing in between is decorative.** Body copy is the pack's `body` stack at the world's scale. There is no third voice.
50
+ - **A serif appears only if the pack has one.** This skill adds no face and loads no font; the two stacks the pack carries are the whole type system.
51
+
52
+ ## Colour
53
+
54
+ - **One substrate, per the archetype.** Everything else is drawn on it.
55
+ - **One hazard accent: the pack's `terracotta`.** It marks what is dangerous, live or out of tolerance. It is never a decoration and never a second heading colour.
56
+ - **One optional terminal green: the pack's `sea`, on exactly one element** across the surface (a live indicator, a cursor, an in-range readout). A second use is a bug.
57
+ - **The pack's `accent` stays where the world put it** — on what is pressable — and does not join the hazard vocabulary.
58
+ - **Prohibited by default**: gradients as a surface, soft shadows, translucency as a material. Where the instance's world is built on glass (the chrome world is), this skill uses the world's glass token for the fixed layers the world already puts it on and adds none of its own; the world wins.
59
+
60
+ ## Layout
61
+
62
+ - **Strict grid anchoring.** One grid for the whole surface, declared once; every compartment starts and ends on a line. Nothing is positioned by eye.
63
+ - **Visible compartments.** A region is bounded by a rule on all four sides or by a full-bleed rule above and below it. The rule is `rule`, 1px, at full strength — this look does not use the soft tier.
64
+ - **The hairline is a grid gap.** A one-pixel `gap` on a grid over a parent painted in `rule` produces a true hairline lattice that never doubles at a junction and never rounds. Prefer it to per-cell borders.
65
+ - **Oscillation.** A dense cluster of readouts sits directly against a band of vast negative space. Even rhythm is the failure mode; if every band has the same weight, the look has not landed.
66
+ - **Zero corner rounding.** Every radius on this surface is `0`, set once in the sheet's own derived tier with a comment naming this skill. A world whose signature shape is a pill keeps it and the sheet says which one it kept.
67
+ - **Full-width rules** between chapters, edge to edge, not inset to the container.
68
+
69
+ ## The component vocabulary
70
+
71
+ Standard web chrome is replaced, not restyled:
72
+
73
+ - **Bracket and slash framing** on labels and actions: `[ DEPLOY ]`, `// SECTION 04`, `< BACK`. The glyphs are text, in the mono voice, never an icon font.
74
+ - **Registration and trademark marks used structurally** — a `®` or `™` at the corner of a compartment as a registration tick, at label size in `ink-faint`.
75
+ - **Crosshairs at grid intersections**: a 9px cross drawn with two pseudo-element rules in `rule`, on a pointer-inert layer, at the corners the grid actually has.
76
+ - **Barcode rules**: a `repeating-linear-gradient` of `ink` at varying stops as a band separator, at low alpha, never as a background for text.
77
+ - **Warning stripes**: a 45-degree `repeating-linear-gradient` of the `terracotta` and the substrate, reserved for a genuinely hazardous state and used on a bar no taller than 6px.
78
+
79
+ Every one of these is drawn from tokens and CSS. None of them is an image, and none of them carries meaning a screen reader cannot reach — a bracket is decoration around a real label, never the label itself.
80
+
81
+ ## Simulated analog degradation
82
+
83
+ Three treatments, at most two per surface, all static:
84
+
85
+ - **Halftone or one-bit dithering** over a plate: a `repeating-radial-gradient` mask at low alpha over a background token, or a `background-blend-mode` against a dot pattern. The plate underneath is one of the instance's own **text-free hero plates** with its provenance sidecar; this skill never asks for a new one.
86
+ - **Scanlines**: a `repeating-linear-gradient` of transparent and pure black at 4 to 6 percent alpha, 3px period, on a fixed `pointer-events: none` pseudo-element. Static. A scanline that moves is an animation and dies under the switch anyway.
87
+ - **One global noise layer**: a single fixed, pointer-inert pseudo-element at very low alpha. One per page, never per component.
88
+
89
+ All three are built from the one legal literal (pure black or white with alpha). None of them reduces the contrast of text measured underneath it — check with `node modules/ui-design/kit/contrast.js` after the layer is on, not before.
90
+
91
+ ## Engineering directives
92
+
93
+ - **Semantic technical elements** for readouts: `<data value>`, `<time datetime>`, `<meter>`, `<output>`, `<abbr title>`. A telemetry number that is only a `<span>` is a defect.
94
+ - **Clamped sizing only on macro type**, as above.
95
+ - **`z-index` on a named systemic scale** declared on `body`; the degradation layers are the top of it, the content never competes.
96
+ - **No layout property animates**; `transform` and `opacity` only.
97
+ - Motion is minimal by nature here, and what exists lives inside the **Kill Switch** — `@media (prefers-reduced-motion: reduce)` at the foot of the token layer killing every animation and transition, with the rest frame complete.
98
+
99
+ ## Pre-flight, measured by the kit
100
+
101
+ Render the real surface — `node modules/ui-design/kit/render.js --page <path>`, and `--style chrome-world` or `--style expedition` for the archetype you committed to — and read the shots.
102
+
103
+ - [ ] The archetype is named in the sheet header and the ship notes, and the interface carries exactly one.
104
+ - [ ] The mode the archetype was **not** composed for is complete and clears AA in its own shots.
105
+ - [ ] The hazard accent appears only on hazard; the terminal green appears exactly once.
106
+ - [ ] Every radius on the surface is 0, or the one the world kept is named.
107
+ - [ ] The lattice is a grid gap, not per-cell borders; no doubled or rounded junctions at 1440, 390 or 320.
108
+ - [ ] At most two degradation treatments, all static, all pointer-inert, and text under them still measures AA.
109
+ - [ ] Mono metadata does not scale with the viewport at any of the three widths.
110
+ - [ ] Reduced-motion pass identical to the rest frame; `check-mock.js` clean, tells waived in the file with a reason where kept.