@bongos/core 1.20.40 → 1.20.42

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 (74) hide show
  1. package/.bongos-core.json +265 -235
  2. package/.claude/skills/design/SKILL.md +6 -5
  3. package/docs/adr/0081-tool-agnostic-design-layer.md +1 -1
  4. package/docs/adr/0198-third-party-skill-vendoring-policy.md +2 -2
  5. package/docs/architecture.md +1 -1
  6. package/docs/file-map.md +51 -54
  7. package/docs/module-api-changelog.md +4 -0
  8. package/docs/modules-contract.md +7 -5
  9. package/docs/onboarding/slash-commands.md +1 -1
  10. package/modules/design-styles/CLAUDE.md +15 -0
  11. package/modules/design-styles/module.json +12 -0
  12. package/modules/{ui-design → design-styles}/skills/PREAMBLE.md +2 -2
  13. package/modules/{ui-design → design-styles}/skills/README.md +6 -6
  14. package/modules/{ui-design → design-styles}/skills/brandkit/SKILL.md +2 -2
  15. package/modules/{ui-design → design-styles}/skills/design-taste-frontend/SKILL.md +2 -2
  16. package/modules/{ui-design → design-styles}/skills/gpt-taste/SKILL.md +2 -2
  17. package/modules/{ui-design → design-styles}/skills/high-end-visual-design/SKILL.md +2 -2
  18. package/modules/{ui-design → design-styles}/skills/image-to-code/SKILL.md +2 -2
  19. package/modules/{ui-design → design-styles}/skills/imagegen-frontend-mobile/SKILL.md +2 -2
  20. package/modules/{ui-design → design-styles}/skills/imagegen-frontend-web/SKILL.md +2 -2
  21. package/modules/{ui-design → design-styles}/skills/impeccable/SKILL.md +3 -3
  22. package/modules/{ui-design → design-styles}/skills/industrial-brutalist-ui/SKILL.md +2 -2
  23. package/modules/{ui-design → design-styles}/skills/minimalist-ui/SKILL.md +2 -2
  24. package/modules/{ui-design → design-styles}/skills/policy.json +1 -1
  25. package/modules/{ui-design → design-styles}/skills/redesign-existing-projects/SKILL.md +2 -2
  26. package/modules/{ui-design → design-styles}/skills/stitch-design-taste/SKILL.md +2 -2
  27. package/modules/{ui-design → design-styles}/skills/style/SKILL.md +2 -2
  28. package/modules/pixel-art/CLAUDE.md +15 -0
  29. package/modules/pixel-art/module.json +12 -0
  30. package/modules/provisioning/starter-bundles.js +15 -0
  31. package/modules/ui-design/kit/serve.js +2 -0
  32. package/modules/ui-design/module.json +3 -3
  33. package/package-lock.json +2 -2
  34. package/package.json +1 -1
  35. package/release-notes.json +12 -0
  36. package/scripts/gds/doc-cli-guard.js +1 -1
  37. package/scripts/gds/fitness-ratchets.js +4 -1
  38. package/scripts/gds/fitness.js +2 -1
  39. package/scripts/gds/module-artifact.js +1 -1
  40. package/scripts/gds/module-assess-security.js +251 -0
  41. package/scripts/gds/publish-manifest.js +2 -0
  42. package/scripts/gds/skill-lint.js +30 -8
  43. package/src/bongos/module-scope-map.js +8 -2
  44. package/src/module-api.js +1 -1
  45. package/tests/claude_materialize.mjs +9 -8
  46. package/tests/module_assess_security.mjs +216 -0
  47. package/tests/module_loader.mjs +1 -1
  48. package/tests/opt_in_skill_modules.mjs +165 -0
  49. package/tests/skill_lint.mjs +26 -2
  50. package/tests/ui_design_skills.mjs +29 -17
  51. package/modules/ui-design/skills/design-taste-frontend-v1/SKILL.md +0 -135
  52. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/adapt.md +0 -0
  53. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/animate.md +0 -0
  54. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/audit.md +0 -0
  55. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/bolder.md +0 -0
  56. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/clarify.md +0 -0
  57. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/colorize.md +0 -0
  58. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/critique.md +0 -0
  59. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/distill.md +0 -0
  60. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/document.md +0 -0
  61. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/extract.md +0 -0
  62. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/harden.md +0 -0
  63. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/layout.md +0 -0
  64. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/onboard.md +0 -0
  65. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/optimize.md +0 -0
  66. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/polish.md +0 -0
  67. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/quieter.md +0 -0
  68. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/shape.md +0 -0
  69. /package/modules/{ui-design → design-styles}/skills/impeccable/reference/typeset.md +0 -0
  70. /package/{.claude → modules/pixel-art}/skills/otb-character-review/SKILL.md +0 -0
  71. /package/{.claude → modules/pixel-art}/skills/otb-design-review/SKILL.md +0 -0
  72. /package/{.claude → modules/pixel-art}/skills/otb-feedback-capture/SKILL.md +0 -0
  73. /package/{.claude → modules/pixel-art}/skills/otb-figma-sync/SKILL.md +0 -0
  74. /package/{.claude → modules/pixel-art}/skills/otb-tile-generate/SKILL.md +0 -0
