@bongos/core 1.19.458 → 1.19.459

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 (35) hide show
  1. package/.bongos-core.json +98 -28
  2. package/.claude/skills/design/SKILL.md +10 -0
  3. package/docs/adr/0219-a-look-is-a-branding-pack-the-style-library.md +30 -0
  4. package/docs/adr/README.md +1 -0
  5. package/docs/branding-contract.md +1 -1
  6. package/docs/file-map.md +1 -1
  7. package/docs/module-api-changelog.md +2 -0
  8. package/docs/recipes/ui-look-before-you-ship.md +2 -0
  9. package/modules/ui-design/kit/README.md +7 -5
  10. package/modules/ui-design/kit/check-mock.js +12 -5
  11. package/modules/ui-design/kit/contrast.js +13 -6
  12. package/modules/ui-design/kit/lib.js +39 -2
  13. package/modules/ui-design/kit/probe.js +4 -1
  14. package/modules/ui-design/kit/render.js +7 -4
  15. package/modules/ui-design/kit/serve.js +9 -2
  16. package/modules/ui-design/styles/README.md +54 -2
  17. package/modules/ui-design/styles/chrome-world/DESIGN.md +56 -0
  18. package/modules/ui-design/styles/chrome-world/mock.html +275 -0
  19. package/modules/ui-design/styles/chrome-world/mock.states.json +11 -0
  20. package/modules/ui-design/styles/chrome-world/pack.json +33 -0
  21. package/modules/ui-design/styles/expedition/DESIGN.md +141 -0
  22. package/modules/ui-design/styles/expedition/mock.html +285 -0
  23. package/modules/ui-design/styles/expedition/mock.states.json +11 -0
  24. package/modules/ui-design/styles/expedition/pack.json +33 -0
  25. package/modules/ui-design/styles/grove/DESIGN.md +148 -0
  26. package/modules/ui-design/styles/grove/mock.html +294 -0
  27. package/modules/ui-design/styles/grove/mock.states.json +11 -0
  28. package/modules/ui-design/styles/grove/pack.json +33 -0
  29. package/package-lock.json +2 -2
  30. package/package.json +1 -1
  31. package/src/module-api.js +1 -1
  32. package/tests/cosmos_token_contrast.mjs +9 -2
  33. package/tests/hall_tokens.mjs +15 -6
  34. package/tests/ui_design_styles.mjs +218 -0
  35. package/tests/world_tokens.mjs +9 -2
package/.bongos-core.json CHANGED
@@ -2,22 +2,22 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.19.458",
6
- "core_contract": "1.19.458",
7
- "source_commit": "154a5282bb349f17f49b47a65e000b0e53c4c7cd",
5
+ "core_version": "1.19.459",
6
+ "core_contract": "1.19.459",
7
+ "source_commit": "72d397abae14ae17bdc5093d55ab28cd31e9da28",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-01T22:33:56.896Z",
9
+ "built_at": "2026-09-01T22:56:06.253Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
- "docs_redacted": 371,
12
+ "docs_redacted": 375,
13
13
  "agent_docs_stubbed": 24,
14
- "functional_verbatim": 1805,
14
+ "functional_verbatim": 1815,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 2200,
20
- "tree_sha256": "70e3303271bd2ec1b48b04c68d830992a02f6a1bb21686ff219a399a03bbe949",
19
+ "file_count": 2214,
20
+ "tree_sha256": "e552b85f10d18fb223b0d8174f2b76d346b0c3e554c071e574493566b8850f32",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -127,7 +127,7 @@
127
127
  {
128
128
  "path": ".claude/skills/design/SKILL.md",
129
129
  "mode": "0000644",
130
- "sha256": "a16c7a31109db8e4e798e419fedd80bb6d258aa1f8b2680ccfd2925fd204e26f"
130
+ "sha256": "456811a0542cad25489519d2b4edda6a712abba284b2dc81a61bd6aa2747e93d"
131
131
  },
132
132
  {
133
133
  "path": ".claude/skills/dev/SKILL.md",
@@ -1599,10 +1599,15 @@
1599
1599
  "mode": "0000644",
1600
1600
  "sha256": "ccc66400a5d2029aa37e9b279887247747b3a119da73c04bf4552379f7c93525"
1601
1601
  },
1602
+ {
1603
+ "path": "docs/adr/0219-a-look-is-a-branding-pack-the-style-library.md",
1604
+ "mode": "0000644",
1605
+ "sha256": "9ae416852dc8112a2cea4837e4a25b29338dbddecee0903830bcf5132645274d"
1606
+ },
1602
1607
  {
1603
1608
  "path": "docs/adr/README.md",
1604
1609
  "mode": "0000644",
1605
- "sha256": "240739ef2027355942c27c471942c64e69db4dd66e96bd6b0132041888540d71"
1610
+ "sha256": "bfae4edac49cb9ff0d29e2e6e275efbc07ecbd668b2375fcbafcbb28f36defa6"
1606
1611
  },
