@bongos/core 1.19.386 → 1.19.388
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 +138 -53
- package/config/branding.neutral.json +19 -19
- package/config/design-tokens.neutral.json +31 -36
- package/docs/adr/0196-chrome-world-token-contract-and-the-landing-sheets.md +31 -0
- package/docs/adr/README.md +1 -0
- package/docs/branding-contract.md +1 -1
- package/docs/copy-inventory.md +156 -133
- package/docs/copy-registry.json +537 -286
- package/docs/file-map.md +14 -0
- package/modules/dev-box/app/assets/tray/win-alert.png +0 -0
- package/modules/dev-box/app/assets/tray/win-busy.png +0 -0
- package/modules/dev-box/app/assets/tray/win-off.png +0 -0
- package/modules/dev-box/app/assets/tray/win-on.png +0 -0
- package/modules/dev-box/app/build-resources/dmg-background.png +0 -0
- package/modules/dev-box/app/build-resources/dmg-background@2x.png +0 -0
- package/modules/dev-box/app/build-resources/icon.png +0 -0
- package/modules/dev-box/app/renderer/index.html +1 -1
- package/modules/dev-box/app/renderer/styles.css +3 -1
- package/modules/dev-box/app/renderer/theme.generated.css +13 -13
- package/modules/dev-box/app/scripts/gen-brand.js +13 -13
- package/modules/dev-box/app/scripts/gen-dmg-backdrop.py +3 -3
- package/modules/dev-box/app/scripts/gen-icons.js +3 -3
- package/modules/dev-box/app/src/brand.generated.json +1 -1
- package/modules/hall-ui/public/government.css +70 -0
- package/modules/hall-ui/public/government.html +9 -2
- package/modules/hall-ui/public/government.js +175 -32
- package/modules/provisioning/routes/provisioning.js +4 -4
- package/modules/status-ui/public/explore.html +12 -6
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/assets/cosmos.css +82 -64
- package/public-landing/assets/world/og-card.jpg +0 -0
- package/public-landing/assets/world/r2-bongos-chrome-a-cut.webp +0 -0
- package/public-landing/assets/world/r2-bongos-chrome-a-cut.webp.json +12 -0
- package/public-landing/assets/world/r2-bongos-chrome-b.jpg +0 -0
- package/public-landing/assets/world/r2-bongos-chrome-b.jpg.json +12 -0
- package/public-landing/assets/world/r2-chrome-blob-one-cut.webp +0 -0
- package/public-landing/assets/world/r2-chrome-blob-one-cut.webp.json +13 -0
- package/public-landing/assets/world/r2-chrome-blob-one.jpg +0 -0
- package/public-landing/assets/world/r2-chrome-blob-one.jpg.json +12 -0
- package/public-landing/assets/world/r2-chrome-blobs-set-cut.webp +0 -0
- package/public-landing/assets/world/r2-chrome-blobs-set-cut.webp.json +13 -0
- package/public-landing/assets/world/r2-chrome-blobs-set.jpg +0 -0
- package/public-landing/assets/world/r2-chrome-blobs-set.jpg.json +12 -0
- package/public-landing/assets/world.css +241 -0
- package/public-landing/contact.html +1 -1
- package/public-landing/index.html +1139 -580
- package/public-landing/privacy.html +1 -1
- package/public-landing/projects.html +2 -2
- package/public-landing/terms.html +1 -1
- package/src/bongos/platform-visibility-gate.js +8 -7
- package/src/bongos/stealth-gate.js +9 -8
- package/src/module-api.js +5 -1
- package/src/ui/adapters/claude-design/index.js +2 -2
- package/tests/cloud_bongos_pack.mjs +22 -18
- package/tests/cosmos_token_contrast.mjs +23 -8
- package/tests/government_board_room_copy.mjs +131 -0
- package/tests/landing_legal_pages.mjs +8 -5
- package/tests/landing_page.mjs +295 -166
- package/tests/platform_boot.mjs +3 -1
- package/tests/projects_hub.mjs +9 -5
- package/tests/projects_hub_pre_uat.mjs +3 -1
- package/tests/world_tokens.mjs +351 -0
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<link rel="canonical" href="{{publicOrigin}}/privacy">
|
|
14
14
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
15
15
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
16
|
-
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=
|
|
16
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
|
|
17
17
|
<link rel="stylesheet" href="/assets/cosmos.css">
|
|
18
18
|
<script>
|
|
19
19
|
/* Mode, resolved before first paint — the same contract as every landing page. */
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
correctly when viewed standalone. -->
|
|
15
15
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
16
16
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
17
|
-
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=
|
|
17
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
|
|
18
18
|
<!-- The R07 redesign (task 1002696): shared token + component layer — the
|
|
19
19
|
space sky both modes ride on, light/dark governing the cards. The server
|
|
20
20
|
re-injects the branding :root AFTER stylesheets, so the pack stays
|
|
@@ -313,7 +313,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
313
313
|
background:var(--bg-card);border:2px solid var(--line);border-radius:999px;padding:9px 16px;
|
|
314
314
|
transition:all .12s ease;}
|
|
315
315
|
.tpill:hover{border-color:var(--accent);color:var(--accent-deep);}
|
|
316
|
-
.tpill[aria-pressed=true]{background:var(--accent);border-color:var(--accent);color
|
|
316
|
+
.tpill[aria-pressed=true]{background:var(--accent);border-color:var(--accent);color:var(--accent-on);}
|
|
317
317
|
.typeCard{border:2px solid color-mix(in srgb,var(--type-acc) 35%,var(--bg-card));border-radius:18px;
|
|
318
318
|
padding:16px 18px;margin-bottom:6px;
|
|
319
319
|
background:linear-gradient(180deg,color-mix(in srgb,var(--type-acc) 9%,var(--bg-card)) 0%,var(--bg-card) 100%);}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<link rel="canonical" href="{{publicOrigin}}/terms">
|
|
14
14
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
15
15
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
16
|
-
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=
|
|
16
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
|
|
17
17
|
<link rel="stylesheet" href="/assets/cosmos.css">
|
|
18
18
|
<script>
|
|
19
19
|
/* Mode, resolved before first paint — the same contract as every landing page. */
|
|
@@ -153,19 +153,20 @@ function doorHtml({ productName: name, signIn }) {
|
|
|
153
153
|
:root{color-scheme:light dark}
|
|
154
154
|
*{box-sizing:border-box}
|
|
155
155
|
body{margin:0;min-height:100vh;display:grid;place-items:center;padding:24px;
|
|
156
|
-
font:500 15px/1.5
|
|
157
|
-
background:#
|
|
158
|
-
@media(prefers-color-scheme:dark){body{background:#
|
|
156
|
+
font:500 15px/1.5 "Manrope",system-ui,-apple-system,"Segoe UI",sans-serif;
|
|
157
|
+
background:#edecee;color:#17181a}
|
|
158
|
+
@media(prefers-color-scheme:dark){body{background:#000000;color:#ffffff}}
|
|
159
159
|
.box{width:min(380px,100%);text-align:center}
|
|
160
160
|
.dots{display:flex;gap:10px;justify-content:center;margin-bottom:22px}
|
|
161
|
-
.dots i{display:block;border-radius:50%;background:#
|
|
161
|
+
.dots i{display:block;border-radius:50%;background:#fb9569}
|
|
162
162
|
.dots i:first-child{width:38px;height:38px}
|
|
163
|
-
.dots i:last-child{width:28px;height:28px;background:#
|
|
163
|
+
.dots i:last-child{width:28px;height:28px;background:#c9512c;align-self:flex-end}
|
|
164
164
|
h1{font-size:20px;font-weight:700;margin:0 0 6px}
|
|
165
165
|
p{margin:0 0 20px;opacity:.72;font-size:14px}
|
|
166
|
-
|
|
166
|
+
/* dark ink on the accent fill: the chrome world's salmon carries white at 2.19:1 */
|
|
167
|
+
a.btn{display:inline-block;padding:11px 22px;border-radius:999px;background:#fb9569;color:#2a1305;
|
|
167
168
|
font:700 14px/1 inherit;text-decoration:none}
|
|
168
|
-
a.btn:focus{outline:2px solid #
|
|
169
|
+
a.btn:focus{outline:2px solid #a3481a;outline-offset:2px}
|
|
169
170
|
</style></head><body><div class="box">
|
|
170
171
|
<div class="dots"><i></i><i></i></div>
|
|
171
172
|
<h1>${safe}</h1>
|
|
@@ -139,22 +139,23 @@ function formHtml({ productName, wrong, returnTo }) {
|
|
|
139
139
|
:root{color-scheme:light dark}
|
|
140
140
|
*{box-sizing:border-box}
|
|
141
141
|
body{margin:0;min-height:100vh;display:grid;place-items:center;padding:24px;
|
|
142
|
-
font:500 15px/1.5
|
|
143
|
-
background:#
|
|
144
|
-
@media(prefers-color-scheme:dark){body{background:#
|
|
142
|
+
font:500 15px/1.5 "Manrope",system-ui,-apple-system,"Segoe UI",sans-serif;
|
|
143
|
+
background:#edecee;color:#17181a}
|
|
144
|
+
@media(prefers-color-scheme:dark){body{background:#000000;color:#ffffff}}
|
|
145
145
|
.box{width:min(380px,100%);text-align:center}
|
|
146
146
|
.dots{display:flex;gap:10px;justify-content:center;margin-bottom:22px}
|
|
147
|
-
.dots i{display:block;border-radius:50%;background:#
|
|
147
|
+
.dots i{display:block;border-radius:50%;background:#fb9569}
|
|
148
148
|
.dots i:first-child{width:38px;height:38px}
|
|
149
|
-
.dots i:last-child{width:28px;height:28px;background:#
|
|
149
|
+
.dots i:last-child{width:28px;height:28px;background:#c9512c;align-self:flex-end}
|
|
150
150
|
h1{font-size:20px;font-weight:700;margin:0 0 6px}
|
|
151
151
|
p{margin:0 0 20px;opacity:.72;font-size:14px}
|
|
152
152
|
form{display:flex;gap:8px}
|
|
153
|
-
input{flex:1;min-width:0;padding:11px 14px;border-radius:999px;border:1.5px solid rgba(
|
|
153
|
+
input{flex:1;min-width:0;padding:11px 14px;border-radius:999px;border:1.5px solid rgba(163,72,26,.35);
|
|
154
154
|
background:rgba(255,255,255,.72);color:inherit;font:inherit}
|
|
155
155
|
@media(prefers-color-scheme:dark){input{background:rgba(255,255,255,.08)}}
|
|
156
|
-
input:focus{outline:2px solid #
|
|
157
|
-
|
|
156
|
+
input:focus{outline:2px solid #a3481a;outline-offset:1px}
|
|
157
|
+
/* dark ink on the accent fill: the chrome world's salmon carries white at 2.19:1 */
|
|
158
|
+
button{padding:11px 20px;border:0;border-radius:999px;background:#fb9569;color:#2a1305;
|
|
158
159
|
font:700 14px/1 inherit;cursor:pointer}
|
|
159
160
|
.err{margin:14px 0 0;color:#b3261e;font-size:13.5px}
|
|
160
161
|
@media(prefers-color-scheme:dark){.err{color:#ffb4a8}}
|
package/src/module-api.js
CHANGED
|
@@ -1212,8 +1212,12 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
1212
1212
|
// landed since 1.19.384 with no explicit bump. run 33118682829. (task 1002620)
|
|
1213
1213
|
// 1.19.386 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1214
1214
|
// landed since 1.19.385 with no explicit bump. run 33120468176. (task 1002620)
|
|
1215
|
+
// 1.19.387 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1216
|
+
// landed since 1.19.386 with no explicit bump. run 33121066592. (task 1002620)
|
|
1217
|
+
// 1.19.388 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1218
|
+
// landed since 1.19.387 with no explicit bump. run 33125589814. (task 1002620)
|
|
1215
1219
|
// ---------------------------------------------------------------------------
|
|
1216
|
-
const CORE_VERSION = '1.19.
|
|
1220
|
+
const CORE_VERSION = '1.19.388'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
1217
1221
|
|
|
1218
1222
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
1219
1223
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -28,8 +28,8 @@ const FALLBACK_ACCENT = (() => {
|
|
|
28
28
|
try {
|
|
29
29
|
return JSON.parse(fs.readFileSync(
|
|
30
30
|
path.resolve(__dirname, '..', '..', '..', '..', 'config', 'design-tokens.neutral.json'), 'utf8',
|
|
31
|
-
)).colors.accent || '#
|
|
32
|
-
} catch { return '#
|
|
31
|
+
)).colors.accent || '#fb9569';
|
|
32
|
+
} catch { return '#fb9569'; }
|
|
33
33
|
})();
|
|
34
34
|
|
|
35
35
|
// ── token helpers ─────────────────────────────────────────────────────────────
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// tests/cloud_bongos_pack.mjs — the Cloud Bongos vanilla brand pack (R74 / task 1216,
|
|
2
2
|
// ADR 0064). "Verified on the standalone boot", executable: a vanilla instance (no
|
|
3
3
|
// config/branding.json → the neutral starter ALONE) resolves to the Cloud Bongos
|
|
4
|
-
// identity, and the served-HTML theme injection paints the
|
|
4
|
+
// identity, and the served-HTML theme injection paints the chrome look (the chrome
|
|
5
|
+
// world replaced the sunrise on 2026-08-27, task 1003298). This is
|
|
5
6
|
// the customer-0 pack Cloud Bongos itself runs on.
|
|
6
7
|
import assert from 'node:assert/strict';
|
|
7
8
|
import { test } from 'node:test';
|
|
@@ -30,36 +31,39 @@ test('R74: a vanilla boot resolves to the Cloud Bongos identity', () => {
|
|
|
30
31
|
assert.equal(b.envPrefix, 'CLOUDBONGOS');
|
|
31
32
|
});
|
|
32
33
|
|
|
33
|
-
test('R74: the vanilla pack carries the Cloud Bongos LOOK (
|
|
34
|
+
test('R74: the vanilla pack carries the Cloud Bongos LOOK (chrome palette, Manrope, bongo favicon)', () => {
|
|
34
35
|
const c = clientBranding(vanilla());
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
assert.
|
|
42
|
-
assert.match(c.theme.fonts.
|
|
43
|
-
assert.
|
|
36
|
+
// the chrome world (owner, 2026-08-27; task 1003298): a salmon accent that is a
|
|
37
|
+
// FILL, never a text colour — white on it is 2.19:1, so cosmos.css paints labels
|
|
38
|
+
// on it with a derived dark --accent-on and accent WORDS with --accent-deep.
|
|
39
|
+
assert.equal(c.theme.colors.accent, '#fb9569', 'salmon accent');
|
|
40
|
+
// the ground is a cool near-grey paper, not the old marigold #f9d9a8: the chrome
|
|
41
|
+
// sculpture reads as metal on it, and the cards still lift off it.
|
|
42
|
+
assert.equal(c.theme.colors.bg, '#edecee', 'chrome paper base');
|
|
43
|
+
assert.match(c.theme.fonts.display, /Manrope/);
|
|
44
|
+
assert.match(c.theme.fonts.body, /Manrope/);
|
|
45
|
+
assert.ok(c.theme.fonts.links.some((l) => /JetBrains\+Mono/.test(l) && /Manrope/.test(l)), 'loads Manrope + JetBrains Mono');
|
|
44
46
|
assert.match(c.theme.favicon, /circle/, 'bongo-pair favicon (circles), not the Greek Α');
|
|
45
47
|
assert.doesNotMatch(c.theme.favicon, /font-family='serif'>Α/);
|
|
46
|
-
|
|
48
|
+
// the backdrop is the TOKEN, not a hex: the served body follows the mode,
|
|
49
|
+
// because the sheets' dark blocks are (0,2,0) and outrank the injected :root.
|
|
50
|
+
assert.equal(c.theme.backdrop, 'var(--bg)', 'the ground follows the mode all the way down');
|
|
47
51
|
// task 1002934: the owner's homepage rewrite retitled the platform.
|
|
48
52
|
assert.equal(c.copy.tagline, 'Building Digitally, as simple as Bongos.');
|
|
49
53
|
assert.equal(c.rankLabels.archon, 'Owner', 'plain rank labels, not "Archon"');
|
|
50
54
|
});
|
|
51
55
|
|
|
52
|
-
test('R74: serving the hall on a vanilla boot injects the Cloud Bongos
|
|
56
|
+
test('R74: serving the hall on a vanilla boot injects the Cloud Bongos chrome theme', () => {
|
|
53
57
|
const c = clientBranding(vanilla());
|
|
54
58
|
const html = '<head><link rel="stylesheet" href="/builders/style.css"><title>x</title></head><body></body>';
|
|
55
59
|
const out = injectBrandingGlobal(html, c);
|
|
56
|
-
// palette override + the
|
|
57
|
-
assert.match(out, /:root\{[^}]*--accent:#
|
|
58
|
-
assert.match(out, /body\{background
|
|
60
|
+
// palette override + the token-valued body backdrop + bongo favicon + the type links
|
|
61
|
+
assert.match(out, /:root\{[^}]*--accent:#fb9569;/);
|
|
62
|
+
assert.match(out, /body\{background:var\(--bg\);\}/);
|
|
59
63
|
assert.match(out, /<link rel="icon" href="data:image\/svg\+xml[^"]*circle/);
|
|
60
|
-
assert.match(out, /fonts\.googleapis\.com\/css2\?family=
|
|
64
|
+
assert.match(out, /fonts\.googleapis\.com\/css2\?family=JetBrains\+Mono/);
|
|
61
65
|
// and it lands AFTER the stylesheet so the Cloud Bongos look wins
|
|
62
|
-
assert.ok(out.indexOf('/builders/style.css') < out.indexOf('--accent:#
|
|
66
|
+
assert.ok(out.indexOf('/builders/style.css') < out.indexOf('--accent:#fb9569'));
|
|
63
67
|
});
|
|
64
68
|
|
|
65
69
|
test('R74: themeMarkup paints the backdrop only when the pack supplies one', () => {
|
|
@@ -169,8 +169,13 @@ const deltaE76 = (a, b) => {
|
|
|
169
169
|
// need: 4.5 normal text · 3.0 large text and meaningful non-text
|
|
170
170
|
const CARD = 'var(--bg-card)';
|
|
171
171
|
const PAIRS = [
|
|
172
|
-
// primary + secondary action
|
|
173
|
-
|
|
172
|
+
// primary + secondary action.
|
|
173
|
+
// The label ink on an accent FILL is --accent-on since task 1003298 (2026-08-27):
|
|
174
|
+
// the chrome world's salmon accent carries white at 2.19:1, so every filled accent
|
|
175
|
+
// control took a derived DARK ink. (The marigold pack this file was written for
|
|
176
|
+
// shipped a fired-down ember where #ffffff measured 4.61:1 — that is history now,
|
|
177
|
+
// and any prose below still naming those values is history too.)
|
|
178
|
+
['.btn label on its fill', 'var(--accent-on)', 'var(--accent)', 4.5],
|
|
174
179
|
['.btn.ghost:hover label', 'var(--accent-deep)', CARD, 4.5],
|
|
175
180
|
['.btn.danger label', 'var(--err)', CARD, 4.5],
|
|
176
181
|
// Code chips, pills, the now-step number, the hover inks: --accent-deep is
|
|
@@ -212,8 +217,11 @@ const PAIRS = [
|
|
|
212
217
|
['strip indicator on the strip tint', 'var(--sea-ink)', 'color-mix(in srgb,var(--sea) 8%,var(--bg-card))', 3.0],
|
|
213
218
|
['strip done tick on its fill', 'var(--on-ok)', 'var(--sea-ink)', 3.0],
|
|
214
219
|
['flowDown done tick on its fill', 'var(--on-ok)', 'var(--ink-faint)', 3.0],
|
|
215
|
-
|
|
216
|
-
|
|
220
|
+
// the create rail's ring + connector: non-text at 1.4.11's 3:1. --accent-deep,
|
|
221
|
+
// not --accent, since task 1003298 — the chrome world's salmon is 2.10:1 on the
|
|
222
|
+
// card, so the mark that IDENTIFIES a done step had to move to the deep twin.
|
|
223
|
+
['.steps done/now ring on card', 'var(--accent-deep)', CARD, 3.0],
|
|
224
|
+
['.steps done vs idle connector', 'var(--accent-deep)', 'var(--line)', 3.0],
|
|
217
225
|
// the opted-out address card swaps to the page ground (--bg); its muted copy
|
|
218
226
|
// must still measure (19px/700 headline = large text, 3.0)
|
|
219
227
|
['optedOut .addrBig (large) on --bg', 'var(--ink-soft)', 'var(--bg)', 3.0],
|
|
@@ -221,10 +229,11 @@ const PAIRS = [
|
|
|
221
229
|
// ?view=mine (task 1002823) — both surfaces traded a subtree opacity for muted
|
|
222
230
|
// tokens, so the tokens themselves now carry the de-emphasis and must measure.
|
|
223
231
|
// The two pill rows are the pair that proves the split: one mute cannot serve
|
|
224
|
-
// both grounds, because on the pressed --accent fill no ink token clears
|
|
225
|
-
//
|
|
232
|
+
// both grounds, because on the pressed --accent fill no muted ink token clears
|
|
233
|
+
// 4.5 — the count inherits the button's own label ink, which is --accent-on
|
|
234
|
+
// since task 1003298 (it was the button's #fff under the marigold pack).
|
|
226
235
|
['.filters .n count, resting pill', 'var(--ink-faint)', CARD, 4.5],
|
|
227
|
-
['.filters .n count, pressed pill', '
|
|
236
|
+
['.filters .n count, pressed pill', 'var(--accent-on)', 'var(--accent)', 4.5],
|
|
228
237
|
['.proj.gone name (muted)', 'var(--ink-soft)', CARD, 4.5],
|
|
229
238
|
['.proj.gone meta (dim removed)', 'var(--ink-faint)', CARD, 4.5],
|
|
230
239
|
// lifecycle strip: 12px/700
|
|
@@ -265,7 +274,13 @@ const PAIRS = [
|
|
|
265
274
|
['--ctl-line boundary on the card', 'var(--ctl-line)', 'var(--card)', 3.0],
|
|
266
275
|
['--ctl-line boundary vs input fill', 'var(--ctl-line)', CARD, 3.0],
|
|
267
276
|
['?view=new input ::placeholder', 'var(--ink-faint)', CARD, 4.5],
|
|
268
|
-
|
|
277
|
+
// the repo-picker panel's own ground. Retargeted 2026-08-27 (task 1003298) from
|
|
278
|
+
// --ink-faint to --ink-soft: under the chrome palette the pack's --ink-faint
|
|
279
|
+
// measures 4.45:1 on --bg-deep, and NO rule paints it there — .ghlist's own copy
|
|
280
|
+
// is .ghrow (--ink) and .ghrow .priv (--ink-soft); the .hint blocks are siblings
|
|
281
|
+
// of #ghList and sit on the card. So the floor now measures the ink the deep
|
|
282
|
+
// panel actually uses, instead of a pairing nothing renders.
|
|
283
|
+
['?view=new .ghlist copy on deep', 'var(--ink-soft)', 'var(--bg-deep)', 4.5],
|
|
269
284
|
['--chrome-accent wordmark', 'var(--chrome-accent)', 'var(--page-bg)', 4.5],
|
|
270
285
|
['--chrome-ink', 'var(--chrome-ink)', 'var(--page-bg)', 4.5],
|
|
271
286
|
['--foot-ink footer copy', 'var(--foot-ink)', 'var(--page-bg)', 4.5],
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// tests/government_board_room_copy.mjs — the Board Room card reads as one
|
|
2
|
+
// decision (task 1003270, goal 1000068).
|
|
3
|
+
//
|
|
4
|
+
// The owner's report was that the room was "extremely poor and unclear": the
|
|
5
|
+
// idea being voted on was collapsed while raw config keys — `first_ratifier`,
|
|
6
|
+
// `rank:archon`, `why_matters` — were expanded above it, and the Vote button
|
|
7
|
+
// sat under a wall of prose. Copy rots back, so the two rules that came out of
|
|
8
|
+
// that pass are pinned here rather than left to the next reader's judgment:
|
|
9
|
+
//
|
|
10
|
+
// 1. NO RAW CONFIG KEY REACHES A READER. Every pass rule the server can hold
|
|
11
|
+
// has a plain sentence, checked against the REAL PASS_RULES list — a rule
|
|
12
|
+
// added server-side with no copy fails here instead of shipping a card
|
|
13
|
+
// that prints its enum. Rank keys go through OTB.rankLabel.
|
|
14
|
+
// 2. THE ORDER IS title → action → idea → apparatus. That order IS the fix;
|
|
15
|
+
// an edit that puts prose back between the title and the ballot has undone
|
|
16
|
+
// the task.
|
|
17
|
+
//
|
|
18
|
+
// Source pins, like tests/government_board_vote_ui.mjs: the hall script is an
|
|
19
|
+
// IIFE over the DOM and cannot be imported into node wholesale.
|
|
20
|
+
import assert from 'node:assert/strict';
|
|
21
|
+
import { test } from 'node:test';
|
|
22
|
+
import { createRequire } from 'node:module';
|
|
23
|
+
import fs from 'node:fs';
|
|
24
|
+
import path from 'node:path';
|
|
25
|
+
import { fileURLToPath } from 'node:url';
|
|
26
|
+
|
|
27
|
+
const require = createRequire(import.meta.url);
|
|
28
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
29
|
+
const CLIENT = fs.readFileSync(path.join(ROOT, 'modules', 'hall-ui', 'public', 'government.js'), 'utf8');
|
|
30
|
+
const HTML = fs.readFileSync(path.join(ROOT, 'modules', 'hall-ui', 'public', 'government.html'), 'utf8');
|
|
31
|
+
const { PASS_RULES } = require(path.join(ROOT, 'modules', 'government', 'config.js'));
|
|
32
|
+
|
|
33
|
+
const card = () => CLIENT.slice(CLIENT.indexOf('function itemCard'), CLIENT.indexOf('async function loadBoard'));
|
|
34
|
+
|
|
35
|
+
// Deleted copy is asserted against the RENDERING source only. The comments in
|
|
36
|
+
// government.js deliberately quote what was removed and why — that history is
|
|
37
|
+
// the point of them, and a test that forbade it would push the record out of
|
|
38
|
+
// the file it explains.
|
|
39
|
+
const CODE = CLIENT.split(/\r?\n/).filter((l) => !/^\s*\/\//.test(l)).join('\n');
|
|
40
|
+
|
|
41
|
+
test('every pass rule the server can hold has a plain-language sentence', () => {
|
|
42
|
+
const block = CLIENT.slice(CLIENT.indexOf('const PASS_RULE_PLAIN'), CLIENT.indexOf('function decidedByHtml'));
|
|
43
|
+
assert.ok(PASS_RULES.length >= 3, `sanity: found the real rule set (${PASS_RULES})`);
|
|
44
|
+
for (const rule of PASS_RULES) {
|
|
45
|
+
assert.ok(block.includes(`${rule}:`), `PASS_RULE_PLAIN must explain '${rule}' — a rule with no copy renders its enum key`);
|
|
46
|
+
}
|
|
47
|
+
// And the sentence must not simply echo the key back at the reader.
|
|
48
|
+
for (const rule of PASS_RULES) {
|
|
49
|
+
const line = block.split('\n').find((l) => l.trim().startsWith(`${rule}:`));
|
|
50
|
+
assert.ok(!/'[^']*\b(first_ratifier|consent_rule|pass_rule)\b/.test(line), `'${rule}' copy leaks a config key: ${line}`);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('the short name map covers the same rule set (the history line)', () => {
|
|
55
|
+
const block = CLIENT.slice(CLIENT.indexOf('const PASS_RULE_NAME'), CLIENT.indexOf('const PASS_RULE_PLAIN'));
|
|
56
|
+
for (const rule of PASS_RULES) {
|
|
57
|
+
assert.ok(block.includes(`${rule}:`), `PASS_RULE_NAME must name '${rule}'`);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('a rank reaches the reader as a LABEL, never as the enum key', () => {
|
|
62
|
+
assert.match(CLIENT, /function rankNames\(keys\)[\s\S]*?window\.OTB\.rankLabel\(k\)/,
|
|
63
|
+
'the board card resolves rank keys through the branding pack');
|
|
64
|
+
const membership = CLIENT.slice(CLIENT.indexOf('function membershipSentence'), CLIENT.indexOf('function passRuleSentence'));
|
|
65
|
+
assert.match(membership, /window\.OTB\.rankLabel\(k\)/,
|
|
66
|
+
'so does the Constitution sentence — `archon` is an enum, "Owner" is what a person reads');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('the card order is title → action → the idea → the apparatus', () => {
|
|
70
|
+
const c = card();
|
|
71
|
+
const at = (needle) => {
|
|
72
|
+
const i = c.indexOf(needle);
|
|
73
|
+
assert.notEqual(i, -1, `itemCard no longer contains ${needle}`);
|
|
74
|
+
return i;
|
|
75
|
+
};
|
|
76
|
+
assert.ok(at('gov-item__title') < at('gov-item__act'),
|
|
77
|
+
'the action follows the title directly — that adjacency is the whole fix');
|
|
78
|
+
assert.ok(at('gov-item__act') < at('ideaSectionsHtml(item)'),
|
|
79
|
+
'nothing may be inserted between the title and the ballot');
|
|
80
|
+
assert.ok(at('ideaSectionsHtml(item)') < at('decidedByHtml(item)'),
|
|
81
|
+
'the idea outranks the rules it is decided under');
|
|
82
|
+
assert.ok(at('decidedByHtml(item)') < at('ballotHtml(item, viewerId)'),
|
|
83
|
+
'who votes, then who has');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('the idea renders OPEN — never behind a summary', () => {
|
|
87
|
+
const sections = CLIENT.slice(CLIENT.indexOf('function ideaSectionsHtml'), CLIENT.indexOf('function rankNames'));
|
|
88
|
+
assert.ok(!/<details/.test(sections),
|
|
89
|
+
'the substance of the vote is not a disclosure — that was the reported bug');
|
|
90
|
+
assert.match(sections, /gov-idea__body/, 'and it is set as prose, not as a config row');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('the tally stays visible without a click (ADR 0175 §9)', () => {
|
|
94
|
+
const c = card();
|
|
95
|
+
assert.match(c, /gov-item__summary">\$\{tally\}/,
|
|
96
|
+
'a board whose votes are invisible constrains nobody — the count rides the summary');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('the self-explaining copy is DELETED, not reworded', () => {
|
|
100
|
+
assert.ok(!/Casting arrives with the vote form/.test(CODE),
|
|
101
|
+
'the sentence that explained the UI to itself — and the form it promised has shipped');
|
|
102
|
+
assert.ok(!/tally: \$\{yes\} yes/.test(CLIENT), 'the raw-config tally line is gone');
|
|
103
|
+
assert.ok(!/The idea, in full/.test(CODE), 'so is the summary that hid the idea');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('the section a voter faults is shown by NAME, in the ballot and the picker', () => {
|
|
107
|
+
const ballot = CLIENT.slice(CLIENT.indexOf('function ballotHtml'), CLIENT.indexOf('// ---- the vote form'));
|
|
108
|
+
assert.match(ballot, /SECTION_LABELS\[v\.section_key\]/,
|
|
109
|
+
'`why_matters` is a jsonb key; the objector picked a named section and the reader should see that name');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('the objection still carries a reason AND one section (ADR 0175 §8)', () => {
|
|
113
|
+
// The requirement simplifying may not weaken: it is why voting lives in the
|
|
114
|
+
// hall instead of on a chat reaction.
|
|
115
|
+
const form = CLIENT.slice(CLIENT.indexOf('function voteFormHtml'), CLIENT.indexOf('async function castVoteFromRoom'));
|
|
116
|
+
assert.match(form, /data-vote-reason/, 'the reason box survives');
|
|
117
|
+
assert.match(form, /gov-vote__sections/, 'so does the five-way section picker');
|
|
118
|
+
assert.match(form, /Object\.keys\(SECTION_LABELS\)\.map/, 'with one radio per declared section');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('the page frames its panels in one line each, not a paragraph', () => {
|
|
122
|
+
// The two intros used to run ~60 and ~50 words before anything actionable.
|
|
123
|
+
for (const heading of ['Constitution', 'Board Room']) {
|
|
124
|
+
const i = HTML.indexOf(`<h2 class="gov-h">${heading}</h2>`);
|
|
125
|
+
assert.notEqual(i, -1, `${heading} panel heading missing`);
|
|
126
|
+
const note = /<p class="gov-grid-note">([\s\S]*?)<\/p>/.exec(HTML.slice(i));
|
|
127
|
+
assert.ok(note, `${heading} has no framing line`);
|
|
128
|
+
const words = note[1].replace(/<[^>]+>/g, ' ').trim().split(/\s+/).length;
|
|
129
|
+
assert.ok(words <= 30, `${heading}'s intro is ${words} words — a reader meets it before anything actionable`);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
@@ -94,13 +94,16 @@ test('the currency token reads as the instance names it (terms)', () => {
|
|
|
94
94
|
test('the landing page and the hub footers link privacy, terms and contact', () => {
|
|
95
95
|
for (const file of ['index.html', 'projects.html']) {
|
|
96
96
|
const html = read(file);
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
// the landing's footer is a bare <footer> since task 1003298 (the chrome world);
|
|
98
|
+
// the hub still carries the cosmos.css <div class="foot"> until task 1003299
|
|
99
|
+
const m = html.match(/<footer[^>]*>([\s\S]*?)<\/footer>/) || html.match(/<div class="foot">([\s\S]*?)<\/div>/);
|
|
100
|
+
assert.ok(m, `${file}: the footer survives`);
|
|
101
|
+
const foot = m[1];
|
|
99
102
|
for (const href of ['/privacy', '/terms', '/contact']) {
|
|
100
|
-
assert.match(foot
|
|
103
|
+
assert.match(foot, new RegExp(`<a href="${href}">`), `${file}: footer links ${href}`);
|
|
101
104
|
}
|
|
102
|
-
assert.match(foot
|
|
103
|
-
assert.match(foot
|
|
105
|
+
assert.match(foot, /Open source \(AGPL-3\.0\)/, `${file}: the licence link stays`);
|
|
106
|
+
assert.match(foot, /Built by agents/, `${file}: the attribution half stays`);
|
|
104
107
|
}
|
|
105
108
|
});
|
|
106
109
|
|