@@ -14,7 +14,11 @@
14
14
  // face, and keeps the translations that ADR fixed (the archetype that still ships both modes,
15
15
  // the deterministic selection, the transcribed colour contract). Since task 1003475 (ADR
16
16
  // 0231) a module skill declares one of TWO origins - a rebuild from a spec, or first party -
17
- // and the origin pins branch on which; the /style session is the first original. DB-free.
17
+ // and the origin pins branch on which; the /style session is the first original. Since
18
+ // task 1004470 those skills are the opt-in `design-styles` module (default: false), split
19
+ // out of ui-design, which keeps /design, the sync skills, the kit and the style library
20
+ // (modules/ui-design/styles/) the skills still name as their palette source. The file
21
+ // keeps its name because the preamble's own marker line points here. DB-free.
18
22
  import assert from 'node:assert/strict';
19
23
  import { test } from 'node:test';
20
24
  import { createRequire } from 'node:module';
@@ -25,7 +29,8 @@ import { fileURLToPath } from 'node:url';
25
29
 
26
30
  const require = createRequire(import.meta.url);
27
31
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
28
- const MODULE = path.join(ROOT, 'modules', 'ui-design');
32
+ const MODULE = path.join(ROOT, 'modules', 'design-styles');
33
+ const UI_DESIGN = path.join(ROOT, 'modules', 'ui-design'); // keeps the style library + /design
29
34
  const SKILLS = path.join(MODULE, 'skills');
30
35
  const VENDOR = path.join(SKILLS, 'vendor');
31
36
  const read = (p) => fs.readFileSync(p, 'utf8').replace(/\r\n/g, '\n');
@@ -114,9 +119,11 @@ test('every vendored SKILL.md opens with the PLATFORM PREAMBLE, byte-identical t
114
119
  });
115
120
 
116
121
  // ── every in-house rebuild: the preamble, the Rebuilt-from section, no sidecar (ADR 0220) ──
117
- const TASTE_CORE = ['design-taste-frontend', 'design-taste-frontend-v1', 'high-end-visual-design', 'redesign-existing-projects'];
122
+ // design-taste-frontend-v1 was the fourth until task 1004470 deleted it (owner: "v1 can go").
123
+ const TASTE_CORE = ['design-taste-frontend', 'high-end-visual-design', 'redesign-existing-projects'];
118
124
 
