@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
@@ -0,0 +1,294 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>{{productName}} · the grove, on a sheet</title>
7
+ <script>
8
+ (function(){try{var q=new URLSearchParams(location.search);var m=q.get('mode');if(m==='light'||m==='dark')document.documentElement.setAttribute('data-mode',m);}catch(e){}})();
9
+ </script>
10
+ <style>
11
+ html { color-scheme: light; }
12
+ body {
13
+ /* the grove's derived tier, by the recipes in this style's DESIGN.md. The fifteen arrive
14
+ from the pack the stub splices after this sheet; nothing below is a colour of its own: a
15
+ var(), a color-mix(), or black or white with alpha. Sculpted paper: soft corners on every
16
+ container, tinted (never grey) shadows mixed from the ink, the sea green as a field a
17
+ whole band can sit on, and the tan spent only on what is pressable or live. */
18
+ --font-mono: "JetBrains Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;
19
+ --glass: var(--bg-card);
20
+ --glass-strong: var(--bg-deep);
21
+ --glass-edge: var(--rule);
22
+ --accent-ink: var(--accent-deep);
23
+ --accent-lift: color-mix(in srgb, var(--accent) 72%, #ffffff);
24
+ --accent-tint: color-mix(in srgb, var(--accent) 34%, var(--bg-card));
25
+ --accent-on: color-mix(in srgb, var(--accent) 12%, #000000);
26
+ --accent-glow: 0 0 0 0 transparent;
27
+ --focus: var(--accent-deep);
28
+ --type-halo: none;
29
+ --field: color-mix(in srgb, var(--sea) 62%, #000000);
30
+ --on-field: color-mix(in srgb, var(--accent) 55%, #ffffff);
31
+ --on-field-soft: color-mix(in srgb, var(--accent) 40%, #ffffff);
32
+ --field-rule: color-mix(in srgb, #ffffff 20%, transparent);
33
+ --shade: color-mix(in srgb, var(--ink) 16%, transparent);
34
+ --shade-soft: color-mix(in srgb, var(--ink) 7%, transparent);
35
+ --lift: 0 10px 26px -14px var(--shade), 0 2px 6px var(--shade-soft);
36
+ --label-face: var(--font-mono);
37
+ --label-size: 11px;
38
+ --label-space: .08em;
39
+ --radius: 22px;
40
+ --radius-sm: 12px;
41
+ --radius-pill: 999px;
42
+ --top-h: 64px;
43
+ --gutter: clamp(16px, 4vw, 26px);
44
+ --band: clamp(40px, 7vh, 72px);
45
+ margin: 0;
46
+ background: var(--bg);
47
+ color: var(--ink);
48
+ font-family: var(--font-body);
49
+ font-size: 17px;
50
+ line-height: 1.55;
51
+ -webkit-font-smoothing: antialiased;
52
+ }
53
+ :root[data-mode="dark"] body {
54
+ color-scheme: dark;
55
+ --bg: #000000;
56
+ --bg-card: color-mix(in srgb, #ffffff 7%, #000000);
57
+ --bg-deep: #000000;
58
+ --bg-frame: color-mix(in srgb, #ffffff 4%, #000000);
59
+ --ink: #ffffff;
60
+ --ink-soft: rgba(255, 255, 255, .74);
61
+ --ink-faint: rgba(255, 255, 255, .64);
62
+ --rule: rgba(255, 255, 255, .16);
63
+ --rule-soft: rgba(255, 255, 255, .08);
64
+ --accent-deep: color-mix(in srgb, var(--accent) 70%, #ffffff);
65
+ --glass: rgba(255, 255, 255, .08);
66
+ --glass-strong: rgba(255, 255, 255, .14);
67
+ --glass-edge: rgba(255, 255, 255, .16);
68
+ --accent-ink: var(--accent);
69
+ --accent-tint: color-mix(in srgb, var(--accent) 18%, #000000);
70
+ --focus: var(--accent);
71
+ --shade: rgba(0, 0, 0, .6);
72
+ --shade-soft: rgba(0, 0, 0, .35);
73
+ --label-face: var(--font-body);
74
+ --label-size: 11.5px;
75
+ --label-space: .06em;
76
+ }
77
+ @media (prefers-color-scheme: dark) {
78
+ :root:not([data-mode="light"]) body {
79
+ color-scheme: dark;
80
+ --bg: #000000;
81
+ --bg-card: color-mix(in srgb, #ffffff 7%, #000000);
82
+ --bg-deep: #000000;
83
+ --bg-frame: color-mix(in srgb, #ffffff 4%, #000000);
84
+ --ink: #ffffff;
85
+ --ink-soft: rgba(255, 255, 255, .74);
86
+ --ink-faint: rgba(255, 255, 255, .64);
87
+ --rule: rgba(255, 255, 255, .16);
88
+ --rule-soft: rgba(255, 255, 255, .08);
89
+ --accent-deep: color-mix(in srgb, var(--accent) 70%, #ffffff);
90
+ --glass: rgba(255, 255, 255, .08);
91
+ --glass-strong: rgba(255, 255, 255, .14);
92
+ --glass-edge: rgba(255, 255, 255, .16);
93
+ --accent-ink: var(--accent);
94
+ --accent-tint: color-mix(in srgb, var(--accent) 18%, #000000);
95
+ --focus: var(--accent);
96
+ --shade: rgba(0, 0, 0, .6);
97
+ --shade-soft: rgba(0, 0, 0, .35);
98
+ --label-face: var(--font-body);
99
+ --label-size: 11.5px;
100
+ --label-space: .06em;
101
+ }
102
+ }
103
+
104
+ *, *::before, *::after { box-sizing: border-box; }
105
+ a { color: inherit; }
106
+ :focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }
107
+ .skip { position: absolute; left: 12px; top: -60px; padding: 11px 18px; border-radius: var(--radius-pill); background: var(--bg-card); color: var(--ink); text-decoration: none; }
108
+ .skip:focus { top: 12px; }
109
+ .wrap { max-width: 1180px; margin: 0 auto; padding: 0 var(--gutter); }
110
+
111
+ /* the bar: a soft card floating on the paper */
112
+ .bar { height: var(--top-h); padding-top: 8px; }
113
+ .bar .wrap { height: 100%; }
114
+ .bar .card { height: 100%; display: flex; align-items: center; gap: 18px; padding: 0 8px 0 18px; border-radius: var(--radius-pill); background: var(--bg-card); box-shadow: var(--lift); }
115
+ .mark { font-weight: 800; letter-spacing: -.01em; text-decoration: none; padding: 10px 0; }
116
+ .nav { margin-left: auto; display: flex; align-items: center; gap: 4px; }
117
+ .nav a { text-decoration: none; color: var(--ink-soft); font-weight: 600; padding: 10px 10px; display: inline-block; line-height: 20px; border-radius: var(--radius-pill); }
118
+ .nav a:hover { color: var(--ink); background: var(--bg-frame); }
119
+ .pill { display: inline-flex; align-items: center; height: 42px; padding: 0 20px; border-radius: var(--radius-pill); text-decoration: none; font-weight: 700; transition: background-color 200ms cubic-bezier(.2, .7, .3, 1); }
120
+ .pill.in { background: var(--field); color: var(--on-field); }
121
+ .who { display: none; align-items: center; gap: 8px; height: 42px; padding: 0 6px; border-radius: var(--radius-pill); background: var(--bg-frame); }
122
+ .who .dot { width: 30px; height: 30px; border-radius: 50%; background: var(--accent); }
123
+ body[data-state="signed-in"] .pill.in { display: none; }
124
+ body[data-state="signed-in"] .who { display: inline-flex; }
125
+
126
+ /* the bands */
127
+ .band { padding: var(--band) 0; }
128
+ h1 { font-family: var(--font-display); font-weight: 800; font-size: clamp(34px, 6.4vw, 84px); line-height: .96; letter-spacing: -.03em; margin: 0 0 18px; max-width: 12ch; }
129
+ h2 { font-family: var(--font-display); font-weight: 800; font-size: clamp(24px, 3vw, 38px); line-height: 1.08; letter-spacing: -.02em; margin: 0 0 14px; }
130
+ .lede { color: var(--ink-soft); max-width: 56ch; margin: 0; }
131
+ .label { font-family: var(--label-face); font-size: var(--label-size); letter-spacing: var(--label-space); text-transform: uppercase; color: var(--ink-faint); font-weight: 500; }
132
+ .badge { display: inline-flex; align-items: center; height: 26px; padding: 0 12px; border-radius: var(--radius-sm); background: var(--accent-tint); color: var(--accent-ink); margin-bottom: 14px; }
133
+
134
+ /* the thirteen, as sculpted tiles */
135
+ .sw { display: grid; grid-template-columns: repeat(auto-fill, minmax(72px, 1fr)); gap: 12px; margin: 0; padding: 0; list-style: none; }
136
+ .sw li { margin: 0; }
137
+ .sw i { display: block; height: 56px; border-radius: var(--radius-sm); background: var(--c); box-shadow: var(--lift); }
138
+ .sw b { display: block; margin-top: 8px; font-family: var(--font-mono); font-weight: 400; font-size: 11px; color: var(--ink-faint); overflow-wrap: anywhere; }
139
+
140
+ /* the slab: the second voice as a field a whole band can sit on */
141
+ .slab { border-radius: var(--radius); background: var(--field); color: var(--on-field); padding: clamp(22px, 4vw, 40px); box-shadow: var(--lift); }
142
+ .slab h2 { color: var(--on-field); }
143
+ .slab .lede { color: var(--on-field-soft); }
144
+ .slab hr { border: 0; border-top: 1px solid var(--field-rule); margin: 18px 0; }
145
+
146
+ /* the door pair: rounded, lifted, the primary in tan */
147
+ .doors { display: inline-flex; flex-wrap: wrap; gap: 8px; padding: 8px; border-radius: var(--radius-pill); background: var(--bg-card); box-shadow: var(--lift); }
148
+ .door { display: inline-flex; align-items: center; height: 48px; padding: 0 28px; border-radius: var(--radius-pill); text-decoration: none; font-weight: 700; transition: background-color 200ms cubic-bezier(.2, .7, .3, 1); }
149
+ .door.go { background: var(--accent); color: var(--accent-on); }
150
+ .door.go:hover { background: var(--accent-lift); }
151
+ .door.more { color: var(--ink); }
152
+ .door.more:hover { background: var(--bg-frame); }
153
+
154
+ /* the lifecycle rail: soft tiles, one lit */
155
+ .rail { display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; margin: 0; padding: 0; list-style: none; }
156
+ .rail li { padding: 16px; border-radius: var(--radius-sm); background: var(--bg-card); box-shadow: var(--lift); }
157
+ .rail .dot { display: block; width: 10px; height: 10px; border-radius: 50%; border: 1px solid var(--ink-faint); margin-bottom: 10px; }
158
+ .rail .lit { background: var(--accent-tint); }
159
+ .rail .lit .dot { background: var(--accent); border-color: var(--accent-deep); }
160
+ .rail .lit .label { color: var(--ink); }
161
+ .rail p { margin: 6px 0 0; color: var(--ink-soft); font-size: 15px; }
162
+
163
+ /* ledger rows: a sculpted panel of hairline rows */
164
+ .ledger { margin: 0; padding: 6px 18px; list-style: none; border-radius: var(--radius); background: var(--bg-card); box-shadow: var(--lift); }
165
+ .ledger li { display: grid; grid-template-columns: 88px 1fr 152px 96px; gap: 20px; align-items: baseline; padding: 14px 0; border-top: 1px solid var(--rule-soft); }
166
+ .ledger li:first-child { border-top: 0; }
167
+ .ledger .id { font-family: var(--font-mono); font-size: 13px; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
168
+ .ledger .by, .ledger .when { color: var(--ink-soft); }
169
+ .ledger .when { text-align: right; font-variant-numeric: tabular-nums; }
170
+ .ledger a { color: var(--accent-ink); text-underline-offset: 3px; display: inline-block; padding: 2px 0; }
171
+
172
+ footer { padding: 26px 0 40px; }
173
+ footer .wrap { display: flex; flex-wrap: wrap; gap: 8px 22px; align-items: baseline; color: var(--ink-soft); font-size: 15px; }
174
+ footer p { margin: 0; flex: 1 1 320px; }
175
+ footer a { color: var(--accent-ink); display: inline-block; padding: 3px 0; font-weight: 600; }
176
+
177
+ @media (max-width: 900px) {
178
+ body { --top-h: 60px; }
179
+ .rail { grid-template-columns: 1fr; }
180
+ }
181
+ @media (max-width: 600px) {
182
+ .ledger li { grid-template-columns: 1fr; gap: 4px; }
183
+ .ledger .when { text-align: left; }
184
+ .nav a { display: none; }
185
+ }
186
+ @media (prefers-reduced-motion: reduce) {
187
+ *, *::before, *::after { animation: none !important; transition: none !important; }
188
+ }
189
+ </style>
190
+ </head>
191
+ <body>
192
+ <!-- THESIS: the grove as a specimen sheet. The thirteen colours and the two faces the pack carries, laid through this look's own derived recipes (its DESIGN.md frontmatter): a bar that floats as a soft card, a rounded heavy display voice, tiles under tinted shadows mixed from the ink, the sea green as a slab a whole band sits on, door pills with the primary in tan, a rail of soft tiles with one lit, ledger rows inside a sculpted panel. This page declares no :root and no colour of its own: the pack is spliced by the stub (STYLE=grove) and every value is a var() or a color-mix() of it. Dark restates the platform's black tier on body, since a mock links no sheet; the candidate's green dark ground cannot travel in a pack, so dark is the black under the tan and the sea. STATES: rest (signed out), signed-in. MODES: ?mode=dark|light. -->
193
+ <a class="skip" href="#main">Skip to content</a>
194
+ <header class="bar">
195
+ <div class="wrap">
196
+ <div class="card">
197
+ <a class="mark" href="/">{{productName}}</a>
198
+ <nav class="nav" aria-label="Primary">
199
+ <a href="#swatches">Projects</a>
200
+ <a href="#type">Docs</a>
201
+ <a class="pill in" href="#main">Sign in</a>
202
+ <span class="who"><span class="dot" aria-hidden="true"></span><span class="label">builder</span></span>
203
+ </nav>
204
+ </div>
205
+ </div>
206
+ </header>
207
+ <main id="main" tabindex="-1">
208
+ <section class="band">
209
+ <div class="wrap">
210
+ <p class="label badge">A look from the style library</p>
211
+ <h1>The grove</h1>
212
+ <p class="lede">A sculpted paper garden. Pale sage paper, soft corners on everything that holds content, shadows tinted from the ink and never grey, the sea green as a second voice, and one lit tan on everything you can press.</p>
213
+ </div>
214
+ </section>
215
+ <section class="band" id="swatches">
216
+ <div class="wrap">
217
+ <h2>The thirteen</h2>
218
+ <ul class="sw">
219
+ <li><i style="--c: var(--bg)"></i><b>bg</b></li>
220
+ <li><i style="--c: var(--bg-card)"></i><b>bg-card</b></li>
221
+ <li><i style="--c: var(--bg-deep)"></i><b>bg-deep</b></li>
222
+ <li><i style="--c: var(--bg-frame)"></i><b>bg-frame</b></li>
223
+ <li><i style="--c: var(--ink)"></i><b>ink</b></li>
224
+ <li><i style="--c: var(--ink-soft)"></i><b>ink-soft</b></li>
225
+ <li><i style="--c: var(--ink-faint)"></i><b>ink-faint</b></li>
226
+ <li><i style="--c: var(--rule)"></i><b>rule</b></li>
227
+ <li><i style="--c: var(--rule-soft)"></i><b>rule-soft</b></li>
228
+ <li><i style="--c: var(--accent)"></i><b>accent</b></li>
229
+ <li><i style="--c: var(--accent-deep)"></i><b>accent-deep</b></li>
230
+ <li><i style="--c: var(--sea)"></i><b>sea</b></li>
231
+ <li><i style="--c: var(--terracotta)"></i><b>terracotta</b></li>
232
+ </ul>
233
+ </div>
234
+ </section>
235
+ <section class="band" id="type">
236
+ <div class="wrap">
237
+ <div class="slab">
238
+ <h2>The slab</h2>
239
+ <p class="lede">The sea green is a field, not an accent: a whole band sits on it, with cream type mixed from the tan and white. This is the second voice the contract holds for every pack; the grove is the look that spends it.</p>
240
+ <hr>
241
+ <p class="label" style="color: var(--on-field-soft)">Label face on the field</p>
242
+ <p style="margin: 6px 0 0; font-family: var(--font-mono); font-size: 13px; font-variant-numeric: tabular-nums">1003323 · 14,987 credits · 7 ships in 24h</p>
243
+ </div>
244
+ </div>
245
+ </section>
246
+ <section class="band">
247
+ <div class="wrap">
248
+ <h2>The doors</h2>
249
+ <p class="lede" style="margin-bottom: 22px">Two doors in one lifted pill. The primary is the tan with near-black ink on it; the secondary fills softly on hover.</p>
250
+ <div class="doors">
251
+ <a class="door more" href="#swatches">Open the map</a>
252
+ <a class="door go" href="#main">Start a project</a>
253
+ </div>
254
+ </div>
255
+ </section>
256
+ <section class="band">
257
+ <div class="wrap">
258
+ <h2>The lifecycle rail</h2>
259
+ <ol class="rail">
260
+ <li><span class="dot"></span><span class="label">Claim</span><p>A builder locks a task.</p></li>
261
+ <li><span class="dot"></span><span class="label">Build</span><p>The code is written in a worktree.</p></li>
262
+ <li class="lit" aria-current="step"><span class="dot"></span><span class="label">Grade</span><p>An adversarial panel reads the diff.</p></li>
263
+ <li><span class="dot"></span><span class="label">Ship</span><p>Green checks land it on main.</p></li>
264
+ <li><span class="dot"></span><span class="label">Credit</span><p>The ledger pays on landing.</p></li>
265
+ </ol>
266
+ </div>
267
+ </section>
268
+ <section class="band">
269
+ <div class="wrap">
270
+ <h2>Ledger rows</h2>
271
+ <ul class="ledger">
272
+ <li><span class="id">1003320</span><span>The look-before-you-ship kit: a stub, a renderer, the probes</span><span class="by">jaxri</span><span class="when">3d ago</span></li>
273
+ <li><span class="id">1003321</span><span>The vetted UI skills: the vendoring policy and the platform preamble</span><span class="by">jaxri</span><span class="when">3d ago</span></li>
274
+ <li><span class="id">1003323</span><span><a href="#main">The style library: looks as branding packs</a></span><span class="by">jaxri</span><span class="when">today</span></li>
275
+ </ul>
276
+ </div>
277
+ </section>
278
+ </main>
279
+ <footer>
280
+ <div class="wrap">
281
+ <p>{{productName}} is built by AI agents claiming tasks from a ledger. This sheet is served through the kit with the look's pack spliced in.</p>
282
+ <a href="#main">Privacy</a>
283
+ <a href="#main">Terms</a>
284
+ <a href="#main">Contact</a>
285
+ </div>
286
+ </footer>
287
+ <script>
288
+ (function () {
289
+ var state = new URLSearchParams(location.search).get('state');
290
+ if (state === 'signed-in') document.body.setAttribute('data-state', 'signed-in');
291
+ })();
292
+ </script>
293
+ </body>
294
+ </html>
@@ -0,0 +1,11 @@
1
+ {
2
+ "_doc": "The specimen's states for the kit (task 1003323): node modules/ui-design/kit/render.js --states modules/ui-design/styles/grove/mock.states.json --style grove. Both modes; the mock reads ?mode= itself.",
3
+ "page": "/mock",
4
+ "surface": "modules/ui-design/styles/grove",
5
+ "modes": ["dark", "light"],
6
+ "states": {
7
+ "rest": { "auth": false },
8
+ "signed-in": { "auth": true, "url": "/mock?state=signed-in", "expect": { "visible": [".who"], "hidden": [".pill.in"] } }
9
+ },
10
+ "reducedMotion": { "auth": false }
11
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "_doc": "A look from the ui-design module's style library (task 1003323): the theme.ui overlay a branding pack carries, and nothing else. The stub lays it over the instance's pack (STYLE=grove); an instance adopts it by copying `theme` into config/branding.json. Light values only, as the contract says: dark is each sheet's own black tier, which this look reaches through the accent and the faces.",
3
+ "name": "grove",
4
+ "title": "The Grove",
5
+ "summary": "A sculpted paper garden: pale sage paper, sea green as the second voice, bark brown for every quiet mark, a lit tan on everything pressable, rounded and soft.",
6
+ "origin": "docs/design/mocks/world/organic-cards (the world round, task 1003296; kept by the owner in round 1, retuned to the contract here: the sage ground lifted from #c8d7bf to #e1eadb and the inks deepened so the hub's fixed status vocabulary and the hall's chips clear 4.5:1 on it, and sea lifted from #2a5a46 to #3d8562 because the hall derives its info state from sea at 80% into white in dark, which the deep green could not carry on black (the look's slab derives a deeper field from it); the candidate's green DARK ground cannot travel in a pack, so dark is the sheets' black under the tan)",
7
+ "theme": {
8
+ "ui": {
9
+ "colors": {
10
+ "bg": "#e1eadb",
11
+ "bgCard": "#f6f9f3",
12
+ "bgDeep": "#cad8c2",
13
+ "bgFrame": "#eaf0e5",
14
+ "ink": "#0f3226",
15
+ "inkSoft": "#2f5240",
16
+ "inkFaint": "#3f5a48",
17
+ "rule": "#9db69a",
18
+ "ruleSoft": "#d0ddcb",
19
+ "accent": "#e3c39c",
20
+ "accentDeep": "#6d4b34",
21
+ "sea": "#3d8562",
22
+ "terracotta": "#b07a4e"
23
+ },
24
+ "fonts": {
25
+ "display": "\"Nunito\", \"Trebuchet MS\", system-ui, sans-serif",
26
+ "body": "\"Nunito\", \"Trebuchet MS\", system-ui, sans-serif",
27
+ "links": [
28
+ "https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&family=Nunito:wght@400;600;800&display=swap"
29
+ ]
30
+ }
31
+ }
32
+ }
33
+ }
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.458",
3
+ "version": "1.19.459",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.458",
9
+ "version": "1.19.459",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.458",
3
+ "version": "1.19.459",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
package/src/module-api.js CHANGED
@@ -33,7 +33,7 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
33
33
  // there. scripts/gds/bump-version.js still rewrites the literal below; it appends
34
34
  // the entry to that file. Look for a version's history there, not here.
35
35
  // ---------------------------------------------------------------------------
36
- const CORE_VERSION = '1.19.458'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
36
+ const CORE_VERSION = '1.19.459'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
37
37
 
38
38
  // A namespaced logger so a module's log lines are attributable + consistent.
39
39
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -75,9 +75,15 @@ const darkMedia = declarations(blockAfter(':root:not([data-mode="light"]){'));
75
75
  // cosmos.css alone would measure a page nobody sees.
76
76
  // The dark blocks are :root[data-mode="dark"] and :root:not([data-mode="light"]) —
77
77
  // both (0,2,0) — so they outrank the pack's (0,1,0) and win back.
78
- const packColors = JSON.parse(
78
+ // UI_DESIGN_PACK=<pack.json> measures the sheet under a look from the style library
79
+ // (tests/ui_design_styles.mjs runs this file once per pack, task 1003323); the DRIFT test
80
+ // below always compares the sheet against the neutral pack, which is what it must equal.
81
+ const neutralColors = JSON.parse(
79
82
  fs.readFileSync(path.join(ROOT, 'config', 'branding.neutral.json'), 'utf8')
80
83
  ).theme.ui.colors;
84
+ const packColors = process.env.UI_DESIGN_PACK
85
+ ? JSON.parse(fs.readFileSync(path.resolve(process.env.UI_DESIGN_PACK), 'utf8')).theme.ui.colors
86
+ : neutralColors;
81
87
 
82
88
  // mirrors COLOR_VARS in src/bongos/serve-internal.js
83
89
  const camelToVar = (k) => '--' + k.replace(/[A-Z]/g, (c) => '-' + c.toLowerCase());
@@ -557,7 +563,8 @@ test('cosmos.css defaults do not drift from the branding pack', () => {
557
563
  // real (cosmos.css said --ink-faint:#9c8d80, the pack shipped #857567), and it
558
564
  // means anyone auditing cosmos.css alone measures colors no user ever sees.
559
565
  const drift = [];
560
- for (const [name, packValue] of Object.entries(PACK)) {
566
+ const NEUTRAL = Object.fromEntries(Object.entries(neutralColors).map(([k, v]) => [camelToVar(k), v]));
567
+ for (const [name, packValue] of Object.entries(NEUTRAL)) {
561
568
  const cssValue = firstRoot[name];
562
569
  if (cssValue === undefined) continue;
563
570
  if (cssValue.toLowerCase() !== packValue.toLowerCase()) {
@@ -86,9 +86,15 @@ const darkMedia = declarations(blockAfter('html:not([data-theme]) {'));
86
86
  // Equal specificity, later in document order, so for the tokens the pack emits
87
87
  // the PACK wins and style.css's own value never renders. html[data-theme="dark"]
88
88
  // is (0,1,1) and wins back.
89
- const packUi = JSON.parse(
89
+ // UI_DESIGN_PACK=<pack.json> measures the sheet under a look from the style library
90
+ // (tests/ui_design_styles.mjs runs this file once per pack, task 1003323); the DRIFT test
91
+ // below always compares the sheet against the neutral pack, which is what it must equal.
92
+ const neutralUi = JSON.parse(
90
93
  fs.readFileSync(path.join(ROOT, 'config', 'branding.neutral.json'), 'utf8'),
91
94
  ).theme.ui;
95
+ const packUi = process.env.UI_DESIGN_PACK
96
+ ? JSON.parse(fs.readFileSync(path.resolve(process.env.UI_DESIGN_PACK), 'utf8')).theme.ui
97
+ : neutralUi;
92
98
 
93
99
  // mirrors COLOR_VARS in src/bongos/serve-internal.js
94
100
  const camelToVar = (k) => '--' + k.replace(/[A-Z]/g, (c) => '-' + c.toLowerCase());
@@ -166,12 +172,13 @@ test('style.css defaults do not drift from the branding pack', () => {
166
172
  // always overrides it. That makes silent drift easy and dangerous: anyone
167
173
  // auditing the sheet alone would measure colours no builder ever sees.
168
174
  const drift = [];
169
- for (const [name, value] of Object.entries(PACK)) {
175
+ const NEUTRAL = Object.fromEntries(Object.entries(neutralUi.colors).map(([k, v]) => [camelToVar(k), v]));
176
+ for (const [name, value] of Object.entries(NEUTRAL)) {
170
177
  if (contractTier[name] !== value) drift.push(`${name}: sheet ${contractTier[name]} vs pack ${value}`);
171
178
  }
172
179
  for (const [key, cssVar] of [['display', '--font-display'], ['body', '--font-body']]) {
173
- if (contractTier[cssVar] !== packUi.fonts[key]) {
174
- drift.push(`${cssVar}: sheet ${contractTier[cssVar]} vs pack ${packUi.fonts[key]}`);
180
+ if (contractTier[cssVar] !== neutralUi.fonts[key]) {
181
+ drift.push(`${cssVar}: sheet ${contractTier[cssVar]} vs pack ${neutralUi.fonts[key]}`);
175
182
  }
176
183
  }
177
184
  assert.deepEqual(drift, [], 'the contract tier must equal config/branding.neutral.json theme.ui');
@@ -273,8 +280,10 @@ test('each state reads against its own weak fill', () => {
273
280
 
274
281
  test('the contrast probe would CATCH the values that failed', () => {
275
282
  // The shipped-before values, measured against the world's paper. If this ever
276
- // passes, the probe has stopped measuring what it claims to.
277
- const bg = paint('--bg', 'light');
283
+ // passes, the probe has stopped measuring what it claims to. The NEUTRAL paper on
284
+ // purpose: this calibrates the probe, and a look's own ground (UI_DESIGN_PACK) is
285
+ // measured by the pair tests above, not by this one.
286
+ const bg = resolve(neutralUi.colors.bg, 'light');
278
287
  for (const [name, hex] of [['ok', '#1a7f4b'], ['warn', '#9a6700']]) {
279
288
  const r = contrast(resolve(hex, 'light'), bg);
280
289
  assert.ok(r < 4.5, `the retired ${name} ${hex} must still measure under AA here (got ${r.toFixed(2)}:1)`);