1607
1612
  {
1608
1613
  "path": "docs/api-reference.md",
@@ -1622,7 +1627,7 @@
1622
1627
  {
1623
1628
  "path": "docs/branding-contract.md",
1624
1629
  "mode": "0000644",
1625
- "sha256": "673b1cf2d9ca0f658d59f04b6f972a4e8bbdb6097d7fa3b6b3332f591a195e01"
1630
+ "sha256": "f71af4c488ecddd8636de3bebcefee2d714ff5035a20428e5f853130fc3f8953"
1626
1631
  },
1627
1632
  {
1628
1633
  "path": "docs/branding-fill-prompt.md",
@@ -2082,7 +2087,7 @@
2082
2087
  {
2083
2088
  "path": "docs/file-map.md",
2084
2089
  "mode": "0000644",
2085
- "sha256": "652802b9b37efc2af5f44b9815f5dd6b97ff1bcd6baa20fa5524e4168a7ddd77"
2090
+ "sha256": "d1c5ac472fbd4cf9d4174da9ded7c63427226346cda4b44ae70276c438df909a"
2086
2091
  },
2087
2092
  {
2088
2093
  "path": "docs/handoff-template.md",
@@ -2092,7 +2097,7 @@
2092
2097
  {
2093
2098
  "path": "docs/module-api-changelog.md",
2094
2099
  "mode": "0000644",
2095
- "sha256": "7a7a0d860839e216ab460281ca6aec6280cb6a039fde9dc04b64e9e4e953b26c"
2100
+ "sha256": "9bbd66e7f90015046bd6c0425be541a3c0ce6407ec8628e32a58822cc019af7f"
2096
2101
  },
2097
2102
  {
2098
2103
  "path": "docs/modules-contract.md",
@@ -2322,7 +2327,7 @@
2322
2327
  {
2323
2328
  "path": "docs/recipes/ui-look-before-you-ship.md",
2324
2329
  "mode": "0000644",
2325
- "sha256": "f77d17010edf03a0a6a7adf3966fe73dc8c536cc0f0a34c7d6f887837b96e702"
2330
+ "sha256": "7484993a3437cc5b48841273b50b9015c3c92fbcd9f0f4bddba92d272c58e55f"
2326
2331
  },
2327
2332
  {
2328
2333
  "path": "docs/recipes/upgrading-the-core.md",
@@ -6242,17 +6247,17 @@
6242
6247
  {
6243
6248
  "path": "modules/ui-design/kit/README.md",
6244
6249
  "mode": "0000644",
6245
- "sha256": "850e72f762d80951ed0ad6ca392a5c0619f26c10a58edd0a1d467038b74bbcfa"
6250
+ "sha256": "b2b98a60d054d774d2a30d03285589e780b4ab9bda6f526a971e09dd5500cab1"
6246
6251
  },
6247
6252
  {
6248
6253
  "path": "modules/ui-design/kit/check-mock.js",
6249
6254
  "mode": "0000644",
6250
- "sha256": "9f38941d23b498feeac12f6f32a64d5f3c7749e1b6e01db7bf83dedd3cc2e549"
6255
+ "sha256": "76c97faa8f04d51900ca00cb5f8a599d105ee44347bc8f285013f1439f6483f2"
6251
6256
  },
6252
6257
  {
6253
6258
  "path": "modules/ui-design/kit/contrast.js",
6254
6259
  "mode": "0000644",
6255
- "sha256": "239cc33a7896dbbe0bab25098904b2be8298ff5c440b7e2a44594e131d21ed9c"
6260
+ "sha256": "a73c88307fa737a022dbc9082da1163df3ebd7cbe68fdc757ca3662bc6d7091f"
6256
6261
  },
6257
6262
  {
6258
6263
  "path": "modules/ui-design/kit/fixtures/cost-summary.json",
@@ -6307,7 +6312,7 @@
6307
6312
  {
6308
6313
  "path": "modules/ui-design/kit/lib.js",
6309
6314
  "mode": "0000644",
6310
- "sha256": "4a9463c60ef81c50a031fbe67dedc550807ad44002c01b2aa368bc9166d01389"
6315
+ "sha256": "c2e6471bf9623a2dbf3a816c355051315309221a03476f4f199f825bcef070f6"
6311
6316
  },
6312
6317
  {
6313
6318
  "path": "modules/ui-design/kit/png.js",
@@ -6317,17 +6322,17 @@
6317
6322
  {
6318
6323
  "path": "modules/ui-design/kit/probe.js",
6319
6324
  "mode": "0000644",
6320
- "sha256": "7072b2238e8c89b408de3e5e8e0356d921b3f76a02620d1914713a0a37699ec9"
6325
+ "sha256": "271ba1814a120a3ac6633c5c5c1ee528f57b8ce13a03b454b2c00369f3849ec1"
6321
6326
  },
6322
6327
  {
6323
6328
  "path": "modules/ui-design/kit/render.js",
6324
6329
  "mode": "0000644",
6325
- "sha256": "5688c326efc893392ff696f278f63c9f88430692467dfdc239589b8779ec4d86"
6330
+ "sha256": "9940f65e99d822c428ae8c76128752dbf55efb664ea40480bb098b9652e87da8"
6326
6331
  },
6327
6332
  {
6328
6333
  "path": "modules/ui-design/kit/serve.js",
6329
6334
  "mode": "0000644",
6330
- "sha256": "011ecc07a737d9bf3d389f344e9fc795a6889b35aaeb0895048b3d1cc5e6a6b3"
6335
+ "sha256": "745fdc2ff5f72951b919ba49ab871b8cdbe0f09db73e79dfa248cd01e9232cdd"
6331
6336
  },
6332
6337
  {
6333
6338
  "path": "modules/ui-design/module.json",
@@ -6367,17 +6372,77 @@
6367
6372
  {
6368
6373
  "path": "modules/ui-design/styles/README.md",
6369
6374
  "mode": "0000644",
6370
- "sha256": "5f98022a5edc4c1b405823cbf8ba8da745f60c3d47a7cb59eba537edf9bf29be"
6375
+ "sha256": "b16cc6da5e9ce518c50a8ef8cc8fa153a11233e24e41c7c036cd5f0f08435d76"
6376
+ },
6377
+ {
6378
+ "path": "modules/ui-design/styles/chrome-world/DESIGN.md",
6379
+ "mode": "0000644",
6380
+ "sha256": "c26f3c754a79997a66b4e4a2f3b00573f2c36805dec2be1ecf65b3c0f90e42c6"
6381
+ },
6382
+ {
6383
+ "path": "modules/ui-design/styles/chrome-world/mock.html",
6384
+ "mode": "0000644",
6385
+ "sha256": "723b7551d938cc04aeb769069ea5cf2cbf5f2980069bc95c68d0a92dc5e1c51a"
6386
+ },
6387
+ {
6388
+ "path": "modules/ui-design/styles/chrome-world/mock.states.json",
6389
+ "mode": "0000644",
6390
+ "sha256": "7d0446408aa8f44d246618f310945825c650592b944fd08230bafa690123fbdd"
6391
+ },
6392
+ {
6393
+ "path": "modules/ui-design/styles/chrome-world/pack.json",
6394
+ "mode": "0000644",
6395
+ "sha256": "326aae0bafe9a579643a72510c7a3274548c4ddcc301e50bb9ae91af5100f4cc"
6396
+ },
6397
+ {
6398
+ "path": "modules/ui-design/styles/expedition/DESIGN.md",
6399
+ "mode": "0000644",
6400
+ "sha256": "dea5d034e7c09ff94f29a2b1755f8ff49babe95d82c6a2b0f0d5b5330f673b40"
6401
+ },
6402
+ {
6403
+ "path": "modules/ui-design/styles/expedition/mock.html",
6404
+ "mode": "0000644",
6405
+ "sha256": "baeccde1879cfb781ddf8da40f0080352fe6a2c1f015b1c8bbfe8fad49f1b6d0"
6406
+ },
6407
+ {
6408
+ "path": "modules/ui-design/styles/expedition/mock.states.json",
6409
+ "mode": "0000644",
6410
+ "sha256": "62a274fedc431a39baba782217d3fb8c3eca13e2a14d0986535a05029af05fad"
6411
+ },
6412
+ {
6413
+ "path": "modules/ui-design/styles/expedition/pack.json",
6414
+ "mode": "0000644",
6415
+ "sha256": "2d8edc18efe573834e05d51450ff44088140f7e2dbe2607f1227f80c676cc92a"
6416
+ },
6417
+ {
6418
+ "path": "modules/ui-design/styles/grove/DESIGN.md",
6419
+ "mode": "0000644",
6420
+ "sha256": "f88f3ff4b0475767f63d4665bdb07caeb36550e3bd8a6c66aebc7fdc1b657887"
6421
+ },
6422
+ {
6423
+ "path": "modules/ui-design/styles/grove/mock.html",
6424
+ "mode": "0000644",
6425
+ "sha256": "ee2fdbe6a16ff00be4986e8c30a3d0fb37a8a077ad83a039610b22f2d20ce835"
6426
+ },
6427
+ {
6428
+ "path": "modules/ui-design/styles/grove/mock.states.json",
6429
+ "mode": "0000644",
6430
+ "sha256": "febbef7118d155f82eaadc4b31f6ac9e3f3c8c0b6e9ee42db950f025e89b4709"
6431
+ },
6432
+ {
6433
+ "path": "modules/ui-design/styles/grove/pack.json",
6434
+ "mode": "0000644",
6435
+ "sha256": "6e3c1dbc57544f82d23d35c4d3500ee7bedc654fa8364f531a33945c0354c8d1"
6371
6436
  },
6372
6437
  {
6373
6438
  "path": "package-lock.json",
6374
6439
  "mode": "0000644",
6375
- "sha256": "e9359e01cdc7a671143890a3f0c6ac1d061b54820f8f8baf280b16407f717e4c"
6440
+ "sha256": "be8dd327cc496a206929ea34a1dc82ab7347151e87770cf5f8a73ef21ea43d7e"
6376
6441
  },
6377
6442
  {
6378
6443
  "path": "package.json",
6379
6444
  "mode": "0000644",
6380
- "sha256": "2244a6b4591b18b491f66d9bfdb121ce1bf6cbe5b1e4ee85e132da226e97ee78"
6445
+ "sha256": "b6c8b24f909aa711121d3abd9497604caf0043784838213b12079f17b45ef62c"
6381
6446
  },
6382
6447
  {
6383
6448
  "path": "public-docs/index.html",
@@ -8027,7 +8092,7 @@
8027
8092
  {
8028
8093
  "path": "src/module-api.js",
8029
8094
  "mode": "0000644",
8030
- "sha256": "9d8316b9f99b85f031950b1177faff0468a97285eeb164df28990255fc552b04"
8095
+ "sha256": "ff4b86268cf0e0b643ffa0f0007d6af4c2aa65fcb5758964206bda3cbc56db5b"
8031
8096
  },
8032
8097
  {
8033
8098
  "path": "src/module-loader/catalog.js",
@@ -8652,7 +8717,7 @@
8652
8717
  {
8653
8718
  "path": "tests/cosmos_token_contrast.mjs",
8654
8719
  "mode": "0000644",
8655
- "sha256": "7d5499379c31698cd0de1bcdd9315db3a32ba35f6d6e749a6b5f6bbcdf588296"
8720
+ "sha256": "960896c8560e4a3ee3420ffc87473515371300c17995f24e60dbd7f7e42c1898"
8656
8721
  },
8657
8722
  {
8658
8723
  "path": "tests/cost.mjs",
@@ -9447,7 +9512,7 @@
9447
9512
  {
9448
9513
  "path": "tests/hall_tokens.mjs",
9449
9514
  "mode": "0000644",
9450
- "sha256": "1fb4b62b191fa7ba328f920859c4add9016acccb70a0da597fd38a4be5c314bb"
9515
+ "sha256": "a1da5c5c009acd1286d1f0862c9c57c29b80e5dd6f2f1e3effb459f849b38740"
9451
9516
  },
9452
9517
  {
9453
9518
  "path": "tests/handle_machinery.mjs",
@@ -10939,6 +11004,11 @@
10939
11004
  "mode": "0000644",
10940
11005
  "sha256": "d43f67840668875a9829d741f661e344a27675704834cd6a5e6b9e3247164068"
10941
11006
  },
11007
+ {
11008
+ "path": "tests/ui_design_styles.mjs",
11009
+ "mode": "0000644",
11010
+ "sha256": "a2ab80b7fb38695bb65c617eff1c39cfbe17403b1586cb1b20e3dee50e5a23fa"
11011
+ },
10942
11012
  {
10943
11013
  "path": "tests/update_channel.mjs",
10944
11014
  "mode": "0000644",
@@ -11012,7 +11082,7 @@
11012
11082
  {
11013
11083
  "path": "tests/world_tokens.mjs",
11014
11084
  "mode": "0000644",
11015
- "sha256": "ca898c5398dbb9b35baefbf53b406bea2a9dbee5b3986f7ab8fab3944d557ace"
11085
+ "sha256": "7be885e9fa066a41a1d7cfb936c5dc270ff9a50c7c4456c3a7efea18363eb392"
11016
11086
  },
11017
11087
  {
11018
11088
  "path": "tests/write_validation_gaps.mjs",
@@ -63,6 +63,16 @@ Third-party design skills reach a builder only through the module's vendoring po
63
63
 
64
64
  **Prerequisite, not vendored:** the **playwright plugin** (`claude plugin install playwright@claude-plugins-official`) is the LIVE browser-driving loop, where the kit in Step 2 is the headless one. Install it on the machine that runs a design session; the module never installs anything.
65
65
 
66
+ ## The style library (the ui-design module's `styles/`)
67
+
68
+ A **look** is a branding pack, never a sheet ([`modules/ui-design/styles/README.md`](../../../modules/ui-design/styles/README.md), ADR 0219): the thirteen colours and the two font stacks of `theme.ui`, a `DESIGN.md` of the look's derived recipes, and one specimen mock. Every look is measured clean against every pair the landing's, the hub's and the hall's sheets paint, in both modes, before it is listed here. Render any surface under any look with `STYLE=<name>` on the stub or `--style <name>` on `render.js` / `probe.js`; `contrast.js --pack <name>` is the pack floor. An instance adopts a look by copying its `pack.json` `theme` into `config/branding.json`; dark stays the sheets' black and the hall's status vocabulary stays the platform's whichever look is on.
69
+
70
+ - `chrome-world` — the canonical entry: one iridescent chrome sculpture in a black void, pale grey paper in light, one warm salmon. Pinned to the neutral pack; its recipes are the root `DESIGN.md`.
71
+ - `expedition` — a printed field journal: warm paper under a halftone screen, hairline frames, big heavy uppercase Archivo headlines, one lamp yellow as a fill only.
72
+ - `grove` — a sculpted paper garden: pale sage, soft corners, tinted shadows, the sea green as a slab, the tan as the one lit thing, Nunito.
73
+
74
+ A look that is not listed here is not in the library. Adding one is the README's five steps under a claimed task; a look that needs a sixteenth token is a contract change and an ADR, not a style.
75
+
66
76
  ## Stay clear of
67
77
 
68
78
  - The pack's neutral values and `DESIGN.md`'s named rules — changing the world is the owner's decision and an ADR, not a session's taste.
@@ -0,0 +1,30 @@
1
+ # ADR 0219 — A look is a branding pack: the style library ships looks as `theme.ui` overlays, never as sheets
2
+
3
+ - **Status:** accepted
4
+ - **Date:** 2026-09-01
5
+ - **Task:** [task 1003323](https://cloudbongos.com/builders#/task/1003323) (goal 1000083 — the ui-design module as a platform capability; criterion `uidesign-style-library`)
6
+ - **Relates to:** [ADR 0062 §3/§7](<redacted>.md) (the branding contract; the core ↔ host split), [ADR 0196](<redacted>.md) (the chrome world becomes the token contract), [ADR 0197](<redacted>.md) (the module; `styles/` reserved), [ADR 0204](<redacted>.md), `modules/ui-design/styles/README.md`, `DESIGN.md`
7
+
8
+ ## Context
9
+
10
+ The owner scoped the ui-design module with "varying ui styles": a look a builder or an instance owner picks for a new surface or a whole instance, with Cloud Bongos's chrome world as the first entry and the world round's other candidates (`docs/design/mocks/world/`) as the alternates. Three facts decided the shape:
11
+
12
+ 1. **Every served sheet already reads only the contract.** Since ADR 0196 the landing's `world.css`, the hub's `cosmos.css` and the hall's `style.css` open on a bare `:root` equal to the neutral pack, derive everything else, and write dark twice at (0,2,0); `tests/world_tokens.mjs`, `tests/cosmos_token_contrast.mjs` and `tests/hall_tokens.mjs` measure every pair they paint by resolving the sheet's own expressions with the pack's thirteen substituted. A look that changes the thirteen is therefore already applied to every surface at once, with no page edit, and can be **measured** before anyone sees it.
13
+ 2. **The pack is one-mode.** `themeMarkup()` emits light values; dark is each sheet's own black tier, and the Unpinned Accent Rule (ADR 0196) is what lets a re-themed instance reach it. So a look can carry a light ground, an accent and two faces, and cannot carry a dark ground. The world round's candidates each had one (`natgeo-editorial` a near-black, `organic-cards` a deep green); those do not travel.
14
+ 3. **The candidates' mock token layers were sheets, not packs.** Each `_tokens.css` carried sixty-odd derived tokens and page composition. Copying one beside `world.css` would have been a fork: two sheets to keep in lockstep with every drift test, and a look that silently repainted the hall's status chips (the collision ADR 0196 Context §2 records).
15
+
16
+ ## Decision
17
+
18
+ 1. **A look is a branding pack overlay and three files.** `modules/ui-design/styles/<name>/` holds `pack.json` (`{ name, title, summary, origin, theme: { ui: { colors: <exactly the thirteen>, fonts: { display, body, links } } } }`, the `theme.ui` block and nothing else: favicon and backdrop stay the instance's), `DESIGN.md` (the look's derived recipes in the frontmatter shape the root `DESIGN.md` uses, its thirteen held in lockstep with the pack by test, and prose in the same sections) and `mock.html` (a specimen that declares no `:root` and no colour of its own, tokens on `body`, dark written twice, served through the kit's stub with the pack spliced in). **Never a fork of a sheet**: a style that needs a sixteenth token is a contract change and an ADR, not a style.
19
+ 2. **The chrome world is the canonical entry** and its `pack.json` is pinned byte-for-byte to `config/branding.neutral.json`'s `theme.ui`; its `DESIGN.md` points at the root `DESIGN.md` rather than restating it, so the world has one record.
20
+ 3. **The two alternates are ported by retuning the thirteen against the sheets, never the sheets against the thirteen.** `expedition` (from `natgeo-editorial`) and `grove` (from `organic-cards`) were laid through the three sheets' own pair lists in both modes and tuned until every pair cleared AA: the grove's sage ground lifted (`#c8d7bf` → `#e1eadb`) and its inks deepened so the hub's fixed status vocabulary and the hall's chips cleared; the expedition's `ink-faint` deepened one step. The retune is recorded in each pack's `origin` line. Dark under both is the sheets' black, reached through the accent and the faces.
21
+ 4. **The picker rides the knob that already exists.** `STYLE=<name>` on the kit's stub (and `--style <name>` on `render.js` / `probe.js`, `--pack <name>` on `contrast.js` / `check-mock.js`) writes the instance's own pack with the look's `theme.ui` laid over it to a temp file and hands the core-side transform `GDS_BRANDING_FILE`, the fixed environment knob `src/branding.js` already has for running one checkout as two instances. The instance's identity strings stay; only the fifteen change. No core file changes for the picker.
22
+ 5. **The three token suites become parametrisable, not duplicated.** `UI_DESIGN_PACK=<pack.json>` makes `world_tokens`, `cosmos_token_contrast` and `hall_tokens` measure under that pack; their drift tests keep comparing each sheet against the **neutral** pack, which is what a sheet must equal. `tests/ui_design_styles.mjs` spawns the three once per alternate and proves the knob is live by running a deliberately thinned pack that must fail. Rejected: a fourth copy of the resolver in a kit module (the pair lists are the surfaces' own paint and would drift), and importing from the test files (a test file is not a module; one file's failure would silence another's).
23
+ 6. **The instance-side switch is the contract, documented, not a new mechanism.** An instance adopts a look by copying the pack's `theme` into `config/branding.json` (the file `cloudbongos init` writes) and leaves it by removing the block. What stays the platform's whichever look is on: the dark ground, the hall's green / amber / red verification vocabulary and the status ledger's pillars, every derived recipe a sheet carries (a radius, a shadow, a glass are a sheet's tier), and the platform floors.
24
+
25
+ ## Consequences
26
+
27
+ - A builder on any instance renders any surface under any look with one environment variable, and a look cannot ship unless every pair three real sheets paint clears AA under it in both modes; the failing pair is named by the suite that owns it.
28
+ - The library's alternates are looks in the contract's sense: a light palette, an accent, two faces. A world with its own dark ground, its own radii or its own materials is a **sheet** decision (a surface's derived tier) or a contract change, and the README says so, so a future "style" that is really a sheet is refused at the door rather than merged.
29
+ - The world round's candidate mocks stay where they are as the record of the round; the library carries only what a pack can.
30
+ - Rejected: shipping the alternates as CSS sheets beside `world.css` (a fork per look, and the status-chip repaint); extending the contract with a dark tier so a look can carry its own black (a contract change touching every sheet, every drift test and `themeMarkup()`, for a benefit the owner did not ask for); a picker that edits `config/branding.json` in place (the kit must never write the host boundary).
@@ -305,5 +305,6 @@ This keeps the decision history honest and traceable.
305
305
  | 0216 | [**A fake-pool interpreter must FAIL LOUDLY on what it cannot model; the refusal is uniform, the interpreters stay separate** ([task 1003354](https://cloudbongos.com/builders#/task/1003354), folding in idea 1000877 from the adversarial review of [task 1002968](https://cloudbongos.com/builders#/task/1002968)). Nine test files carry a **fake pool** whose row filter is derived from each statement’s own WHERE text — the design that makes an isolation proof say something about the real query rather than about a canned response. It has one silent failure mode: a term the interpreter does not recognise is simply not applied, so the boundary goes vacuous and every assertion above it keeps passing. Each file guarded a piece of that (unread `$N`, unknown literal, unmodelled `IS NULL`) and every one of those is a **term** guard — and the term list is not the predicate. Three reviews found the same shape. **The missing term:** ADR 0213 made consent two columns and `scouting_owner_access_and_reach.mjs` modelled only the first; a NULL test is a third syntax neither sweep can see, so the suite reported 17 passed while modelling a predicate the database no longer had, inside a file whose own header calls that impossible. **The rewritten joiner:** an interpreter that COLLECTS recognised terms and ANDs them re-imposes an `AND` the SQL may no longer contain — rewrite a predicate’s `AND` to `OR` and Postgres serves every active account on the platform, or lets any builder revoke any session by id, while the fake answers exactly as before. **Recognition is not application:** ADR 0213’s own first draft checked only that a column name was recognised, and a mutation deleting the filter still passed. Decisions: **D1** the governing rule — an interpreter that cannot model a term must REFUSE the statement, never ignore the term; incompleteness is allowed, silence is not, because a refusal fails the build in the session that changed the SQL while a silent drop fails nothing ever. **D2** the mechanism is a CONSUMING parse: each recognised term is spliced out of the text and the residue must be a pure conjunction (`/^[\s()]*(?:AND[\s()]*)*$/`), which subsumes every term guard, catches `OR`/`NOT`/`CASE`, and retires ADR 0213’s `modelled`-set bookkeeping because an unapplied term is by construction still there; a legitimate `OR` (the rename window, the either-side clause, the expiry disjunction, the rescind guard) is consumed as a whole unit. **D3** the interpreters stay SEPARATE and only the refusal is uniform — the term vocabulary differs completely across six tables, so a shared registry would be a small SQL engine and would couple unrelated proofs; instead every copy carries the identical guard and its OWN calibration test, which is strictly better anti-drift than extraction (a helper proves the guard exists somewhere; a calibration test proves it is reached by that file’s own statements). **D4** a modelled term proves nothing until a fixture makes it observable — three fixtures added, each differing from its control in exactly one column, so the mutation that deletes the term has somewhere to land; where a file deliberately lacks one because another owns that matrix, the comment says so. **D5** every copy verified BY MUTATION against the real source — ten mutations, ten reds, each a 0-failure pass beforehand. ⚠ Honest limits: no fitness check asserts a NEW fake pool carries the guard (identifying “a fake pool” needs a heuristic, and a heuristic gate over test internals has a false-positive cost) — the per-file calibration tests hold the nine that exist and a tenth is a review question; `canonical_profile_url.mjs` is untouched because its residue must be EMPTY, strictly stronger already. Rejected: one shared interpreter module; a fitness heuristic; keeping `KNOWN_NULL_TESTS` + `modelled` beside the structure guard; parsing the boolean tree properly; accepting green as verification.](<redacted>.md) | testing / proof integrity |
306
306
  | 0217 | [**Rank gates INVITING, not VIEWING: the owner’s answers to the account-security review’s five blockers** ([task 1003358](https://cloudbongos.com/builders#/task/1003358) · goal 1000045; answers blockers 1000121/1000122/1000123/1000124/1000126). The 2026-08-29 account-security batch shipped fourteen tasks and deliberately refused five audience/consent questions. Four turned out to be the same question, and one answer reframed the rest. Decisions: **D1** the governing line — *rank gates the ability to INVITE; it does not gate the ability to VIEW*. Seeing what someone built across projects is not privileged (the T&C already promises that data is public by consent, to search engines included); reaching out to them costs them something, and that is where a floor belongs. So `membership_kind` is **NOT** widened — `membershipKind()` stays FIRST_ADMIN-only, the federated wire contract is unchanged, the My Projects owned/building split keeps its meaning, the cross-version window never opens, and task 1003350 is **abandoned**. A **per-project INVITE rank floor defaulting to archon** replaces it (task 1003362), set by that project’s own archons — strictly better than a founder requesting a platform-side `project.curate` grant per teammate. Viewing gains no gate, on a new cross-project achievements page that per the owner **lives on cloudbongos.com, not the hall variant** (task 1003363) — so `public-landing/` inside the apex-only region, not `modules/hall-ui/`. The risk to hold as it is built: public consented profile data and a PRIVATE account’s recruiter sliver are different material, and the sliver must not ride along — not as a row, a facet count, or a search-result absence that reveals presence. **D2** the sliver keeps its **archon floor** (blocker 1000126), and ADR 0215 D4’s defensive hold becomes a positive agreement: the invite floor also defaults to archon, and the C5 tension dissolves because C5 was about browsing. Whoever builds 1003362 must check whether it IS the console wave’s `project.recruit` atom before shipping a rival knob. **D3** the consent pair — `/terms` said acceptance is recorded “by signing in here” but the stamp is reachable only via `/sso/authorize`, which a CHILD PROJECT reaches, so a hub-only builder was never stamped and (NULL `terms_accepted_at` → `isAccountActive` false) was invisible everywhere, permanently, under no-backfill. Owner took **both halves**: correct the sentence now (task 1003353) and **capture at the hub’s own sign-in** next (task 1003361), through the `platform-identity.hub` seam so child instances see no change. On grandfathering the owner chose **B — grandfather** (task 1003360) against the recommendation, having been shown that it records a consent which did not happen; the ledger does not absorb that silently, so a grandfathered acceptance is **marked** (queryably distinct from a sign-in, via a recorded source not a magic timestamp) and `/terms` **says so**. ADR 0213’s honest property is not “every acceptance was a sign-in” but **the record never claims more than what happened**, and that survives. **D4** `/access-requests/status` **collapses** to `admitted` + one `status` of `member`|`invited`|`not_admitted` (task 1003359, the fix ADR 0209 already named). It does **not** close the oracle and must not claim to — `admitted:true` still means member-or-invited; the 1003339 limiter is what reduces enumeration, and collapsing narrows what leaks about **applicants**. The cost is paid in the same change: `login.js` is the ONLY place telling a declined applicant they were declined, so its 30-minute timeout copy is rewritten to be honest rather than promising an approval that is never coming. ⚠ Also: report 1000027 (the live SSO check-in identity hole) marked **fixed**, held open until now so the ledger would not claim a fix while the hole still served; `verifyHubAssertion`’s purpose-confusion weakness filed as report **1000028** (medium, latent — zero call sites, becomes an admission bypass the moment ADR 0201 D2.1’s vouch is minted, so the fix must land BEFORE the feature that arms it). **New invariant:** any gate added to a viewing surface, or any recruiting/invite surface below the archon floor, contradicts this ADR and needs one that supersedes it.](<redacted>.md) | platform identity / privacy / permissions |
307
307
  | 0218 | [**The apex-root web surface: a module may own the front door, and says so with a flag, not an empty prefix** ([task 1003322](https://cloudbongos.com/builders#/task/1003322) · goal 1000080 — the last of the five tasks scoped with the owner on 2026-08-28). Carves the repo-root `public-landing/` into `modules/public-landing/`, the THIRD web-surface module after status-UI (BV1.R84) and hall-UI (BV1.R85) and the first that is not a subdomain. The obstacle: `moduleWebSurfaces()`'s whole routing vocabulary was a Host-header PREFIX, and the apex root is not a prefix of anything — it is whatever is left once the subdomains are taken. So a surface now names where it answers in exactly one of two ways, `{ host: "status.", dir }` or `{ apexRoot: true, dir }`, never both and never neither. **`host: ""` is rejected on purpose:** the routers match with `host.startsWith(s.host)` and every string starts with `""`, so an empty prefix would answer on `builders.`/`status.` too AND make `isModuleSurfaceHost()` true for every request — the predicate eleven `/builders` guards use to step aside — silently un-serving the hall. A boolean cannot be mistaken for a prefix. The landing keeps its DEDICATED serving block (the hall precedent): the apex-only strip, the branding stamp, the `?v=` content stamp and the six routes are untouched; only `LANDING_DIR` changes, from a hardcoded path to a lookup through the loader, resolved by the `apexRoot` PREDICATE so neither `platform-server.js` nor `serve-internal.js` names a module key (ADR 0083 §Decision #4). Disabling the module removes the front door outright and the apex answers the game-less 404 tail — the module contract's "disable without breaking anything" in its most literal form. At most one apex-root surface: the first discovered wins and a second is logged. Also: the BV1.R76 host-identity gate learned that an **apex-only region is exempt** — the server deletes it on every non-hub instance, so it cannot make a child render the hub's brand, which is the failure that gate names. Evidence: the five apex pages hashed as served, before and after, differ by exactly the same 22 bytes — `"public-landing":true,`, the module's own `window.__MODULES__` entry — and are otherwise identical; the 106 moved files hash identically on disk.](<redacted>.md) |
308
+ | 0219 | [**A look is a branding pack: the style library ships looks as `theme.ui` overlays, never as sheets** ([task 1003323](https://cloudbongos.com/builders#/task/1003323) · goal 1000083). The owner scoped "varying ui styles" for the ui-design module; the shape was decided by three facts: every served sheet already reads only the contract and is measured by a suite that resolves its own expressions under the pack (ADR 0196), the pack is one-mode (light values; dark is each sheet's black tier), and the world round's candidate token layers were sheets, not packs. Decisions: **D1** a look is `modules/ui-design/styles/<name>/` = `pack.json` (the `theme.ui` overlay, exactly the thirteen colours + the two font stacks and their links) + `DESIGN.md` (the derived recipes in the root DESIGN.md's frontmatter shape, thirteen in lockstep with the pack) + `mock.html` (a specimen with no `:root`, served through the stub with the pack spliced in), never a fork of a sheet; a sixteenth token is a contract change. **D2** `chrome-world` is canonical and pinned byte-for-byte to the neutral pack; its DESIGN.md points at the root one. **D3** the alternates `expedition` (natgeo-editorial) and `grove` (organic-cards) are ported by retuning the thirteen against the three sheets' own pair lists in both modes until every pair clears AA (the grove's sage lifted and its inks deepened for the hub's status vocabulary and the hall's chips); dark under both is the sheets' black. **D4** the picker rides `GDS_BRANDING_FILE`, the fixed knob `src/branding.js` already has: `STYLE=<name>` on the stub, `--style` on render/probe, `--pack <name>` on contrast/check-mock; no core change. **D5** the three token suites take `UI_DESIGN_PACK` and their drift tests keep measuring against the neutral pack; `tests/ui_design_styles.mjs` runs them once per alternate and proves the knob live with a pack that must fail. **D6** an instance adopts a look by copying `theme` into `config/branding.json`; what stays the platform's whichever look is on: the dark ground, the hall's verification vocabulary, every sheet's derived tier, the floors. Rejected: sheets per look beside world.css; a dark tier in the contract; a picker that writes the host boundary.](<redacted>.md) | ui-design / branding contract |
308
309
 
309
310
  > ⚠️ **Numbering collisions are now machine-enforced, not narrated.** Twenty numbers are shared by two ADRs each — assigned in parallel sessions before anything checked. The files keep their filenames (renumbering would break every existing citation, and a number once assigned is never reused), and this table disambiguates each pair as `NNNN-a` / `NNNN-b`. The authoritative list is `LEGACY_DUPLICATE_ADRS` in [`scripts/gds/adr-namespace.js`](../../scripts/gds/adr-namespace.js), frozen by exact filename: a **new** collision — or a third file joining a legacy number, or a rename of either half — hard-fails the `unit` CI gate, as does an index row that points at no file or an ADR with no row. The list may only shrink. This paragraph used to narrate the collisions one by one and had fallen eight behind reality; see [ADR 0195](<redacted>.md) for why the check exists and why the pairs are grandfathered rather than renumbered.
@@ -39,7 +39,7 @@ env override → instance config (config/branding.json) → neutral star
39
39
  | `reward` | `{ marginPct, usdPeg, mode }` — the per-instance reward POLICY the economy module reads ([ADR 0054](adr/<redacted>.md) + ADR 0146, tasks 2009/1002270): the cost-plus margin percent (default `20`), the example-per-USD peg (default `1`), and `mode` — `"cost-plus-and-estimate"` (default; pay both the per-task author estimate AND the cost-plus session reward) or `"cost-plus-only"` (suppress the per-task estimate payout; the cost-plus session reward is the sole equity reward). A committed, code-reviewed change — not a live UI knob. Omit ⇒ code defaults (20% margin, 1:1 peg, pay both streams). |
40
40
  | `models` | `{ main, subagentDefault, subagentRoutine }` — the per-instance subagent model-allocation DEFAULT the builder-settings module reads (task 2011): the tier for the main session, subagent-default work, and routine plumbing (tiers: `haiku`/`sonnet`/`opus`/`fable`). A per-builder override resolves over this. Omit ⇒ code defaults (`opus`/`opus`/`haiku`). |
41
41
  | `tracks` | The configurable version-track set (replaces the hardcoded `product\|internal`). |
42
- | `theme` | `{ palette, mode, ui }` — palette file ref + theme mode, plus `ui`, the WEB look `themeMarkup()` splices into every served page as a plain `:root` after the page's stylesheet: `colors` (the thirteen contract tokens — **light-mode values**; each stylesheet derives its own dark tier from them at `:root[data-mode="dark"]` + its `prefers-color-scheme` twin, which outrank the pack), `fonts` (`display`, `body`, `links`), `favicon`, and `backdrop` — a `body{background:…}` rule that outranks every sheet in **both** modes, so a dual-mode world sets it to `var(--bg)` (the neutral pack does since [task 1003298](https://cloudbongos.com/builders#/task/1003298), ADR 0196) rather than a literal. The neutral values are the chrome world's; `config/design-tokens.neutral.json` must mirror `colors` + `fonts` key for key (`tests/design_tokens_sync.mjs`), and the world's derived recipes are recorded in `DESIGN.md`. |
42
+ | `theme` | `{ palette, mode, ui }` — palette file ref + theme mode, plus `ui`, the WEB look `themeMarkup()` splices into every served page as a plain `:root` after the page's stylesheet: `colors` (the thirteen contract tokens — **light-mode values**; each stylesheet derives its own dark tier from them at `:root[data-mode="dark"]` + its `prefers-color-scheme` twin, which outrank the pack), `fonts` (`display`, `body`, `links`), `favicon`, and `backdrop` — a `body{background:…}` rule that outranks every sheet in **both** modes, so a dual-mode world sets it to `var(--bg)` (the neutral pack does since [task 1003298](https://cloudbongos.com/builders#/task/1003298), ADR 0196) rather than a literal. The neutral values are the chrome world's; `config/design-tokens.neutral.json` must mirror `colors` + `fonts` key for key (`tests/design_tokens_sync.mjs`), and the world's derived recipes are recorded in `DESIGN.md`. The ui-design module's **style library** (`modules/ui-design/styles/`, [ADR 0219](adr/<redacted>.md)) ships ready-made `theme.ui` blocks (looks) an instance copies into this field; every served sheet reads only these fifteen, so a look re-themes every surface with no page edit. |
43
43
  | `copy` | Instance copy keys (tagline, hallName, notFound, …). Full copy pack adoption = task [#1196](https://example.com/builders#/task/1196). |
44
44
  | `landing.postLogin` | Where a successful web sign-in lands when it carried no explicit `?return=` / same-apex `Referer` (`src/bongos/routes/auth.js` `postLoginLanding`). **Portable default `/builders`** — the hall always exists. A platform-home instance points this at its OWN front door so a visitor who signs in from there returns there instead of being auto-redirected into the hall (cloudbongos.com → `/gate/`, the orbs). Validated through the same open-redirect allowlist as `?return=` (a misconfigured value falls back to `/builders`). Env-overridable: `<PREFIX>_POST_LOGIN_LANDING`. (task [#1002240](https://cloudbongos.com/builders#/task/1002240)) |
45
45
  | `envPrefix` | Env-var namespace for overrides (`OTB`, `CLOUDBONGOS`, …). |
package/docs/file-map.md CHANGED
@@ -130,7 +130,7 @@
130
130
  │ ├── hall-ui/ ← CORE-DOMAIN WEB-SURFACE carve (BV1.R85, default-on): the authenticated builders' hall at builders.<apex>, moved from public-builders/ (~13k LOC, the largest). NO routes/db/port — declares contributes.webSurfaces:[{host:"builders.",dir:"public"}] for discovery, but UNLIKE status-ui keeps its DEDICATED serving block in serve-internal (the /builders shim + canonical redirects + citizen/Archon page gates + asset-stamping + hall-widget injection), partitioned out of the generic web-surface loop and served from BUILDERS_DIR. CLAUDE.md, module.json, public/{index.html,builders.js,style.css,work,watch,harbor,gate,pair,settings,ranks,sessions,primer,diagrams,atlas,...}.{html,js},cursors/,chime.wav. Three ARCHETYPE sheets sit beside the per-page ones: `record.css` (the task + idea records, task 1003305), `room.css` (the READING ROOMS — the primer + the diagrams index, task 1003306: the reading measure and rhythm, the table of contents, the progress hairline and the framed figure diagram-viewer.js upgrades on both pages) and `oversight.css` (the OVERSIGHT family — government, watch, harbor, gate, task 1003469: the row, the figure band inside a panel, the group head, the inline form, the grant toggle and the shared confirm modal; named oversight because the vocabulary wall bans "governance" on the government surface). A page sheet then holds only what one page has and the others do not — `primer.css` the acknowledge gate, `diagrams.css` the legend strip, `gate.css` the hold reasons and file chips, `watch.css` the severities, the report reader and the grade trend.
131
131
  │ ├── lifecycle/ ← CORE-DOMAIN carve (BV1.R86, default-on): the build state machine — tasks · claims · versions · done-when · goals · dependencies · claim→ship→grade→credit · github-push + merge-lock + conflict-resolve + publish-reconciler. The LAST, most-coupled carve; registers the `lifecycle` kernel port (createTask, classifyTaskKind, tallyPeerVotes, work-tracking reads). CLAUDE.md, module.json, routes/{tasks,claims,versions,done-when,goals,dependencies,gate-approvals,analytics}.js, lifecycle.js, github-push.js, merge-lock.js, conflict-resolve.js, publish-reconciler.js, task-visuals.js + task-visual-db.js + routes/visuals.js (the optional ship-time visual, task 1003109 — the route file is NOT named task-*.js on purpose; see the module CLAUDE.md).
132
132
  │ ├── copy-desk/ ← CORE-DOMAIN module (task 1003113 / R02 of goal 1000074, default-on): the COPY half of the design contract — ADR 0081 gave design TOKENS a repo-owned source of truth, text had none. Owns the FLAG (anyone in any role marks a string or a whole surface as needing an artist's attention, with a reason required in BOTH the route and a CHECK constraint) and the QUEUE an artist reads it from. READS R01's committed registry (docs/copy-registry.json, generated by scripts/gds/copy-inventory.js) and never writes it. NO LIVE CMS is the hard non-goal: no column here could hold replacement text, and copy_no_cms.mjs pins that. Key is `copy-desk`, not `copy` — the branding contract already owns a `copy` block, so the bare word reds the "core carries no module code" fitness check. CLAUDE.md, module.json, registry.js, flags.js, routes/copy-desk.js, migrations/copy_desk_001_flags.sql, tests/{copy_flags,copy_queue,copy_no_cms}.mjs. Its hall surface lives with hall-ui (public/copy-desk.{html,js,css}), per the one-key-per-web-surface precedent.
133
- │ ├── ui-design/ ← CORE-DOMAIN module (task 1003324 / ADR 0197, default-on): the design capability every builder on every instance gets — ships METHOD, never a world. Absorbs ADR 0081's layer: adapters/{claude-design,figma}/index.js (moved from src/ui/adapters/), scripts/{design-sync,figma-design-sync,validate-design}.js (moved from scripts/gds/, which keeps thin shims), docs/design-contract.md (moved from docs/). Declaration-only: contributes.skills design (the /design playbook, new) + design-sync + figma-design-sync; no routes/port/migrations, so disabling it touches no served surface. CLAUDE.md separates the platform FLOORS (the Fifteen Rule, the dark twins, no page :root, the 24px floor, AA in both modes, the kill switch) from the instance's WORLD (its packs + DESIGN.md). kit/ (task 1003320) is the look-before-you-ship kit: lib.js (the floors, the states/probes contracts, the action + expectation vocabularies, the WCAG maths, the stub spawner), serve.js (any surface through the doorway's real transforms + fixtures/), render.js (states × 1440/390/320 × dark/light, audited), probe.js (PASS/FAIL contracts), check-mock.js (platform rules + the instance's DESIGN.md taste bans), contrast.js + png.js; a page's <page>.states.json / <page>.probes.json sit BESIDE the surface (modules/public-landing/public/, modules/hall-ui/public/). Recipe: docs/recipes/ui-look-before-you-ship.md. skills/ (task 1003321 / ADR 0198) is the vendoring policy: policy.json (licence allowlist, verdicts that may land, sidecar fields, preamble markers), PREAMBLE.md (the platform preamble every vendored SKILL.md opens with), README.md (the roster — both origins scanned dangerous on 2026-08-28, landed as rebuild specs; nothing vendored — and the landing recipe); a landable copy goes under skills/vendor/<name>/ with PROVENANCE.md + LICENSE and is materialised into an instance's .claude/skills/ by claude-materialize.js step 1b. Reserved home styles/ (1003323) with a README. config/design-tokens.* + design-sources.json STAY in config/ as the host boundary; public-landing keeps its own key (first customer, not owner).
133
+ │ ├── ui-design/ ← CORE-DOMAIN module (task 1003324 / ADR 0197, default-on): the design capability every builder on every instance gets — ships METHOD, never a world. Absorbs ADR 0081's layer: adapters/{claude-design,figma}/index.js (moved from src/ui/adapters/), scripts/{design-sync,figma-design-sync,validate-design}.js (moved from scripts/gds/, which keeps thin shims), docs/design-contract.md (moved from docs/). Declaration-only: contributes.skills design (the /design playbook, new) + design-sync + figma-design-sync; no routes/port/migrations, so disabling it touches no served surface. CLAUDE.md separates the platform FLOORS (the Fifteen Rule, the dark twins, no page :root, the 24px floor, AA in both modes, the kill switch) from the instance's WORLD (its packs + DESIGN.md). kit/ (task 1003320) is the look-before-you-ship kit: lib.js (the floors, the states/probes contracts, the action + expectation vocabularies, the WCAG maths, the stub spawner), serve.js (any surface through the doorway's real transforms + fixtures/), render.js (states × 1440/390/320 × dark/light, audited), probe.js (PASS/FAIL contracts), check-mock.js (platform rules + the instance's DESIGN.md taste bans), contrast.js + png.js; a page's <page>.states.json / <page>.probes.json sit BESIDE the surface (modules/public-landing/public/, modules/hall-ui/public/). Recipe: docs/recipes/ui-look-before-you-ship.md. skills/ (task 1003321 / ADR 0198) is the vendoring policy: policy.json (licence allowlist, verdicts that may land, sidecar fields, preamble markers), PREAMBLE.md (the platform preamble every vendored SKILL.md opens with), README.md (the roster — both origins scanned dangerous on 2026-08-28, landed as rebuild specs; nothing vendored — and the landing recipe); a landable copy goes under skills/vendor/<name>/ with PROVENANCE.md + LICENSE and is materialised into an instance's .claude/skills/ by claude-materialize.js step 1b. styles/ (task 1003323 / ADR 0219) is the style library: <name>/pack.json (the theme.ui overlay, exactly the fifteen) + DESIGN.md (the look's derived recipes, thirteen in lockstep with the pack) + mock.html (the specimen); chrome-world (pinned to the neutral pack), expedition, grove; STYLE=<name> on the kit lays a look over the instance's pack through GDS_BRANDING_FILE; tests/ui_design_styles.mjs runs the world/cosmos/hall token suites once per alternate (UI_DESIGN_PACK). config/design-tokens.* + design-sources.json STAY in config/ as the host boundary; public-landing keeps its own key (first customer, not owner).
134
134
  │ ├── character-anim/ ← consistent-character generative animation (ADR 0088) — graded painterly frame sets + manifest, the otb-character-review skill. CLAUDE.md, module.json.
135
135
  │ ├── provisioning/ ← stands up + operates a Cloud Bongos instance's own infra (server/DB/domain/DNS/TLS) via the API (ADR 0111); web-tier request+state only, no cloud tokens — a separate control-plane runner drains the intents. Distinct from the `hosting` module (ADR 0092), which brokers compute workloads. CLAUDE.md, module.json.
136
136
  │ └── demo/ ← BV1.R52 acceptance-proof module: one rank-gated write route + one namespaced migration proving the loader mounts a scaffolded module with zero core edits. CLAUDE.md, module.json.
@@ -1353,5 +1353,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
1353
1353
  landed since 1.19.456 with no explicit bump. run 33561330390. (task 1002620)
1354
1354
  1.19.458 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1355
1355
  landed since 1.19.457 with no explicit bump. run 33566876510. (task 1002620)
1356
+ 1.19.459 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1357
+ landed since 1.19.458 with no explicit bump. run 33568609735. (task 1002620)
1356
1358
  ---------------------------------------------------------------------------
1357
1359
  ```
@@ -21,6 +21,7 @@ SURFACE=hall-ui PREFIX=/builders/ node modules/ui-design/kit/serve.js
21
21
  CHILD=1 node modules/ui-design/kit/serve.js # the child-instance shape (apex-only regions stripped)
22
22
  LIVE=1 node modules/ui-design/kit/serve.js # public feeds proxied to the pack's public origin
23
23
  FAIL=featured,progress node modules/ui-design/kit/serve.js # those feeds answer 500
24
+ STYLE=grove node modules/ui-design/kit/serve.js # under a look from the style library (modules/ui-design/styles/; task 1003323)
24
25
  ```
25
26
 
26
27
  The stub runs the page through the **instance's real transforms** — the apex-only strip, the brand-token stamp, the pack's theme injection, run by the core-side command `scripts/gds/ui-kit-transform.js` (a module may not require core, so the stub spawns the command and memoises the page) — then the content-hash `?v=` stamp, with the instance's own branding pack, and answers the public feeds from `kit/fixtures/`. It also serves `docs/` raw, so a mock under `docs/design/mocks/` renders with its relative links: `http://127.0.0.1:3412/docs/design/mocks/hub/v2.html`.
@@ -34,6 +35,7 @@ node modules/ui-design/kit/render.js --page /projects
34
35
  node modules/ui-design/kit/render.js --page / # MSYS_NO_PATHCONV=1 in Git Bash, or the kit reads the mangled root back
35
36
  node modules/ui-design/kit/render.js --page /builders/ranks --surface hall-ui
36
37
  node modules/ui-design/kit/render.js --page /projects --only in-map,card --modes dark --sizes 390
38
+ node modules/ui-design/kit/render.js --page / --style expedition # the same page under a look from the style library
37
39
  ```
38
40
 
39
41
  `render.js` starts its own two stubs (signed out, signed in), renders each state of the page's **states file** in dark + light at 1440×900 / 390×844 / 320×700, drives the state's actions, and audits every render against the **platform floors**:
@@ -3,11 +3,13 @@
3
3
  The stub, the renderer/auditor, the interaction probes, the mock checker and the contrast probe any builder points at any surface of any instance ([task 1003320](https://cloudbongos.com/builders#/task/1003320), [ADR 0197](../../../docs/adr/<redacted>.md)). Plain Node; the browser is the system Edge or Chrome via `playwright-core` (a devDependency — nothing is downloaded).
4
4
 
5
5
  ```
6
- node modules/ui-design/kit/serve.js # the stub (SURFACE, PREFIX, AUTH=1, CHILD=1, LIVE=…, FAIL=…, STUB_PORT)
7
- node modules/ui-design/kit/render.js --page /projects # every state × 1440/390/320 × dark/light, audited against the floors
8
- node modules/ui-design/kit/probe.js --page /projects # the interaction contract as PASS/FAIL lines
9
- node modules/ui-design/kit/check-mock.js --file docs/design/mocks/hub/v2.html
10
- node modules/ui-design/kit/contrast.js --pack | --url <url> [--glyphs sel,sel]
6
+ node modules/ui-design/kit/serve.js # the stub (SURFACE, PREFIX, AUTH=1, CHILD=1, LIVE=…, FAIL=…, STYLE=<look>, STUB_PORT)
7
+ node modules/ui-design/kit/render.js --page /projects # every state × 1440/390/320 × dark/light, audited against the floors (--style <look>)
8
+ node modules/ui-design/kit/probe.js --page /projects # the interaction contract as PASS/FAIL lines (--style <look>)
9
+ node modules/ui-design/kit/check-mock.js --file docs/design/mocks/hub/v2.html # (--pack <look> for a style's own mock)
10
+ node modules/ui-design/kit/contrast.js --pack [look] | --url <url> [--glyphs sel,sel]
11
11
  ```
12
12
 
13
+ A **look** is a branding pack from the module's style library (`modules/ui-design/styles/<name>/`, task 1003323): `STYLE=<name>` lays it over the instance's own pack through `GDS_BRANDING_FILE`, so any surface renders under any look with no page edit.
14
+
13
15
  A surface describes itself in two JSON files beside it — `<page>.states.json` and `<page>.probes.json` — in the vocabularies `lib.js` defines and validates. The full loop, the file shapes and the failure table: [`docs/recipes/ui-look-before-you-ship.md`](../../../docs/recipes/ui-look-before-you-ship.md). Exit codes everywhere: 0 clean · 1 problems · 2 a broken file or usage · 3 no browser.
@@ -14,8 +14,10 @@
14
14
  // shadow). The em dash ban is Cloud Bongos's, not the platform's: an instance
15
15
  // whose DESIGN.md never mentions it is not held to it.
16
16
  //
17
- // node modules/ui-design/kit/check-mock.js --file <mock.html> [--states a,b] [--keys THESIS,BAR] [--design <DESIGN.md>] [--no-taste]
17
+ // node modules/ui-design/kit/check-mock.js --file <mock.html> [--states a,b] [--keys THESIS,BAR] [--design <DESIGN.md>] [--no-taste] [--pack <style>]
18
18
  //
19
+ // `--pack <style>` checks a style's mock against that look's own colours (the pinned-literal
20
+ // rule) and, unless --design says otherwise, its own DESIGN.md bans (task 1003323).
19
21
  // Prints PASS/FAIL/WARN per rule; exit 1 on any FAIL, 2 on usage.
20
22
  const fs = require('node:fs');
21
23
  const path = require('node:path');
@@ -89,7 +91,8 @@ function checkMock(html, { bans = new Set(), packColors = {}, states = [], keys
89
91
  ok('no literal hex colour (the fifteen via var()/color-mix(); black or white with alpha only)', hexes.length === 0, hexes.length ? [...new Set(hexes)].slice(0, 12).join(',') : '');
90
92
  const rgbLit = [...css.matchAll(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/g)].filter((x) => !((x[1] === '0' && x[2] === '0' && x[3] === '0') || (x[1] === '255' && x[2] === '255' && x[3] === '255')));
91
93
  ok('rgb() literals only as black or white with alpha', rgbLit.length === 0, rgbLit.length ? `${rgbLit.length} found` : '');
92
- const pinned = Object.entries(packColors).filter(([, v]) => typeof v === 'string' && /^#[0-9a-f]{6}$/i.test(v) && new RegExp(v.replace('#', '#'), 'i').test(css)).map(([k]) => k);
94
+ // a pack whose card is pure white (the expedition's) must not turn the one legal literal into a pin
95
+ const pinned = Object.entries(packColors).filter(([, v]) => typeof v === 'string' && /^#[0-9a-f]{6}$/i.test(v) && !BLACK_OR_WHITE.test(v.slice(1).toLowerCase()) && new RegExp(v.replace('#', '#'), 'i').test(css)).map(([k]) => k);
93
96
  ok('no pack colour pinned as a literal (a var() of the contract token instead)', pinned.length === 0, pinned.join(','));
94
97
 
95
98
  // ── the taste bans the instance declared ──
@@ -126,16 +129,20 @@ function checkMock(html, { bans = new Set(), packColors = {}, states = [], keys
126
129
  function main() {
127
130
  const args = process.argv.slice(2);
128
131
  const file = lib.argOf(args, '--file', null);
129
- if (!file || lib.hasFlag(args, '--help')) { process.stderr.write('usage: node modules/ui-design/kit/check-mock.js --file <mock.html> [--states a,b] [--keys A,B] [--design <DESIGN.md>] [--no-taste]\n'); process.exit(lib.EXIT.USAGE); }
132
+ if (!file || lib.hasFlag(args, '--help')) { process.stderr.write('usage: node modules/ui-design/kit/check-mock.js --file <mock.html> [--states a,b] [--keys A,B] [--design <DESIGN.md>] [--no-taste] [--pack <style>]\n'); process.exit(lib.EXIT.USAGE); }
130
133
  if (!fs.existsSync(file)) { process.stderr.write(`check-mock: not found: ${file}\n`); process.exit(lib.EXIT.USAGE); }
131
- const designPath = lib.argOf(args, '--design', path.join(lib.REPO_ROOT, 'DESIGN.md'));
134
+ const styleArg = lib.argOf(args, '--pack', null);
135
+ const style = styleArg ? lib.resolveStyle(styleArg) : null;
136
+ if (styleArg && !style) { process.stderr.write(`check-mock: --pack "${styleArg}" is not a style under modules/ui-design/styles/ (${lib.listStyles().join(', ') || 'none'})\n`); process.exit(lib.EXIT.USAGE); }
137
+ const designPath = lib.argOf(args, '--design', style ? path.join(style.dir, 'DESIGN.md') : path.join(lib.REPO_ROOT, 'DESIGN.md'));
132
138
  let bans = new Set();
133
139
  if (!lib.hasFlag(args, '--no-taste')) {
134
140
  if (fs.existsSync(designPath)) bans = readDesignBans(fs.readFileSync(designPath, 'utf8'));
135
141
  else process.stdout.write(`WARN taste bans off: no DESIGN.md at ${designPath} (the platform rules still apply)\n`);
136
142
  }
137
143
  let packColors = {};
138
- try { packColors = lib.packColors(lib.instancePack()); } catch { packColors = {}; }
144
+ if (style) packColors = lib.packColors(style.pack);
145
+ else { try { packColors = lib.packColors(lib.instancePack()); } catch { packColors = {}; } }
139
146
  const results = checkMock(fs.readFileSync(file, 'utf8'), { bans, packColors, states: lib.listArg(args, '--states', []), keys: lib.listArg(args, '--keys', []) });
140
147
  for (const [s, rule, detail] of results) process.stdout.write(`${s.padEnd(4)} ${rule}${detail ? ' (' + detail + ')' : ''}\n`);
141
148
  const fails = results.filter((r) => r[0] === 'FAIL').length;
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
  // contrast.js — the contrast probe (task 1003320, ADR 0197), three ways:
4
- // --pack the instance's branding pack: every ink on every ground,
5
- // light values from the pack (pure; no browser)
4
+ // --pack [name] the instance's branding pack — or, named, a look from the
5
+ // style library (modules/ui-design/styles/<name>/pack.json, or a
6
+ // pack.json path): every ink on every ground, light values
7
+ // from the pack (pure; no browser)
6
8
  // --url <url> | --file <html> walk every visible text element in dark + light
7
9
  // and compute the real WCAG ratio against its COMPOSITED
8
10
  // background (the degraded static detector cannot do this)
@@ -11,7 +13,7 @@
11
13
  // ground's median — for text over a moving or textured
12
14
  // ground where the stylesheet cannot say what is under it
13
15
  //
14
- // node modules/ui-design/kit/contrast.js --pack
16
+ // node modules/ui-design/kit/contrast.js --pack [expedition]
15
17
  // node modules/ui-design/kit/contrast.js --url http://127.0.0.1:3412/projects [--modes dark,light] [--glyphs "#projTrig,.foot a"]
16
18
  // node modules/ui-design/kit/contrast.js --file docs/design/mocks/hub/v2.html
17
19
  //
@@ -110,12 +112,17 @@ async function glyphContrast(page, selector) {
110
112
 
111
113
  async function main() {
112
114
  const args = process.argv.slice(2);
113
- if (lib.hasFlag(args, '--help')) { process.stderr.write('usage: node modules/ui-design/kit/contrast.js --pack | --url <url> | --file <html> [--modes dark,light] [--glyphs sel,sel]\n'); process.exit(lib.EXIT.USAGE); }
115
+ if (lib.hasFlag(args, '--help')) { process.stderr.write('usage: node modules/ui-design/kit/contrast.js --pack [name] | --url <url> | --file <html> [--modes dark,light] [--glyphs sel,sel]\n'); process.exit(lib.EXIT.USAGE); }
114
116
  const url = lib.argOf(args, '--url', null), file = lib.argOf(args, '--file', null);
115
117
  let failures = 0;
116
118
  if (lib.hasFlag(args, '--pack') || (!url && !file)) {
117
- const rows = packContrast(lib.instancePack());
118
- process.stdout.write('the pack (light values):\n');
119
+ // `--pack` alone is the instance's; `--pack <name>` is a look from the style library
120
+ const packArg = lib.argOf(args, '--pack', null);
121
+ const named = packArg && !packArg.startsWith('--') ? packArg : null;
122
+ const style = named ? lib.resolveStyle(named) : null;
123
+ if (named && !style) { process.stderr.write(`contrast: --pack "${named}" is not a style under modules/ui-design/styles/ (${lib.listStyles().join(', ') || 'none'}) nor a pack.json path\n`); process.exit(lib.EXIT.USAGE); }
124
+ const rows = packContrast(style ? style.pack : lib.instancePack());
125
+ process.stdout.write(`${style ? `the ${style.name} pack` : 'the pack'} (light values):\n`);
119
126
  for (const r of rows) { process.stdout.write(` ${r.ok === null ? 'SKIP' : r.ok ? 'PASS' : 'FAIL'} ${String(r.fg).padEnd(11)} on ${String(r.bg).padEnd(8)} ${r.ratio === null ? r.note : r.ratio + ':1 (need ' + r.need + ')'}\n`); if (r.ok === false) failures++; }
120
127
  if (!url && !file) { process.stdout.write(failures ? `\n${failures} pack pair(s) below the floor\n` : '\nPACK CLEAN\n'); process.exit(failures ? lib.EXIT.PROBLEMS : lib.EXIT.CLEAN); }
121
128
  }