119
- test('the taste core of task 1003325 exists as four in-house skills and every one is declared', () => {
125
+ test('the taste core of task 1003325 exists as three in-house skills, every one declared, and the deleted v1 stays gone', () => {
126
+ assert.ok(!ownDirs.includes('design-taste-frontend-v1') && !manifest.contributes.skills.includes('design-taste-frontend-v1'), 'design-taste-frontend-v1 is neither on disk nor declared (task 1004470)');
120
127
  for (const name of TASTE_CORE) {
121
128
  assert.ok(ownDirs.includes(name), `${name}: skills/${name}/SKILL.md exists`);
122
129
  assert.ok(manifest.contributes.skills.includes(name), `${name}: declared in contributes.skills`);
@@ -208,7 +215,7 @@ test('no image-family skill carries the directives the panel flagged: no eagerne
208
215
  });
209
216
 
210
217
  test('the worked example of the image family is the grove specimen: the pair with a sidecar beside every raster, referenced as tokens on the mock body', () => {
211
- const grove = path.join(MODULE, 'styles', 'grove');
218
+ const grove = path.join(UI_DESIGN, 'styles', 'grove');
212
219
  for (const f of ['grove-pod.jpg', 'grove-pod-cut.webp']) {
213
220
  assert.ok(fs.existsSync(path.join(grove, 'assets', f)), `styles/grove/assets/${f} exists`);
214
221
  const side = path.join(grove, 'assets', `${f}.json`);
@@ -360,7 +367,7 @@ test('every in-house skill works inside the world: the style library is the pale
360
367
 
361
368
  test('the preamble copies are policy, not doc-entropy debt: the scanner skips the block and still flags a plain duplicate', () => {
362
369
  const de = require(path.join(ROOT, 'scripts', 'gds', 'docs-entropy.js'));
363
- const carriers = ['modules/ui-design/skills/PREAMBLE.md', ...ownDirs.map((n) => `modules/ui-design/skills/${n}/SKILL.md`)];
370
+ const carriers = ['modules/design-styles/skills/PREAMBLE.md', ...ownDirs.map((n) => `modules/design-styles/skills/${n}/SKILL.md`)];
364
371
  assert.ok(carriers.length >= 2, 'at least one own skill carries the block beside PREAMBLE.md');
365
372
  for (const rel of carriers) assert.ok(read(path.join(ROOT, rel)).includes(policy.preamble.begin), `${rel} carries the block`);
366
373
  const dupes = de.scanDuplicateProse(carriers);
@@ -388,41 +395,42 @@ test('every skill the manifest contributes resolves to a skill dir (core .claude
388
395
  assert.ok(homes.some((h) => fs.existsSync(path.join(h, 'SKILL.md'))), `${name}: declared but no SKILL.md in any home`);
389
396
  }
390
397
  for (const name of [...ownDirs, ...vendoredDirs]) assert.ok(declared.includes(name), `${name}: on disk under the module but not declared in contributes.skills`);
391
- const sources = materialize.moduleSkillSources({ coreRoot: ROOT, instanceDir: ROOT });
398
+ const sources = materialize.moduleSkillSources({ coreRoot: ROOT, instanceDir: ROOT }).filter((s) => s.module === 'design-styles');
392
399
  assert.deepEqual(sources.map((s) => s.name).sort(), [...ownDirs, ...vendoredDirs].sort(), 'the materialiser resolves exactly the module skill dirs on disk');
393
400
  });
394
401
 
395
402
  // ── materialisation: the builder's .claude/skills carries the set, sidecar included ─
396
- test('a module-owned skill lands in a materialised .claude/skills with its sidecar when the module is on, and not when it is off', () => {
403
+ // The module ships default: false (task 1004470), so the shape under test is the real one:
404
+ // ON only when the instance's config/modules.json says so, OFF with no config at all.
405
+ test('a module-owned skill lands in a materialised .claude/skills with its sidecar when the instance turns the module on, and not by default', () => {
397
406
  const core = fs.mkdtempSync(path.join(os.tmpdir(), 'ui-skills-core-'));
398
- const mod = path.join(core, 'modules', 'ui-design');
407
+ const mod = path.join(core, 'modules', 'design-styles');
399
408
  fs.mkdirSync(path.join(mod, 'skills', 'vendor', 'sample'), { recursive: true });
400
409
  fs.mkdirSync(path.join(core, '.claude', 'skills'), { recursive: true });
401
- fs.writeFileSync(path.join(mod, 'module.json'), JSON.stringify({ key: 'ui-design', default: true, contributes: { skills: ['sample'] } }));
410
+ fs.writeFileSync(path.join(mod, 'module.json'), JSON.stringify({ key: 'design-styles', default: false, contributes: { skills: ['sample'] } }));
411
+ const enable = (dir) => { fs.mkdirSync(path.join(dir, 'config'), { recursive: true }); fs.writeFileSync(path.join(dir, 'config', 'modules.json'), JSON.stringify({ modules: { 'design-styles': true } })); return dir; };
402
412
  const pre = read(path.join(SKILLS, 'PREAMBLE.md'));
403
413
  fs.writeFileSync(path.join(mod, 'skills', 'vendor', 'sample', 'SKILL.md'), `---\nname: sample\n---\n\n${pre}\nupstream body\n`);
404
414
  fs.writeFileSync(path.join(mod, 'skills', 'vendor', 'sample', 'PROVENANCE.md'), '---\nname: sample\nlicence: MIT\nscanVerdict: clean\n---\n');
405
415
  fs.writeFileSync(path.join(mod, 'skills', 'vendor', 'sample', 'LICENSE'), 'MIT\n');
406
416
 
407
- const on = fs.mkdtempSync(path.join(os.tmpdir(), 'ui-skills-inst-'));
417
+ const on = enable(fs.mkdtempSync(path.join(os.tmpdir(), 'ui-skills-inst-')));
408
418
  const resOn = materialize.materializeClaude({ coreRoot: core, instanceDir: on, dryRun: false });
409
419
  assert.equal(resOn.moduleSkills, 3);
410
420
  for (const f of ['SKILL.md', 'PROVENANCE.md', 'LICENSE']) assert.ok(fs.existsSync(path.join(on, '.claude', 'skills', 'sample', f)), `materialised ${f}`);
411
421
  assert.ok(read(path.join(on, '.claude', 'skills', 'sample', 'SKILL.md')).includes(policy.preamble.begin), 'the preamble survives materialisation');
412
422
 
413
- const off = fs.mkdtempSync(path.join(os.tmpdir(), 'ui-skills-inst-'));
414
- fs.mkdirSync(path.join(off, 'config'), { recursive: true });
415
- fs.writeFileSync(path.join(off, 'config', 'modules.json'), JSON.stringify({ modules: { 'ui-design': false } }));
423
+ const off = fs.mkdtempSync(path.join(os.tmpdir(), 'ui-skills-inst-')); // no config/modules.json: the module's own default, off
416
424
  const resOff = materialize.materializeClaude({ coreRoot: core, instanceDir: off, dryRun: false });
417
425
  assert.equal(resOff.moduleSkills, 0);
418
- assert.ok(!fs.existsSync(path.join(off, '.claude', 'skills', 'sample')), 'disabling the module withdraws the skill');
426
+ assert.ok(!fs.existsSync(path.join(off, '.claude', 'skills', 'sample')), 'a default-config instance never receives the skill');
419
427
  assert.deepEqual(resOff.excludedSkills, ['sample']);
420
428
 
421
429
  // the policy holds at COPY time too, read from this module's own policy.json: a
422
430
  // dangerous verdict never lands, whatever the CI test would have said
423
431
  fs.copyFileSync(path.join(SKILLS, 'policy.json'), path.join(mod, 'skills', 'policy.json'));
424
432
  fs.writeFileSync(path.join(mod, 'skills', 'vendor', 'sample', 'PROVENANCE.md'), '---\nname: sample\nlicence: MIT\nscanVerdict: dangerous\n---\n');
425
- const refusedInst = fs.mkdtempSync(path.join(os.tmpdir(), 'ui-skills-inst-'));
433
+ const refusedInst = enable(fs.mkdtempSync(path.join(os.tmpdir(), 'ui-skills-inst-')));
426
434
  const resRefused = materialize.materializeClaude({ coreRoot: core, instanceDir: refusedInst, dryRun: false });
427
435
  assert.equal(resRefused.moduleSkills, 0);
428
436
  assert.ok(!fs.existsSync(path.join(refusedInst, '.claude', 'skills', 'sample')), 'a dangerous verdict is refused at copy time');
@@ -438,6 +446,10 @@ test('/design lists every module skill (vendored or in-house) by name, one line
438
446
  assert.ok(line, `/design names ${name} on a line of its own`);
439
447
  }
440
448
  assert.match(design, /playwright/i, '/design names the playwright plugin');
449
+ // task 1004470: the styles are opt-in, so /design must say so plainly when they are absent
450
+ assert.match(design, /`design-styles` module/, '/design names the module the styles come from');
451
+ assert.match(design, /"design-styles": true/, '/design gives the one config line that turns them on');
452
+ assert.match(design, /If `\.claude\/skills\/` has no `impeccable`[^\n]*design-styles[^\n]*off/i, '/design says in plain words that a missing style skill means the module is off');
441
453
  });
442
454
 
443
455
  test('the skills README and the module CLAUDE.md roster every module skill by name', () => {
@@ -445,7 +457,7 @@ test('the skills README and the module CLAUDE.md roster every module skill by na
445
457
  const claude = read(path.join(MODULE, 'CLAUDE.md'));
446
458
  for (const name of [...vendoredDirs, ...ownDirs]) {
447
459
  assert.ok(readme.includes(`\`${name}\``), `README rosters ${name}`);
448
- assert.ok(claude.includes(`\`${name}\``), `modules/ui-design/CLAUDE.md names ${name}`);
460
+ assert.ok(claude.includes(`\`${name}\``), `modules/design-styles/CLAUDE.md names ${name}`);
449
461
  }
450
462
  assert.ok(readme.includes('policy.json') && readme.includes('PREAMBLE.md') && readme.includes('PROVENANCE.md'));
451
463
  assert.ok(readme.includes('Rebuilt from'), 'the README says what a rebuild carries instead of a sidecar');
@@ -1,135 +0,0 @@
1
- ---
2
- name: design-taste-frontend-v1
3
- description: >-
4
- The earlier generation of the taste skill, kept under its own name so a task or builder that asks for it
5
- gets exactly this behaviour while the default (design-taste-frontend) evolves separately. A fixed triple of
6
- dials, the layout bans, full interaction cycles, and a named vocabulary of premium patterns. Triggers on
7
- "design-taste-frontend-v1", "the fixed dials", "the pattern vocabulary".
8
- plain: >-
9
- The earlier version of the landing-page style guide, kept so work that asked for it gets exactly the same results.
10
- reach-for: >-
11
- Only when a task asks for this older version by name.
12
- cost: >-
13
- Uses your session. It changes the page, and you check it on a preview.
14
- ---
15
-
16
- <!-- 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 -->
17
- > **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.
18
- >
19
- > 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.
20
- > 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.
21
- > 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.
22
- > 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.
23
- > 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.
24
- > 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.
25
- > 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.
26
- >
27
- > 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.
28
- <!-- END PLATFORM PREAMBLE -->
29
-
30
- ## Rebuilt from
31
-
32
- 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/0198-third-party-skill-vendoring-policy.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/0220-an-in-house-rebuilt-skill-is-a-first-party-skill.md).
33
-
34
- 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).
35
-
36
- ## Why v1 is kept
37
-
38
- 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.
39
-
40
- ## The baseline triple
41
-
42
- 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.
43
-
44
- 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.
45
-
46
- ## Stack conventions for a served surface
47
-
48
- - **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.
49
- - **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.
50
- - **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.
51
- - **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.
52
- - **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`.
53
-
54
- ## Typography rules
55
-
56
- - **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.
57
- - **Body measure is capped near 65 characters** (`max-width: 62ch` on prose; the chrome world's bands cap at 56 to 62ch).
58
- - **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.
59
- - **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.
60
- - Every real number is `font-variant-numeric: tabular-nums`; a figure that refreshes must not reflow the composition.
61
-
62
- ## Colour rules
63
-
64
- - **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.
65
- - **No purple-to-blue glow aesthetic**, no neon-on-near-black HUD, no gradient text. These are bans whichever pack is on.
66
- - **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.
67
- - 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.
68
-
69
- ## Emoji and icons
70
-
71
- - **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.
72
- - **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.
73
- - An icon is never the only carrier of meaning: the label is the text, the glyph is the pointer.
74
-
75
- ## Layout bans
76
-
77
- - **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.
78
- - **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.
79
- - **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.
80
-
81
- ## Full interaction cycles, not the successful state only
82
-
83
- Every interactive component ships all of its states, and the page's states file (`<page>.states.json`) names them so the kit renders each:
84
-
85
- - **Loading**: a skeleton shaped like the layout it replaces (the same columns, the same row heights, the ground tone `bg-deep`), never a spinner.
86
- - **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.
87
- - **Error**: inline, beside the thing that failed, in the world's `err` state colour on text, never a modal for a field.
88
- - **Press**: tactile feedback on `:active` (`transform: scale(.98)` on the state curve); **focus** with the world's two-tone ring; **hover** on anything pressable.
89
- - Every control clears the 24px floor in both axes, footer links included; primary pills sit at 46 to 48px.
90
-
91
- ## Motion rules
92
-
93
- - **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.
94
- - **Staggered list reveals** are CSS: `animation-delay` from an `--i` custom property set in the markup (`style="--i: 3"`), never a script loop.
95
- - **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).
96
- - Everything is dead under the Kill Switch and the rest frame is the composition.
97
-
98
- ## Performance guardrails
99
-
100
- - Animate **`transform` and `opacity` only** (and `filter` on a plate where the world does). Never a layout property.
101
- - **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.
102
- - **No arbitrary `z-index`.** A named scale on `body` (`--z-bar`, `--z-band`, `--z-dialog`), reserved for systemic layers.
103
- - **Full-height sections use dynamic viewport height** (`100dvh`, or the world's floored stage `max(100dvh, 900px)`), never `100vh`.
104
- - `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).
105
-
106
- ## The vocabulary of premium patterns
107
-
108
- 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:
109
-
110
- - **The two-line display**: the h1 as two lines, the second indented, lowercase where the world's display voice is.
111
- - **The tabular figure**: a real number from the ledger at display weight, tabular, with a caption.
112
- - **The hairline ledger**: rows built from 1px top rules, baseline-aligned columns, no borders, no boxes.
113
- - **The door pill**: two equal actions joined in one glass bar, the primary carrying the accent with the world's `accent-on` ink.
114
- - **The lit rail step**: one step of a pipeline lit in the accent (hairline, dot, name), the rest in ink; exactly one is live.
115
- - **The leader-line callout**: a 1px diagonal line, a figure, a caption; real numbers only, at most two per surface.
116
- - **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.
117
- - **The contact shadow**: the one shadow in a world, and it belongs to the object, not to a component.
118
- - **The hairline band menu**: a disclosure that unfolds as a full-width band under the bar, over the page, closed by one hairline.
119
- - **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.
120
- - **Pill or nothing**: every control a full pill (999px), and the one exception the world names.
121
-
122
- ## The tile-grid archetypes
123
-
124
- Motion-first tile grids, each with its collapse under the world's breakpoint:
125
-
126
- - **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.
127
- - **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.
128
- - **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.
129
- - **The five-column rail**: five beats on one rail, one lit; collapses to one column with the hairlines kept.
130
-
131
- ## Pre-flight (short)
132
-
133
- - [ ] **Mobile collapse** looked at in the 390 and 320 shots; nothing scrolls sideways; every archetype collapsed the way its entry says.
134
- - [ ] **Effect cleanup**: nothing to clean, because nothing moves in script; any live-refresh timer is cleared on navigation.
135
- - [ ] **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`.