@bongos/core 1.19.396 → 1.19.398
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.
- package/.bongos-core.json +149 -19
- package/.claude/skills/design/SKILL.md +9 -1
- package/docs/adr/0198-third-party-skill-vendoring-policy.md +43 -0
- package/docs/adr/README.md +1 -0
- package/docs/file-map.md +1 -1
- package/docs/recipes/ui-look-before-you-ship.md +113 -0
- package/modules/hall-ui/public/ranks.states.json +15 -0
- package/modules/ui-design/kit/README.md +12 -2
- package/modules/ui-design/kit/check-mock.js +147 -0
- package/modules/ui-design/kit/contrast.js +156 -0
- package/modules/ui-design/kit/fixtures/cost-summary.json +1 -0
- package/modules/ui-design/kit/fixtures/featured.json +1 -0
- package/modules/ui-design/kit/fixtures/leaderboard.json +1 -0
- package/modules/ui-design/kit/fixtures/me.json +9 -0
- package/modules/ui-design/kit/fixtures/my-projects.json +1 -0
- package/modules/ui-design/kit/fixtures/progress.json +1 -0
- package/modules/ui-design/kit/fixtures/recent-shipped.json +1 -0
- package/modules/ui-design/kit/lib.js +389 -0
- package/modules/ui-design/kit/png.js +51 -0
- package/modules/ui-design/kit/probe.js +100 -0
- package/modules/ui-design/kit/render.js +137 -0
- package/modules/ui-design/kit/serve.js +178 -0
- package/modules/ui-design/skills/PREAMBLE.md +13 -0
- package/modules/ui-design/skills/README.md +36 -2
- package/modules/ui-design/skills/policy.json +12 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/index.probes.json +37 -0
- package/public-landing/index.states.json +16 -0
- package/public-landing/projects.probes.json +79 -0
- package/public-landing/projects.states.json +25 -0
- package/scripts/gds/claude-materialize.js +122 -13
- package/scripts/gds/ui-kit-transform.js +43 -0
- package/src/bongos/module-scope-map.js +1 -0
- package/src/module-api.js +5 -1
- package/tests/claude_materialize.mjs +83 -0
- package/tests/ui_design_kit.mjs +217 -0
- package/tests/ui_design_skills.mjs +168 -0
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.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.398",
|
|
6
|
+
"core_contract": "1.19.398",
|
|
7
|
+
"source_commit": "d2fe79ca3d995c8416584bc06575b73afeaec45c",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-
|
|
9
|
+
"built_at": "2026-08-28T18:29:17.996Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
|
-
"docs_redacted":
|
|
12
|
+
"docs_redacted": 348,
|
|
13
13
|
"agent_docs_stubbed": 23,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1658,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2029,
|
|
20
|
+
"tree_sha256": "034d29b37b3208023040c5ded6d355c32bba38f3e57ced2a770e893f8d1928ff",
|
|
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": "
|
|
130
|
+
"sha256": "a16c7a31109db8e4e798e419fedd80bb6d258aa1f8b2680ccfd2925fd204e26f"
|
|
131
131
|
},
|
|
132
132
|
{
|
|
133
133
|
"path": ".claude/skills/dev/SKILL.md",
|
|
@@ -1489,10 +1489,15 @@
|
|
|
1489
1489
|
"mode": "0000644",
|
|
1490
1490
|
"sha256": "901dcd1ca9748e999ea0db27e213f76c7e38caf620fb6b17fc8d1cf9dbee5e9d"
|
|
1491
1491
|
},
|
|
1492
|
+
{
|
|
1493
|
+
"path": "docs/adr/0198-third-party-skill-vendoring-policy.md",
|
|
1494
|
+
"mode": "0000644",
|
|
1495
|
+
"sha256": "f56d8dfb794186e5a4424d87d40b3ad72cc0fd9872b58c5952aa7313c4f5285a"
|
|
1496
|
+
},
|
|
1492
1497
|
{
|
|
1493
1498
|
"path": "docs/adr/README.md",
|
|
1494
1499
|
"mode": "0000644",
|
|
1495
|
-
"sha256": "
|
|
1500
|
+
"sha256": "ca23e96e590686514d2021c0d0c410121333029204e3e3db9e9c177d000c731f"
|
|
1496
1501
|
},
|
|
1497
1502
|
{
|
|
1498
1503
|
"path": "docs/api-reference.md",
|
|
@@ -1822,7 +1827,7 @@
|
|
|
1822
1827
|
{
|
|
1823
1828
|
"path": "docs/file-map.md",
|
|
1824
1829
|
"mode": "0000644",
|
|
1825
|
-
"sha256": "
|
|
1830
|
+
"sha256": "af39b836538ca2a2fbdb002be8b9dcc7b899b23e48cfe2cfe60325b5cb73bd45"
|
|
1826
1831
|
},
|
|
1827
1832
|
{
|
|
1828
1833
|
"path": "docs/handoff-template.md",
|
|
@@ -2054,6 +2059,11 @@
|
|
|
2054
2059
|
"mode": "0000644",
|
|
2055
2060
|
"sha256": "02ae31810440b86f7c896b7014c75549497cab47345079986d54c9785cf5cc4a"
|
|
2056
2061
|
},
|
|
2062
|
+
{
|
|
2063
|
+
"path": "docs/recipes/ui-look-before-you-ship.md",
|
|
2064
|
+
"mode": "0000644",
|
|
2065
|
+
"sha256": "cd8272b75c5fe26f165cf95a45656109266c8ef1942a47705ab10f5f5affcdd5"
|
|
2066
|
+
},
|
|
2057
2067
|
{
|
|
2058
2068
|
"path": "docs/recipes/upgrading-the-core.md",
|
|
2059
2069
|
"mode": "0000644",
|
|
@@ -3999,6 +4009,11 @@
|
|
|
3999
4009
|
"mode": "0000644",
|
|
4000
4010
|
"sha256": "99f9338383c4940f199f8ad66d2140779b0262b628c135646c00778973a5b292"
|
|
4001
4011
|
},
|
|
4012
|
+
{
|
|
4013
|
+
"path": "modules/hall-ui/public/ranks.states.json",
|
|
4014
|
+
"mode": "0000644",
|
|
4015
|
+
"sha256": "3a2e1d649c217473b3706f9295b796c50a20327b78b2856daf85bf01a1e34b27"
|
|
4016
|
+
},
|
|
4002
4017
|
{
|
|
4003
4018
|
"path": "modules/hall-ui/public/roadmap.css",
|
|
4004
4019
|
"mode": "0000644",
|
|
@@ -5177,7 +5192,77 @@
|
|
|
5177
5192
|
{
|
|
5178
5193
|
"path": "modules/ui-design/kit/README.md",
|
|
5179
5194
|
"mode": "0000644",
|
|
5180
|
-
"sha256": "
|
|
5195
|
+
"sha256": "850e72f762d80951ed0ad6ca392a5c0619f26c10a58edd0a1d467038b74bbcfa"
|
|
5196
|
+
},
|
|
5197
|
+
{
|
|
5198
|
+
"path": "modules/ui-design/kit/check-mock.js",
|
|
5199
|
+
"mode": "0000644",
|
|
5200
|
+
"sha256": "9f38941d23b498feeac12f6f32a64d5f3c7749e1b6e01db7bf83dedd3cc2e549"
|
|
5201
|
+
},
|
|
5202
|
+
{
|
|
5203
|
+
"path": "modules/ui-design/kit/contrast.js",
|
|
5204
|
+
"mode": "0000644",
|
|
5205
|
+
"sha256": "239cc33a7896dbbe0bab25098904b2be8298ff5c440b7e2a44594e131d21ed9c"
|
|
5206
|
+
},
|
|
5207
|
+
{
|
|
5208
|
+
"path": "modules/ui-design/kit/fixtures/cost-summary.json",
|
|
5209
|
+
"mode": "0000644",
|
|
5210
|
+
"sha256": "057391fcbba1985a234cf1d4f56db36a89a79d1ee44a80b5cd30f51ed7175cdb"
|
|
5211
|
+
},
|
|
5212
|
+
{
|
|
5213
|
+
"path": "modules/ui-design/kit/fixtures/featured.json",
|
|
5214
|
+
"mode": "0000644",
|
|
5215
|
+
"sha256": "a5e075d343bc7d29e69896ba90de2fc96e2aeb5765f09e193cb26926b4ae697d"
|
|
5216
|
+
},
|
|
5217
|
+
{
|
|
5218
|
+
"path": "modules/ui-design/kit/fixtures/leaderboard.json",
|
|
5219
|
+
"mode": "0000644",
|
|
5220
|
+
"sha256": "53f4fbe364bee2295133ea55082b79069451b0deaf3e99e42d0963c63ee1da87"
|
|
5221
|
+
},
|
|
5222
|
+
{
|
|
5223
|
+
"path": "modules/ui-design/kit/fixtures/me.json",
|
|
5224
|
+
"mode": "0000644",
|
|
5225
|
+
"sha256": "71e560cd3ffcc86287f0ec9c0e527de5ce568a1a9c5c8a6cefea7efe2459d070"
|
|
5226
|
+
},
|
|
5227
|
+
{
|
|
5228
|
+
"path": "modules/ui-design/kit/fixtures/my-projects.json",
|
|
5229
|
+
"mode": "0000644",
|
|
5230
|
+
"sha256": "ff0195be4b19616f30fccde0dc980a47329587124bfac08158b1e8741386fc1b"
|
|
5231
|
+
},
|
|
5232
|
+
{
|
|
5233
|
+
"path": "modules/ui-design/kit/fixtures/progress.json",
|
|
5234
|
+
"mode": "0000644",
|
|
5235
|
+
"sha256": "bbc16593ef50e966647f54559834a956778ac59937ef3fc27794d98d1e12a67e"
|
|
5236
|
+
},
|
|
5237
|
+
{
|
|
5238
|
+
"path": "modules/ui-design/kit/fixtures/recent-shipped.json",
|
|
5239
|
+
"mode": "0000644",
|
|
5240
|
+
"sha256": "5c50e901d4e2e123b2c94f17b42e68cdb5dd9702edff840802777f0796e8f976"
|
|
5241
|
+
},
|
|
5242
|
+
{
|
|
5243
|
+
"path": "modules/ui-design/kit/lib.js",
|
|
5244
|
+
"mode": "0000644",
|
|
5245
|
+
"sha256": "9b0b5eb21711fd7f5c0857a89066270ac8c0911cec19d0b8f21ed369a1dde8e5"
|
|
5246
|
+
},
|
|
5247
|
+
{
|
|
5248
|
+
"path": "modules/ui-design/kit/png.js",
|
|
5249
|
+
"mode": "0000644",
|
|
5250
|
+
"sha256": "d4a7f9c791dec3857452642a1f87dac716c2f098a36d6f7b5a0655da9a110b4e"
|
|
5251
|
+
},
|
|
5252
|
+
{
|
|
5253
|
+
"path": "modules/ui-design/kit/probe.js",
|
|
5254
|
+
"mode": "0000644",
|
|
5255
|
+
"sha256": "d38d6168c3bd701a368726fbf53bfb9209711bd2cb68b7df79a3d2505edf2efe"
|
|
5256
|
+
},
|
|
5257
|
+
{
|
|
5258
|
+
"path": "modules/ui-design/kit/render.js",
|
|
5259
|
+
"mode": "0000644",
|
|
5260
|
+
"sha256": "15931b862ff62824b9916172d75df677bea186e1be23eefb99bbd4541932acfd"
|
|
5261
|
+
},
|
|
5262
|
+
{
|
|
5263
|
+
"path": "modules/ui-design/kit/serve.js",
|
|
5264
|
+
"mode": "0000644",
|
|
5265
|
+
"sha256": "f1d349f681525b78abb47df98bf26a1f7470a72b00b8d5696d5453bb936647c8"
|
|
5181
5266
|
},
|
|
5182
5267
|
{
|
|
5183
5268
|
"path": "modules/ui-design/module.json",
|
|
@@ -5199,10 +5284,20 @@
|
|
|
5199
5284
|
"mode": "0000644",
|
|
5200
5285
|
"sha256": "c9d90b5ffb6e3895c8e98cefbdc89ff4fdd52a22a1073d8fe956a487c4b7816d"
|
|
5201
5286
|
},
|
|
5287
|
+
{
|
|
5288
|
+
"path": "modules/ui-design/skills/PREAMBLE.md",
|
|
5289
|
+
"mode": "0000644",
|
|
5290
|
+
"sha256": "aa96d856793c1e804a90f40c498cff6143f2b5fee80dd5f840422cc6f1230644"
|
|
5291
|
+
},
|
|
5202
5292
|
{
|
|
5203
5293
|
"path": "modules/ui-design/skills/README.md",
|
|
5204
5294
|
"mode": "0000644",
|
|
5205
|
-
"sha256": "
|
|
5295
|
+
"sha256": "0f7045426ad746ec98f6cf16ba0e2bf5c8ee573de9907dbc93676eac7d43d7b6"
|
|
5296
|
+
},
|
|
5297
|
+
{
|
|
5298
|
+
"path": "modules/ui-design/skills/policy.json",
|
|
5299
|
+
"mode": "0000644",
|
|
5300
|
+
"sha256": "f73cb4ce230ffbd940a4aa00742f5d09e736a8f70402c155d1933618e70392d7"
|
|
5206
5301
|
},
|
|
5207
5302
|
{
|
|
5208
5303
|
"path": "modules/ui-design/styles/README.md",
|
|
@@ -5212,12 +5307,12 @@
|
|
|
5212
5307
|
{
|
|
5213
5308
|
"path": "package-lock.json",
|
|
5214
5309
|
"mode": "0000644",
|
|
5215
|
-
"sha256": "
|
|
5310
|
+
"sha256": "13f7c20f7d01157842d006764090fabd60536d20e4dee048243f04851ece16c8"
|
|
5216
5311
|
},
|
|
5217
5312
|
{
|
|
5218
5313
|
"path": "package.json",
|
|
5219
5314
|
"mode": "0000644",
|
|
5220
|
-
"sha256": "
|
|
5315
|
+
"sha256": "f45044af3ba97b0e1b923e179ea75e8186280f21bb0168cefd7489a133a137c0"
|
|
5221
5316
|
},
|
|
5222
5317
|
{
|
|
5223
5318
|
"path": "public-docs/index.html",
|
|
@@ -5549,6 +5644,16 @@
|
|
|
5549
5644
|
"mode": "0000644",
|
|
5550
5645
|
"sha256": "1e7ceb571c899be95c7a945a8bd4337bb000af2b90d6f8de5b4dfcef3a5e8891"
|
|
5551
5646
|
},
|
|
5647
|
+
{
|
|
5648
|
+
"path": "public-landing/index.probes.json",
|
|
5649
|
+
"mode": "0000644",
|
|
5650
|
+
"sha256": "eb90141b2ef676e06b3140d2e8caa61481991a748b36491502592987ed9c0880"
|
|
5651
|
+
},
|
|
5652
|
+
{
|
|
5653
|
+
"path": "public-landing/index.states.json",
|
|
5654
|
+
"mode": "0000644",
|
|
5655
|
+
"sha256": "8d5955085ab5059a15195d5c475262d114061d616dd9c82335a91a5992cc5602"
|
|
5656
|
+
},
|
|
5552
5657
|
{
|
|
5553
5658
|
"path": "public-landing/privacy.html",
|
|
5554
5659
|
"mode": "0000644",
|
|
@@ -5559,6 +5664,16 @@
|
|
|
5559
5664
|
"mode": "0000644",
|
|
5560
5665
|
"sha256": "1e1867918ec2a8fe489d9d21aa2e50fada9950b220c865c57f23c703a6c29f24"
|
|
5561
5666
|
},
|
|
5667
|
+
{
|
|
5668
|
+
"path": "public-landing/projects.probes.json",
|
|
5669
|
+
"mode": "0000644",
|
|
5670
|
+
"sha256": "e5594cda5b6c30569aaca6181dc39eb28d20e716cdebbca7ad618748bbb8ccb5"
|
|
5671
|
+
},
|
|
5672
|
+
{
|
|
5673
|
+
"path": "public-landing/projects.states.json",
|
|
5674
|
+
"mode": "0000644",
|
|
5675
|
+
"sha256": "26a1473a42005d89b4a15b3538b3e9fe9d1982633f1ec556524d83e114df5240"
|
|
5676
|
+
},
|
|
5562
5677
|
{
|
|
5563
5678
|
"path": "public-landing/terms.html",
|
|
5564
5679
|
"mode": "0000644",
|
|
@@ -5807,7 +5922,7 @@
|
|
|
5807
5922
|
{
|
|
5808
5923
|
"path": "scripts/gds/claude-materialize.js",
|
|
5809
5924
|
"mode": "0000644",
|
|
5810
|
-
"sha256": "
|
|
5925
|
+
"sha256": "4cfe40c72c326ac80a467c56ad24fea60e1b2f1a8a1399c21208eb333b9980fa"
|
|
5811
5926
|
},
|
|
5812
5927
|
{
|
|
5813
5928
|
"path": "scripts/gds/claude-md-template.js",
|
|
@@ -6789,6 +6904,11 @@
|
|
|
6789
6904
|
"mode": "0000644",
|
|
6790
6905
|
"sha256": "cc6e8640376ada7ceff6a896007b3257c8ea4d943a900eae28433cd41bdd139d"
|
|
6791
6906
|
},
|
|
6907
|
+
{
|
|
6908
|
+
"path": "scripts/gds/ui-kit-transform.js",
|
|
6909
|
+
"mode": "0000644",
|
|
6910
|
+
"sha256": "8735394cfc5bcdd85b7addb78c3de302f4ba0fee37c25a595f5dcb46240ec529"
|
|
6911
|
+
},
|
|
6792
6912
|
{
|
|
6793
6913
|
"path": "scripts/gds/update-channel.js",
|
|
6794
6914
|
"mode": "0000644",
|
|
@@ -7032,7 +7152,7 @@
|
|
|
7032
7152
|
{
|
|
7033
7153
|
"path": "src/bongos/module-scope-map.js",
|
|
7034
7154
|
"mode": "0000644",
|
|
7035
|
-
"sha256": "
|
|
7155
|
+
"sha256": "9705e8a5176cfd1136c1b432b1d4ff71351d9f4654c636345597a4d96763e500"
|
|
7036
7156
|
},
|
|
7037
7157
|
{
|
|
7038
7158
|
"path": "src/bongos/module-submissions.js",
|
|
@@ -7207,7 +7327,7 @@
|
|
|
7207
7327
|
{
|
|
7208
7328
|
"path": "src/module-api.js",
|
|
7209
7329
|
"mode": "0000644",
|
|
7210
|
-
"sha256": "
|
|
7330
|
+
"sha256": "ecd70bbc85834d5014b2062dd73436d56124aa399c12f9f4f0fc0f5e5f25a594"
|
|
7211
7331
|
},
|
|
7212
7332
|
{
|
|
7213
7333
|
"path": "src/module-loader/catalog.js",
|
|
@@ -7692,7 +7812,7 @@
|
|
|
7692
7812
|
{
|
|
7693
7813
|
"path": "tests/claude_materialize.mjs",
|
|
7694
7814
|
"mode": "0000644",
|
|
7695
|
-
"sha256": "
|
|
7815
|
+
"sha256": "37b2632142b2b2855a3c2b4b5dff6ab84536ee7b4a73fc24078851b6fc289f91"
|
|
7696
7816
|
},
|
|
7697
7817
|
{
|
|
7698
7818
|
"path": "tests/clawback_confirmed_backlog.mjs",
|
|
@@ -9954,6 +10074,16 @@
|
|
|
9954
10074
|
"mode": "0000644",
|
|
9955
10075
|
"sha256": "74798ce81280d4fcfa5844b575c3c64db582308f7a0726fc420317fe83eadd96"
|
|
9956
10076
|
},
|
|
10077
|
+
{
|
|
10078
|
+
"path": "tests/ui_design_kit.mjs",
|
|
10079
|
+
"mode": "0000644",
|
|
10080
|
+
"sha256": "2d3c4f767a3de04beddf22d6d0acf8ca57b7a85dc0f132930466eb4f33aa8c13"
|
|
10081
|
+
},
|
|
10082
|
+
{
|
|
10083
|
+
"path": "tests/ui_design_skills.mjs",
|
|
10084
|
+
"mode": "0000644",
|
|
10085
|
+
"sha256": "d43f67840668875a9829d741f661e344a27675704834cd6a5e6b9e3247164068"
|
|
10086
|
+
},
|
|
9957
10087
|
{
|
|
9958
10088
|
"path": "tests/update_channel.mjs",
|
|
9959
10089
|
"mode": "0000644",
|
|
@@ -25,7 +25,7 @@ Read, in this order, and quote nothing from memory:
|
|
|
25
25
|
|
|
26
26
|
Render the real surface before and after — never reason about layout from source alone:
|
|
27
27
|
|
|
28
|
-
-
|
|
28
|
+
- Run the kit — `node modules/ui-design/kit/render.js --page <path>` renders every state of the page's states file at **1440 / 390 / 320 × dark / light** through a stub that serves the surface the way the instance does, audits each render against the floors below, and writes the shots; `node modules/ui-design/kit/probe.js --page <path>` runs the page's interaction contract as PASS/FAIL lines. The loop, the file shapes and the failure table: [`docs/recipes/ui-look-before-you-ship.md`](../../../docs/recipes/ui-look-before-you-ship.md). A page with no states file yet gets one first (copy the hub's). `/builder-stage` on a box shows the same change on a live preview.
|
|
29
29
|
- Show the screens, then caption them in one line. The person you're working with judges the picture.
|
|
30
30
|
- Grep the receiving page for class collisions before porting a component across pages (the landing's content sections are `.band`; the menu is `.menuBand`).
|
|
31
31
|
|
|
@@ -55,6 +55,14 @@ Generated code uses the `--dt-*` variables; adapters write only `src/ui/<surface
|
|
|
55
55
|
- **Figma** (`/figma-design-sync`): push the current state so a designer never opens a stale file; land their edit back as committed code. Canvas art that won't translate becomes an asset, never forced into code.
|
|
56
56
|
- Both are adapters against `modules/ui-design/docs/design-contract.md`; a third tool is a third adapter, not a new integration.
|
|
57
57
|
|
|
58
|
+
## The vetted skills (the ui-design module's `skills/`)
|
|
59
|
+
|
|
60
|
+
Third-party design skills reach a builder only through the module's vendoring policy ([`modules/ui-design/skills/README.md`](../../../modules/ui-design/skills/README.md), ADR 0198): scanned with `/scan-before-install` and reduced to `clean` or `caution`, a licence on the allowlist, a `PROVENANCE.md` sidecar, and the PLATFORM PREAMBLE that reads the instance's world before any taste rule. A vendored skill is listed here by name, one line each; a name that is not listed is not vendored, whatever a builder's own `~/.claude/skills` carries.
|
|
61
|
+
|
|
62
|
+
- *None vendored (2026-08-28).* Both origins scanned for the scoped set — `pbakaus/impeccable`, and the eleven `Leonxlnx/taste-skill` skills (`design-taste-frontend`, `design-taste-frontend-v1`, `high-end-visual-design`, `minimalist-ui`, `industrial-brutalist-ui`, `redesign-existing-projects`, `gpt-taste`, `stitch-design-taste`, `image-to-code`, `imagegen-frontend-web`, `imagegen-frontend-mobile`, `brandkit`) — reduced to `dangerous` at the scanner's deterministic floor and landed as rebuild specs (GDS tasks). The roster, the verdicts and the way to land one are in the README; vendoring one anyway is an owner override recorded in an ADR, not a session's call.
|
|
63
|
+
|
|
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
|
+
|
|
58
66
|
## Stay clear of
|
|
59
67
|
|
|
60
68
|
- 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,43 @@
|
|
|
1
|
+
# ADR 0198 — Third-party skill vendoring: a scan verdict the session cannot override, a licence allowlist, a provenance sidecar, one preamble block, and no auto-update
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted (2026-08-28)
|
|
4
|
+
- **Task:** [task 1003321](https://cloudbongos.com/builders#/task/1003321) (goal 1000080 — the second of the five ui-design tasks scoped with the owner on 2026-08-28; [ADR 0197](<redacted>.md) §7 pointed the vendoring policy at this ADR)
|
|
5
|
+
- **Owner intent (2026-08-28):** every builder, on every instance, can produce good-looking, functional UIs in line with that instance's world and build on it. The skills that produced Cloud Bongos's world lived in one builder's `~/.agents/skills`; they were to become part of the `ui-design` module, materialised for every builder where the module is on.
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
Twelve design skills were installed on the founding builder's machine and used through the whole-frontend pass: `impeccable` (the operating skill the hub round ran under, 148 files, Apache-2.0) and eleven single-file "taste" skills from one repository (`Leonxlnx/taste-skill`, MIT). The task: vendor them under `modules/ui-design/skills/`, each scanned with `/scan-before-install` on its origin, with a provenance sidecar, a licence allowlist, and a platform preamble that reads the instance's world before any taste rule, so `claude-materialize` installs them for every builder on every instance with the module on.
|
|
10
|
+
|
|
11
|
+
Three facts shaped what happened:
|
|
12
|
+
|
|
13
|
+
1. **The platform already owns a scanner with a deterministic floor and a fixed arithmetic** (`modules/security/artifact-scan/`, the `/scan-before-install` skill). Its rules are owner decisions written down: the floor's findings are untouchable by any model; the verdict is `reduceVerdict()`'s max over floor, panel, lens health, tier clamp and coverage; on `dangerous` no install command is printed and no sanitised copy is offered — the honest landing left is a rebuild spec derived from the artifact's documentation, or walking away.
|
|
14
|
+
2. **Both origins reduced to `dangerous`.** `Leonxlnx/taste-skill` @ `ccbc1563…` (64 files, `default` tier, five lenses + one refuter, 21 panel findings upheld, 0 refuted): floor `dangerous` on one critical — an injection-phrase hit at `skills/taste-skill/SKILL.md:892` that the prompt-injection lens identifies as a filename-template placeholder (`<name>--<system>.md`); panel `caution` (one medium: the only documented install path runs an unpinned third-party `npx` CLI; twenty-one low: mandated invocation of any available image-generation tool, unbounded image counts, remote assets and an unpinned CDN script in generated pages, an auto-loaded cross-agent instruction file). `pbakaus/impeccable` @ `ea360025…` (3323 files, 63.7 MB — 16 harness-specific copies of one skill plus a CLI, a browser extension, a plugin and tests; `default` tier): floor `dangerous` on three criticals (a `bypassPermissions` marker in the skill's own live copy-edit agent; an instruction-override phrase in a test fixture that the lens confirms is injection-resistance test data; a same-file credential-store + http-client co-occurrence in a test) with 19 high, 107 medium, 147 low; the panel's upheld findings include PostToolUse and Stop hooks shipped in `.claude/settings.json` and written by the installer into five harness configs (under `$HOME` on a global install, so they fire in every project), a child agent spawned with `--permission-mode bypassPermissions` / `--dangerously-bypass-approvals-and-sandbox` from a loopback HTTP route with the parent environment forwarded, a setup directive that tells the model to disregard its harness system prompt's autonomy statement, a live-server token embedded in page source, an unverified remote bundle download on `install`/`update`, default-on telemetry, and a browser extension requesting `<all_urls>`.
|
|
15
|
+
3. **The materialiser only knew `.claude/skills/`.** `claude-materialize.js` copied the core's `.claude/skills/` tree and skipped a disabled module's declared names; it never looked inside `modules/<key>/`. A skill living in a module's own tree had no path to a builder's harness.
|
|
16
|
+
|
|
17
|
+
The taste skills' one critical is a false positive in the human sense and a true positive in the scanner's. That is exactly the case the scanner's rules were written for: if a session may decide which floor findings "don't really count", the floor is decoration and the next injection is one plausible sentence away from a clean verdict. The eleven taste skills are also, on the panel's reading, not benign as instructions: they mandate spending the builder's image-generation credits whenever a tool is present, with the cost brake removed — on a platform where the owner's rule confines image generation to hero plates and hero objects.
|
|
18
|
+
|
|
19
|
+
## Decision
|
|
20
|
+
|
|
21
|
+
1. **A scan verdict is the gate, and a session cannot override it.** A third-party skill lands under `modules/ui-design/skills/vendor/<name>/` only from a commit that `/scan-before-install` reduced to `clean` or `caution` at the `default` tier or higher. `dangerous` lands a rebuild spec (a GDS task holding the engine's script-framed `REBUILD-SPEC.md`), never a copy — not for a skill already installed on a builder's machine, not for one that ran the last round. Vendoring a `dangerous`-scanned skill is an **owner decision recorded in its own ADR**, with the scan report cited, and the test that enforces this policy (`tests/ui_design_skills.mjs`) is amended in the same change so the exception is visible in code, not just prose.
|
|
22
|
+
2. **A licence allowlist:** `Apache-2.0`, `MIT`, `BSD-2-Clause`, `BSD-3-Clause`, `CC-BY-4.0`. Anything else, or unlicensed, is a rebuild spec. The upstream `LICENSE` text travels with every copy.
|
|
23
|
+
3. **A provenance sidecar** — `PROVENANCE.md` beside every vendored `SKILL.md`, YAML frontmatter with `name`, `origin`, `sourcePath`, `commit` (the full sha; never a branch or tag), `licence`, `scanVerdict`, `scanTier`, `scanReport`, `scannedOn`, `vendoredOn`, then prose. The policy is data, not a README: [`modules/ui-design/skills/policy.json`](../../modules/ui-design/skills/policy.json) holds the allowlist, the landable verdicts, the sidecar's required fields and the preamble markers; the test reads it.
|
|
24
|
+
4. **One preamble block, byte-identical everywhere.** [`modules/ui-design/skills/PREAMBLE.md`](../../modules/ui-design/skills/PREAMBLE.md) is inserted as the first thing after a vendored `SKILL.md`'s frontmatter, fenced by two HTML-comment markers, so an upstream refresh is a three-way merge around one block and the test can pin every copy against the source. It says, in order: load the instance's world (its pack, its tokens, its `DESIGN.md`; the neutral pack and the floors where it has none) before any taste rule; the fifteen-token contract is the only colour source; a style skill produces variants inside that world, never a second world; images are for hero plates and hero objects only; the instance's taste bans apply and win; the platform floors and the Kill Switch hold on every instance; look before you ship through the kit.
|
|
25
|
+
5. **`vendor/` is the home, and it is exempt from the first-party budgets on purpose.** `scripts/gds/fitness-ratchets.js` already excludes any `vendor/` path segment from the line, oversize and duplication ratchets; a 150-file upstream tree is third-party code, not our debt. A skill the module *authors* lives at `skills/<name>/` and is counted.
|
|
26
|
+
6. **The manifest stays the single declaration.** `module.json` `contributes.skills` names every skill the module ships, whether it lives in `.claude/skills/` or under the module; an undeclared dir is not a skill. `claude-materialize.js` gains step 1b: for every enabled module, each declared name that resolves to `modules/<key>/skills/<name>/` or `modules/<key>/skills/vendor/<name>/` (a dir with a `SKILL.md`) is copied — sidecar and licence included, the same `bongosify` + rebrand transforms — into the instance's `.claude/skills/<name>/`; a disabled module's are excluded by the rule that already existed; a name the core's own `.claude/skills/` carries keeps the core copy. `moduleSkillRoster` is the one place the enabled-or-not rule lives, shared by the old skip and the new copy, and it is walked once per materialise. **The gate holds at copy time, not only in CI:** a declared name is a path segment the materialiser writes under, so it must match `SKILL_NAME_RE` (a lowercase slug — `../x` names nothing) before any path is built; and a vendored dir lands only when `vendoredSkillGate` reads a `PROVENANCE.md` whose `licence` is on the allowlist and whose `scanVerdict` may land (the module's own `skills/policy.json` when present, the same five licences and two verdicts otherwise). A refusal is logged and reported in the summary, never a silent skip — a manual materialise outside CI cannot land an unvetted copy.
|
|
27
|
+
7. **No auto-update.** A refresh is a task: re-scan the new commit, re-apply the preamble, update the sidecar. An instance may disable the module (which withdraws every vendored skill) or add its own skills under its own `.claude/skills/`; it never edits a vendored copy.
|
|
28
|
+
8. **The playwright plugin is a documented prerequisite, never vendored** — an MCP plugin is the LIVE browser-driving loop, where the kit is the headless one; the module installs nothing.
|
|
29
|
+
|
|
30
|
+
## Consequences
|
|
31
|
+
|
|
32
|
+
- **The roster is empty on the day the policy ships, and that is the policy working.** Both origins were scanned at the standard tier with the strongest model on every lens and refuter; the reports live in the scanning builder's `~/.config/otb/scan-reports/`, the rows in `security_scans`, and two rebuild-spec tasks hold the documented functionality for an in-house build. The module CLAUDE.md, the skills README and `/design` say so in the same words; `/design` names the twelve by name so a builder knows what was scoped and why it is not there.
|
|
33
|
+
- **A builder's materialised `.claude/skills` carries a vendored skill with its provenance and licence the moment one lands** — proven by fixture in `tests/ui_design_skills.mjs` and `tests/claude_materialize.mjs`, not by the roster.
|
|
34
|
+
- **The core checkout itself does not see module-owned skills** (the materialiser's self-copy guard is a no-op when instance == core). A core developer uses their own `~/.claude/skills`; an instance gets the module's on `init`/`upgrade`. Filed as an idea for a dev-checkout materialise step.
|
|
35
|
+
- **What the owner can decide next, and where:** vendor a subset anyway (an override ADR citing the report, plus the test amendment); or accept the rebuild-spec tasks and build the taste layer in-house on the style library (task 1003323) and the kit; or re-scan a later upstream commit when the flagged behaviours change. None of those is a session's call.
|
|
36
|
+
|
|
37
|
+
## Rejected
|
|
38
|
+
|
|
39
|
+
- **Treating the floor's filename-placeholder hit as a false positive and vendoring the eleven taste skills.** It is one, in the human sense — and the scanner's rule 6 exists so that a model never gets to say so. The value of the gate is that its arithmetic is not negotiable inside a task.
|
|
40
|
+
- **Scanning a fork or a subtree to get a cleaner verdict.** The pinned SHA of the origin is the only install source; a narrower artifact chosen to pass the scan is evasion of our own gate.
|
|
41
|
+
- **Vendoring `impeccable` with the hooks and the live loop stripped.** The scanner's sanitise landing is refused on any critical by design (a malice indicator makes whole-file removal unsafe to reason about), and a hand-pruned copy would be exactly the unscanned bytes the policy forbids.
|
|
42
|
+
- **Putting vendored skills in `.claude/skills/` like the core's own.** They would count against the first-party ratchets, show in the file-map's generated skills section as ours, and lose the `vendor/` signal that says "not our code, see the sidecar".
|
|
43
|
+
- **A second declaration surface** (a `vendored.json` roster the materialiser reads). The manifest is already the declaration; the roster is the README, the enforcement is the test.
|
package/docs/adr/README.md
CHANGED
|
@@ -285,5 +285,6 @@ This keeps the decision history honest and traceable.
|
|
|
285
285
|
| 0195 | [**ADR numbers are checked like migration numbers, and the twenty existing collisions are grandfathered by name** ([task 1003164](https://cloudbongos.com/builders#/task/1003164)). Two ADRs could share a number and nothing flagged it — git sees no path collision, the index auto-merges into two rows, and every gate passes; it was found by a human reading `ls docs/adr/`, after a task note had already cited a number the collision made point at an unrelated document. Migrations had this guard already (`stemCollisions()` hard-fails CI, because a re-used migration number silently skips a migration); ADRs had only a best-effort allocator at claim time and nothing afterwards. `scripts/gds/adr-namespace.js` is the sibling module, wired into fitness as Check 31 and so into the required `unit` gate. Building it found twenty live collisions, not one, plus fourteen index defects of the same merge class — two ADRs listed twice and twelve with no row at all, ten of them the second file of a colliding pair, which is how a collision hides. The twenty pairs are grandfathered by EXACT FILENAME rather than renumbered: renumbering breaks every citation to them, which is the harm the check exists to prevent, and set-equality keeps the exemption as narrow as the history that earned it — a third file on a legacy number, or a rename of either half, still fails, and the list may only shrink. The index half is keyed on LINK TARGET, not number (a number rule cannot express a grandfathered pair): every file has exactly one row, every row points at a file that exists. Row ordering warns and never blocks — ten legacy rows are out of order and no PR author caused that (the ADR 0189 split). Rejected: renumbering; grandfathering by number; a numeric cutoff like LEGACY_BARE_CORE_MAX (a new ADR takes a number above the max, so it would catch nothing); fixing it in the claim-time allocator (which already tries, against a stale tree — that is how the collision happened).](<redacted>.md) | docs / numbering discipline |
|
|
286
286
|
| 0196 | [**The chrome world becomes the token contract; the landing gets its own world sheet beside the hub's** ([task 1003298](https://cloudbongos.com/builders#/task/1003298), goal 1000080). The owner picked the chrome-blobs world (1003296) and the v3 composition with light rebuilt to the dark standard (1003297); this build makes it the platform's token contract. Decisions: (1) the neutral pack moves to the world's LIGHT values (pale grey paper, black ink, salmon accent, Manrope + JetBrains Mono) and design-tokens mirrors it; --accent-deep is #a3481a rather than the mock's #d96a35 because the hub and the gate pages paint TEXT with it and #a3481a is the lightest burnt orange clearing 4.5:1 on all three light grounds; the rgba hairlines are flattened to hex so hex-maths consumers survive. (2) theme.ui.backdrop becomes var(--bg): a literal there paints the light ground under the dark page in both modes, since the body rule outranks every sheet. (3) The landing gets its own world sheet public-landing/assets/world.css (contract tier = pack, derived tier both modes, root-absolute plate token PAIRS, the kill switch; NO composition switches) and cosmos.css stays the hub's component sheet until A4 — folding the two tiers would have collided (--line is a width in the world and a colour in the hub; the world's warm --ok/--warn/--err would repaint the hub's fixed green/amber/red chips). cosmos.css's dark tier moves to the world's black and its derived tier is retuned for the salmon accent (dark ink on the fill, never white). (4) Plates ship under public-landing/assets/world/ with provenance embedded: black plates verbatim JPEG, cutouts as WebP with alpha at 1600px (85–170 KB vs 1.1–2.4 MB PNG), sidecars; plate URLs in custom properties escape the ?v= stamp (edge cache 4h), accepted. (5) Live figures are BAKED inside apex-only sentinels (real, dated; a child renders empty until its own feed answers) and refreshed at render time from the five public feeds with fixed definitions: tasks shipped = the sum over every version row, builders = leaderboard entries holding credits, the ledger line = the first building version; live titles have em dashes normalised to a comma and a trailing parenthetical dropped. Every pack-reading fallback site (the claude-design adapter, the status explore page, provisioning onboarding, dev-box brand artifacts + tray icons, the two gate pages) is moved in the same task so no page keeps a private palette. Rejected: one merged token tier; a landing-private palette; PNG cutouts; a script-only ledger; #d96a35 as accent-deep.](<redacted>.md) | apex / design system |
|
|
287
287
|
| 0197 | [**The ui-design module: design is a platform capability; the instance owns its world** ([task 1003324](https://cloudbongos.com/builders#/task/1003324), goal 1000080 — the first of the five ui-design tasks scoped with the owner 2026-08-28). ADR 0081 put the tool-agnostic design layer in core as Cloud Bongos's machinery; the whole-frontend pass then built the chrome world on it and left the three things a builder needs — the contract and its tooling, a way to look before shipping, the taste and the looks — scattered across core, scratchpads and personal skill installs. The owner's reframing: this is NOT the front door, it is a PLATFORM capability every builder on every instance gets, and Cloud Bongos's front door is its first customer, not its owner. Decisions: (1) `modules/ui-design/` is a default-on core-domain module beside grading/economy/memory — it ships METHOD, never a world; (2) ADR 0081's layer moves in (`src/ui/adapters/` → `adapters/`, the three `scripts/gds/` CLIs → `scripts/`, `docs/design-contract.md` → `docs/`) while `config/design-tokens.*`, `design-sources.json` and `DESIGN.md` STAY out as the host boundary — the ADR 0062 §7 split written for design; (3) declaration-only: three contributed skills, no routes/port/migrations, so `GET /modules` lists it and disabling it withdraws the skills and touches no served surface — and NO `ui` discipline yet, because the hall settings page and newcomer-restock don't know it; (4) the `/design` playbook is finally written — `discipline-modes.json` had routed `ui` tasks to a skill that did not exist since task 1267; (5) the module CLAUDE.md draws the platform FLOORS (the Fifteen Rule, the dark twins, no page `:root`, the 24px floor, AA in both modes, the Kill Switch) against the instance's WORLD (its packs + DESIGN.md), tightenable and never loosenable; (6) the CLIs keep thin `scripts/gds/` shims — the form `claude-materialize` rewrites to `bongos exec` on a packaged core, the form `doc-cli-guard` resolves, and the carve precedent — while inside the module they walk up from `__dirname` instead of the computed `require()` into `src/instance-config.js` the static boundary scanner could not see; (7) three reserved homes, each README'd for the task that fills it, the vendoring policy pointed at the skills task's ADR; (8) `public-landing` keeps its own key. Rejected: leaving the layer in core; moving the token files in; the module owning the front door; module-hosted CLIs with no shims; contributing the `ui` discipline now.](<redacted>.md) | modules / design system |
|
|
288
|
+
| 0198 | [**Third-party skill vendoring: a scan verdict the session cannot override, a licence allowlist, a provenance sidecar, one preamble block, and no auto-update** ([task 1003321](https://cloudbongos.com/builders#/task/1003321), goal 1000080 — the second of the five ui-design tasks). Twelve design skills installed on one builder's machine were to become the `ui-design` module's vendored set. Both origins reduced to DANGEROUS under `/scan-before-install` at the default tier: `Leonxlnx/taste-skill` on one floor critical (an injection-phrase hit the panel identifies as a filename placeholder) with a caution panel; `pbakaus/impeccable` on three floor criticals and a panel that upheld shipped PostToolUse/Stop hooks written into five harness configs, a child agent spawned with permissions bypassed from a loopback HTTP route, a directive telling the model to disregard its harness system prompt, a page-embedded live-server token and an unverified remote bundle download. DECISION: the scan verdict is the gate and a session cannot override it — `dangerous` lands a rebuild spec (a GDS task), never a copy, and vendoring one anyway is an owner ADR plus a test amendment; a licence allowlist (Apache-2.0, MIT, BSD-2/3, CC-BY-4.0); a `PROVENANCE.md` sidecar with the full pinned sha; ONE preamble block (`skills/PREAMBLE.md`, byte-identical, fenced by two markers so an upstream refresh is a three-way merge) that reads the instance's world before any taste rule; `skills/vendor/<name>/` as the home because `fitness-ratchets.js` already exempts `vendor/` from the first-party budgets; the manifest stays the single declaration and `claude-materialize.js` step 1b lands a declared module skill — sidecar included — in every instance's `.claude/skills/` and withdraws it with the module; no auto-update; the playwright plugin a documented prerequisite. The roster ships EMPTY and that is the policy working: the machinery is proven by fixture (`tests/ui_design_skills.mjs`), the twelve are named in `/design` with their verdicts, two rebuild-spec tasks hold the documented functionality. Rejected: treating the placeholder hit as a false positive; scanning a fork or subtree; vendoring impeccable with the hooks stripped; `.claude/skills/` as the home; a second roster file.](<redacted>.md) | modules / design system / security |
|
|
288
289
|
|
|
289
290
|
> ⚠️ **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.
|
package/docs/file-map.md
CHANGED
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
│ ├── 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.
|
|
130
130
|
│ ├── 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).
|
|
131
131
|
│ ├── 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.
|
|
132
|
-
│ ├── 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).
|
|
132
|
+
│ ├── 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 (public-landing/, 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
133
|
│ ├── character-anim/ ← consistent-character generative animation (ADR 0088) — graded painterly frame sets + manifest, the otb-character-review skill. CLAUDE.md, module.json.
|
|
134
134
|
│ ├── 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.
|
|
135
135
|
│ └── 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.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Look before you ship — the ui-design kit
|
|
2
|
+
|
|
3
|
+
> The `ui-design` module's kit (`modules/ui-design/kit/`, [task 1003320](https://cloudbongos.com/builders#/task/1003320), [ADR 0197](../adr/<redacted>.md)): a stub, a renderer/auditor, the interaction probes, the mock checker and the contrast probe any builder points at any surface of any instance. Plain Node; the browser is the system Edge or Chrome (nothing is downloaded). The `/design` playbook sends you here.
|
|
4
|
+
|
|
5
|
+
The loop is five commands and one habit: **stub → render → probe → read the shots → fix in one batch → re-run once.** A surface describes itself in two small JSON files beside it; the kit never hardcodes a page.
|
|
6
|
+
|
|
7
|
+
## 0. Once per machine
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
npm install # playwright-core is a devDependency; no browser download
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Edge or Chrome must be installed. If neither launches, every kit command prints one line saying so and exits 3.
|
|
14
|
+
|
|
15
|
+
## 1. Stub — serve the surface the way the instance would
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
node modules/ui-design/kit/serve.js # public-landing at http://127.0.0.1:3412/
|
|
19
|
+
AUTH=1 STUB_PORT=3413 node modules/ui-design/kit/serve.js # the same, signed in (/me answers { builder })
|
|
20
|
+
SURFACE=hall-ui PREFIX=/builders/ node modules/ui-design/kit/serve.js
|
|
21
|
+
CHILD=1 node modules/ui-design/kit/serve.js # the child-instance shape (apex-only regions stripped)
|
|
22
|
+
LIVE=1 node modules/ui-design/kit/serve.js # public feeds proxied to the pack's public origin
|
|
23
|
+
FAIL=featured,progress node modules/ui-design/kit/serve.js # those feeds answer 500
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
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`.
|
|
27
|
+
|
|
28
|
+
`SURFACE` is a checkout directory (`public-landing`, `modules/hall-ui/public`) or a module key whose manifest declares a `webSurfaces` dir. `PREFIX` is the URL prefix the surface's pages and assets live under (the hall's is `/builders/`).
|
|
29
|
+
|
|
30
|
+
## 2. Render — every state, every width, both modes, audited
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
node modules/ui-design/kit/render.js --page /projects
|
|
34
|
+
node modules/ui-design/kit/render.js --page / # MSYS_NO_PATHCONV=1 in Git Bash, or the kit reads the mangled root back
|
|
35
|
+
node modules/ui-design/kit/render.js --page /builders/ranks --surface hall-ui
|
|
36
|
+
node modules/ui-design/kit/render.js --page /projects --only in-map,card --modes dark --sizes 390
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`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**:
|
|
40
|
+
|
|
41
|
+
| Floor | What fails it |
|
|
42
|
+
|---|---|
|
|
43
|
+
| `console-errors` | a `console.error` or an uncaught page error (the file's `ignoreConsole` regexes excepted) |
|
|
44
|
+
| `failed-requests` | a 4xx/5xx response the file did not declare in `ignoreRequests` |
|
|
45
|
+
| `horizontal-overflow` | `scrollWidth` wider than the viewport |
|
|
46
|
+
| `one-header-main-h1` | not exactly one *rendered* `<header>`, `<main>` and `<h1>` |
|
|
47
|
+
| `controls-24px` | a visible control under 24px on either side (`ignoreControls` for the skip link and the like) |
|
|
48
|
+
| `top-bar-80px` | a `<header>` taller than 80px |
|
|
49
|
+
| `reduced-motion` | under `prefers-reduced-motion` an animation still runs or a `requestAnimationFrame` loop ticks |
|
|
50
|
+
|
|
51
|
+
Then a state's own `expect` (a band open, a dialog present, a field's value, a class, a query param). Shots and `report.json` land in `%TMP%/ui-design-kit/<surface>/<page>/` (or `--out`). Exit 0 is `ALL CLEAN`; 1 lists the problems; 2 is a broken states file, named line by line.
|
|
52
|
+
|
|
53
|
+
### The states file — `<surface>/<page>.states.json`
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"page": "/projects",
|
|
58
|
+
"surface": "public-landing",
|
|
59
|
+
"modeQuery": "mode",
|
|
60
|
+
"ignoreRequests": ["/api/(gds|bongos)/me$", "/my-projects"],
|
|
61
|
+
"ignoreConsole": ["\\b401\\b"],
|
|
62
|
+
"ignoreControls": [".skip", ".vh"],
|
|
63
|
+
"states": {
|
|
64
|
+
"out-map": { "auth": false, "expect": { "expanded": false } },
|
|
65
|
+
"in-map-projects": { "auth": true, "actions": [["click", "#projTrig"], ["wait", 350]], "expect": { "expanded": true, "open": "#menuProjects" } },
|
|
66
|
+
"card": { "auth": true, "actions": [["dispatchClick", "#orbField a.worldOrb:not(#selfOrb)"], ["wait", 600]], "expect": { "dialog": true } }
|
|
67
|
+
},
|
|
68
|
+
"reducedMotion": { "auth": true, "actions": [["click", "#projTrig"], ["wait", 100]] }
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Actions: `click`, `focus`, `press`, `fill`, `wait`, `clickAt x y`, `clickIfVisible`, `dispatchClick`, `check`, `back`, `forward`. Expectations: `expanded`, `dialog`, `visible[]`, `hidden[]`, `open`, `closed`, `activeId`, `activeText`, `expandedIds[]`, `param{}`, `attr[[sel,name,value]]`, `text[[sel,value]]`, `href[[sel,value]]`, `count[[sel,n]]`, `value[[sel,v]]`, `hasClass[[sel,class,bool]]`, `checked`, `noOverflow`. The kit validates the file before it opens a browser and names every problem.
|
|
73
|
+
|
|
74
|
+
The worked examples: `public-landing/projects.states.json` (the hub's thirteen), `public-landing/index.states.json` (the landing's four), `modules/hall-ui/public/ranks.states.json` (a second surface under its own prefix — it renders **red**, deliberately: the hall predates the 24px floor and Ranks has no `<h1>`, and the states file declares the shell's un-fixtured feeds rather than hiding the findings; idea 1000863 carries them).
|
|
75
|
+
|
|
76
|
+
## 3. Probe — the interaction contract, as PASS/FAIL lines
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
node modules/ui-design/kit/probe.js --page /projects
|
|
80
|
+
node modules/ui-design/kit/probe.js --page / --only "Escape"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
A **probes file** (`<surface>/<page>.probes.json`) is a sequence: each probe opens the page (or continues the previous probe's page with `"continue": true`), drives steps from the same action vocabulary, and states what must be true with the same expectations. An uncaught page error during a probe fails it. The disclosure contract ships first — `public-landing/projects.probes.json` (24 probes: Enter opens on the first item, arrows cycle and skip hidden cells, Escape returns focus, Tab out closes, a click outside closes, one band closes the other, the views, the card yields Escape, the fold under 480, signed out nothing throws) and `index.probes.json` (10, the landing).
|
|
84
|
+
|
|
85
|
+
## 4. Read the shots, fix in one batch, re-run once
|
|
86
|
+
|
|
87
|
+
Open the shots directory. Look at the 390 and 320 renders first (the fold, the bar, the bands), then dark, then the states you touched. Fix everything you see in **one** batch — a render pass costs a minute, a change-render-change loop costs the afternoon — then re-run `render.js` and `probe.js` once. Ship the tests with the change: a new interactive behaviour gets a pin in the page's own test file in the same diff.
|
|
88
|
+
|
|
89
|
+
## 5. Two more probes, for a mock and for colour
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
node modules/ui-design/kit/check-mock.js --file docs/design/mocks/hub/v2.html [--states out-map,card] [--keys THESIS,BAR]
|
|
93
|
+
node modules/ui-design/kit/contrast.js --pack
|
|
94
|
+
node modules/ui-design/kit/contrast.js --url http://127.0.0.1:3412/projects --glyphs "#projTrig,.foot a"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`check-mock.js` runs the mechanical rules over a self-contained mock before anyone looks at it: the **platform's** (no page-local `:root`, page tokens on `body`, every dark rule written twice byte-identical, no literal colour beyond black/white with alpha, no pack colour pinned, the `?state=` contract, a head comment after `<body>`, one header/main/h1 + a skip link, apex-only sentinels balanced, no `innerHTML`, no motion in JavaScript, transitions ≤ 400ms) and the **instance's taste bans**, read from its `DESIGN.md` "Don't" section (an em/en dash, emoji, gradient text, a coloured glow, a box shadow). The em dash ban is Cloud Bongos's, not the platform's: an instance whose `DESIGN.md` never mentions it is not held to it.
|
|
98
|
+
|
|
99
|
+
`contrast.js --pack` measures the instance's own pack — every ink on every ground, light values — with no browser. `--url`/`--file` walks every visible text element in both modes and computes the real WCAG ratio against its *composited* background; `--glyphs` screenshots named elements and reads the glyph pixels against the local ground's median, for text over a moving or textured ground where the stylesheet cannot say what is under it.
|
|
100
|
+
|
|
101
|
+
## When it goes wrong
|
|
102
|
+
|
|
103
|
+
| Symptom | Cause | Do |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| `no states file beside the surface: …/Git.states.json` | Git Bash turned `--page /` into the MSYS root | the kit reads it back as `/`; or `MSYS_NO_PATHCONV=1 node …` |
|
|
106
|
+
| `no system browser` (exit 3) | neither Edge nor Chrome is installed | install one; the kit never downloads a browser |
|
|
107
|
+
| `failed-requests: 404 …/api/bongos/goals` on a hall page | the shell polls feeds the stub does not fixture | declare them in the states file's `ignoreRequests` (the hall's does) — or add a fixture under `kit/fixtures/` and the route in `serve.js` |
|
|
108
|
+
| `controls-24px: button.trig "builder" 13x44` | the chip sized itself by a missing avatar | the fixture builder's avatar is served by the stub (`/__kit/avatar.svg`); a page that rejects non-http avatars gets it absolutised |
|
|
109
|
+
| a probe times out on `click` | an earlier probe in the chain left the page in another state | fix the earlier FAIL first; chains are sequences |
|
|
110
|
+
|
|
111
|
+
## What the kit is not
|
|
112
|
+
|
|
113
|
+
Not CI: `tests/ui_design_kit.mjs` pins the contract (the floors, the file shapes, the audit, the mock rules, the pack, the exit codes, the stub) without a browser; the renders are yours to run and look at. Not a screenshot-diff tool: it audits against floors and expectations, not against a golden image. Not an instance's taste: everything <redacted> lives in the states/probes files beside Cloud Bongos's pages and in its `DESIGN.md`.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_": "The hall's Ranks page as the SECOND surface through modules/ui-design/kit/render.js (task 1003320): the hall serves under /builders/, signed out and in.",
|
|
3
|
+
"page": "/builders/ranks",
|
|
4
|
+
"surface": "hall-ui",
|
|
5
|
+
"stub": { "prefix": "/builders/" },
|
|
6
|
+
"modeQuery": false,
|
|
7
|
+
"_feeds": "The hall's shell polls goals + inbox + profile feeds on every page; the stub fixtures only the public feeds, so those 404s are declared here, not hidden by the kit. The 24px-floor and h1 findings the render reports are REAL (the hall predates the floor) and are deliberately not ignored.",
|
|
8
|
+
"ignoreRequests": ["/api/(gds|bongos)/me$", "/api/(gds|bongos)/goals", "/api/(gds|bongos)/inbox/", "/api/(gds|bongos)/me/"],
|
|
9
|
+
"ignoreConsole": ["/api/(gds|bongos)/me", "\\b401\\b", "Unauthorized", "404 \\(Not Found\\)", "goals load failed"],
|
|
10
|
+
"states": {
|
|
11
|
+
"out": { "auth": false },
|
|
12
|
+
"in": { "auth": true }
|
|
13
|
+
},
|
|
14
|
+
"reducedMotion": { "auth": true }
|
|
15
|
+
}
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
-
# `modules/ui-design/kit/` —
|
|
1
|
+
# `modules/ui-design/kit/` — look before you ship
|
|
2
2
|
|
|
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
|
+
|
|
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]
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
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.
|