@bongos/core 1.19.393 → 1.19.395
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 +108 -58
- package/.claude/skills/design/SKILL.md +68 -0
- package/.claude/skills/design-sync/SKILL.md +2 -2
- package/.claude/skills/figma-design-sync/SKILL.md +3 -3
- package/config/design-tokens.neutral.json +1 -1
- package/config/design-tokens.schema.json +1 -1
- package/docs/adr/0197-the-ui-design-module.md +52 -0
- package/docs/adr/README.md +1 -0
- package/docs/architecture.md +1 -1
- package/docs/copy-inventory.md +249 -236
- package/docs/copy-registry.json +543 -324
- package/docs/file-map.md +2 -0
- package/docs/modules-contract.md +1 -0
- package/docs/onboarding/slash-commands.md +1 -1
- package/docs/recipes/claude-design-loop.md +3 -3
- package/docs/recipes/figma-design-loop.md +4 -4
- package/modules/ui-design/CLAUDE.md +15 -0
- package/{src/ui → modules/ui-design}/adapters/claude-design/index.js +1 -1
- package/{src/ui → modules/ui-design}/adapters/figma/index.js +3 -3
- package/{docs → modules/ui-design/docs}/design-contract.md +12 -12
- package/modules/ui-design/kit/README.md +3 -0
- package/modules/ui-design/module.json +11 -0
- package/modules/ui-design/scripts/design-sync.js +65 -0
- package/modules/ui-design/scripts/figma-design-sync.js +64 -0
- package/modules/ui-design/scripts/validate-design.js +258 -0
- package/modules/ui-design/skills/README.md +3 -0
- package/modules/ui-design/styles/README.md +3 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/assets/cosmos.css +235 -103
- package/public-landing/index.html +228 -75
- package/public-landing/projects.html +256 -140
- package/scripts/gds/copy-inventory.js +7 -5
- package/scripts/gds/design-sync.js +9 -59
- package/scripts/gds/doc-cli-guard.js +11 -5
- package/scripts/gds/figma-design-sync.js +9 -59
- package/scripts/gds/fitness-checks-identity.js +5 -3
- package/scripts/gds/publish-manifest.js +1 -1
- package/scripts/gds/validate-design.js +7 -249
- package/src/bongos/module-scope-map.js +18 -0
- package/src/module-api.js +5 -1
- package/tests/design_tokens_sync.mjs +1 -1
- package/tests/doc_cli_guard.mjs +19 -0
- package/tests/figma_adapter.mjs +1 -1
- package/tests/landing_page.mjs +72 -2
- package/tests/module_loader.mjs +4 -1
- package/tests/projects_hub.mjs +52 -73
- package/tests/projects_hub_pre_uat.mjs +10 -8
package/tests/landing_page.mjs
CHANGED
|
@@ -195,8 +195,9 @@ test('the social card is the world card, stamped onto THIS instance', () => {
|
|
|
195
195
|
test('every cross-host link is stamped to THIS instance, never the platform', () => {
|
|
196
196
|
const out = stamp();
|
|
197
197
|
const halls = [...out.matchAll(/data-link-hall href="([^"]*)"/g)].map((m) => m[1]);
|
|
198
|
-
// the
|
|
199
|
-
|
|
198
|
+
// the footer entry (the bar carries no hall link since task 1003300 — the hall is a
|
|
199
|
+
// per-project affordance and the footer's door, on both pages)
|
|
200
|
+
assert.ok(halls.length >= 1, 'the footer points at the hall');
|
|
200
201
|
for (const href of halls) assert.equal(href, 'https://builders.spike-harbor.example');
|
|
201
202
|
// C6: this is the bug the stamping fixes — clientBranding() omits domains, so a
|
|
202
203
|
// client fill can never re-point these and every child sent visitors to the platform.
|
|
@@ -453,3 +454,72 @@ test('the page satisfies the parked platform_boot assertions', () => {
|
|
|
453
454
|
// an ORG-level action that lives only on the gate — asserted absent there
|
|
454
455
|
assert.doesNotMatch(HTML, /Create a project/);
|
|
455
456
|
});
|
|
457
|
+
|
|
458
|
+
// ── the bar and the bands (task 1003300) ───────────────────────────────────
|
|
459
|
+
// The hub's chrome on the front door: one hairline bar, the Projects band and
|
|
460
|
+
// the profile band. Everything a visitor can reach is markup; the script only
|
|
461
|
+
// opens and closes. These pin the shape the grader could not see from a diff.
|
|
462
|
+
|
|
463
|
+
test('the bar is markup: the mark, the Projects trigger, the mode glyph, the sign-in door (task 1003300)', () => {
|
|
464
|
+
const body = HTML.split('<body>')[1].split('<script>')[0];
|
|
465
|
+
assert.match(body, /<header class="topbar bar">/, 'one bar, the hub\'s class beside the page\'s');
|
|
466
|
+
assert.match(body, /<a class="barMark" href="\/" aria-label="\{\{productName\}\}">\s*<svg viewBox="0 0 26 20"/, 'the static mark is the way home, labelled from the stamped name');
|
|
467
|
+
assert.match(body, /<span class="mark-word">\{\{productName\}\}<\/span>/, 'the product name as plain type, folded away under 900');
|
|
468
|
+
assert.match(body, /<button class="trig" type="button" id="projTrig" data-menu="projects" aria-expanded="false" aria-controls="menuProjects">Projects<svg class="cv"/);
|
|
469
|
+
assert.match(body, /<button class="modeBtn" id="mode" type="button" aria-label="Toggle light or dark">/);
|
|
470
|
+
assert.match(body, /<span class="whoSlot" id="who">\s*<a class="pill signin" href="\/api\/bongos\/auth\/web\/start\?return=\/">Sign in<\/a>\s*<\/span>/, 'the door stands in the identity slot until the session resolves');
|
|
471
|
+
// what left with the pill bar
|
|
472
|
+
assert.doesNotMatch(body, /class="pill nav-pill"|class="right-pills"|class="pill mark-pill"|data-nav="home"/, 'no nav pill, no right pills, no Home');
|
|
473
|
+
assert.doesNotMatch(body.slice(body.indexOf('<header'), body.indexOf('</header>')), /data-link-hall/, 'the bar carries no hall link — the footer does');
|
|
474
|
+
assert.doesNotMatch(PAGE_CSS, /--sky-topscrim|\.topbar::before/, 'the top wash left with the grounded bar');
|
|
475
|
+
assert.match(PAGE_CSS, /\.topbar\{position:relative;z-index:50;height:var\(--top-h\);display:flex;align-items:center;gap:22px;\s*padding:0 26px;background:var\(--bg\);border-bottom:1px solid var\(--rule\)\}/, 'the page ground closed by one hairline, z-index unified with the hub');
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
test('the Projects band deep-links the hub\'s views; About and Docs sit under a hairline; no item is current here', () => {
|
|
479
|
+
const body = HTML.split('<body>')[1].split('<script>')[0];
|
|
480
|
+
assert.match(body, /<div class="menuBand" id="menuProjects" data-menu="projects" hidden>/);
|
|
481
|
+
assert.match(body, /<a class="bandT" href="\/projects\?view=map">Sky<\/a>/);
|
|
482
|
+
assert.match(body, /<a class="bandT" href="\/projects\?view=map&sub=table">Table<\/a>/, 'the Table item lands on the table (the hub reads sub=table on boot)');
|
|
483
|
+
assert.match(body, /<li class="bandCell" id="mineCell" hidden><a class="bandT" href="\/projects\?view=mine">Your projects<\/a>/, 'member-gated: hidden until the session resolves');
|
|
484
|
+
assert.match(body, /<a class="bandT" href="\/projects\?view=new">New project<\/a>/);
|
|
485
|
+
assert.match(body, /<ul class="bandRow bandMore">\s*<li class="bandCell"><a class="bandT" href="#about">About<\/a><\/li>\s*<li class="bandCell"><a class="bandT" href="\/docs">Docs<\/a><\/li>/, 'About and Docs under the hairline (§5)');
|
|
486
|
+
assert.match(body, /<section class="band" id="about">/, 'About has a real anchor: the lifecycle band');
|
|
487
|
+
assert.doesNotMatch(body.split('<main')[0], /aria-current/, 'the landing is none of those views');
|
|
488
|
+
// the profile band: the person, My projects, the deferred slot, Sign out as the honest anchor
|
|
489
|
+
assert.match(body, /<div class="menuBand" id="menuProfile" data-menu="profile" hidden>/);
|
|
490
|
+
assert.match(body, /<p class="bandWho" id="bandWho"><\/p>/);
|
|
491
|
+
assert.match(body, /<a class="bandT" href="\/projects\?view=mine">My projects<\/a>/);
|
|
492
|
+
assert.match(body, /<li class="bandSlot" aria-hidden="true"><\/li>/);
|
|
493
|
+
assert.match(body, /<a class="bandT signout" href="\/api\/bongos\/auth\/logout">Sign out<\/a>/);
|
|
494
|
+
// a crawler reads every destination without scripting on
|
|
495
|
+
for (const copy of ['Sky', 'Table', 'Your projects', 'New project', 'About', 'Docs', 'My projects', 'Sign out']) {
|
|
496
|
+
assert.ok(body.includes('>' + copy + '<'), `"${copy}" is only reachable with scripting on`);
|
|
497
|
+
}
|
|
498
|
+
// the menu container is .menuBand: .band is this page's own content sections
|
|
499
|
+
assert.match(PAGE_CSS, /\.menuBand\{position:relative;z-index:49;background:var\(--bg-card\);border-bottom:1px solid var\(--rule\);/);
|
|
500
|
+
assert.doesNotMatch(PAGE_CSS, /\.band\{[^}]*animation:bandIn/, 'the menu rule never lands on the content bands');
|
|
501
|
+
assert.match(PAGE_CSS, /\.bandMore \.bandT\{font-size:17px;font-weight:400;min-height:24px;display:inline-flex;align-items:center\}/, 'the About/Docs links clear the 24px floor');
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
test('the bands are one disclosure: Escape returns focus, arrows cycle, Tab out closes, one closes the other', () => {
|
|
505
|
+
assert.match(CODE, /function openBand\(\) \{ return document\.querySelector\('\.menuBand:not\(\[hidden\]\)'\); \}/);
|
|
506
|
+
assert.match(CODE, /function closeMenus\(\) \{[\s\S]*?if \(inside && trig\) trig\.focus\(\);/, 'focus restoration is a property of the close, not of one caller');
|
|
507
|
+
assert.match(CODE, /function openMenu\(trig, byKeyboard\) \{[\s\S]*?closeMenus\(\);[\s\S]*?if \(byKeyboard\) \{ var first = band\.querySelector\('\.bandCell:not\(\[hidden\]\) a'\); if \(first\) first\.focus\(\); \}/, 'opening one closes the other; Enter lands on the first visible item');
|
|
508
|
+
assert.match(CODE, /else openMenu\(t, ev\.detail === 0\);/, 'a keyboard-fired click is told apart from a pointer');
|
|
509
|
+
assert.match(CODE, /if \(ev\.key === 'Escape'\) \{ closeMenus\(\); return; \}/);
|
|
510
|
+
assert.match(CODE, /if \(ev\.key !== 'ArrowDown' && ev\.key !== 'ArrowUp'\) return;/);
|
|
511
|
+
assert.match(CODE, /return !a\.closest\('\[hidden\]'\);/, 'the arrows skip the hidden Your-projects cell');
|
|
512
|
+
assert.match(CODE, /b\.addEventListener\('focusout', function \(ev\) \{[\s\S]*?if \(!to \|\| b\.contains\(to\) \|\| \(to\.closest && to\.closest\('\.trig'\)\)\) return;[\s\S]*?closeMenus\(\);/, 'Tab out closes; a null relatedTarget is left alone');
|
|
513
|
+
assert.match(CODE, /if \(ev\.target\.closest && \(ev\.target\.closest\('\.menuBand'\) \|\| ev\.target\.closest\('\.trig'\)\)\) return;[\s\S]*?closeMenus\(\);/, 'a click outside closes');
|
|
514
|
+
assert.doesNotMatch(HTML, /role="menu"|aria-haspopup/, 'plain links in a disclosure — what is announced is what ships');
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
test('the chip is the profile trigger, built with createElement; Sign out is the POST', () => {
|
|
518
|
+
assert.match(CODE, /var chip = document\.createElement\('button'\);[\s\S]*?chip\.className = 'trig who';[\s\S]*?chip\.setAttribute\('data-menu', 'profile'\);[\s\S]*?chip\.setAttribute\('aria-controls', 'menuProfile'\);/);
|
|
519
|
+
assert.match(CODE, /chip\.setAttribute\('aria-label', 'Account, ' \+ me\.github_login\);/, 'the trigger names the account');
|
|
520
|
+
assert.match(CODE, /wireTrigger\(chip\);/, 'a chip built after boot is wired like the static trigger');
|
|
521
|
+
assert.match(CODE, /if \(who\) who\.appendChild\(document\.createTextNode\(me\.github_login\)\);/, 'the band names the person');
|
|
522
|
+
assert.match(CODE, /if \(mine\) mine\.hidden = false;/, 'a member sees the Your-projects cell');
|
|
523
|
+
assert.match(CODE, /var signout = document\.querySelector\('#menuProfile a\.signout'\);\s*if \(signout\) signout\.addEventListener\('click', function \(e\) \{ e\.preventDefault\(\); doLogout\(\); \}\);/, 'the honest href, upgraded to the POST');
|
|
524
|
+
assert.doesNotMatch(CODE, /className = 'pill signout'/, 'no sign-out pill in the bar any more');
|
|
525
|
+
});
|
package/tests/module_loader.mjs
CHANGED
|
@@ -176,6 +176,9 @@ test('the live tree discovers the extracted modules cleanly (BV1.R43/R48/R47/R49
|
|
|
176
176
|
// off in enabledModules(). And copy-desk (task 1003113 / R02 of goal 1000074) —
|
|
177
177
|
// the copy half of the design contract: the flag + the artist's queue over
|
|
178
178
|
// user-facing text, default-on because every instance shows a person words.
|
|
179
|
+
// And ui-design (task 1003324 / ADR 0197) — the design contract, its adapters
|
|
180
|
+
// and the /design playbook as a platform capability: declaration-only (three
|
|
181
|
+
// skills, no routes/port), default-on because every instance has a face.
|
|
179
182
|
// All are discovered; none has validation
|
|
180
183
|
// errors or coreVersion mismatches. Order is the readdir sort.
|
|
181
184
|
//
|
|
@@ -200,7 +203,7 @@ test('the live tree discovers the extracted modules cleanly (BV1.R43/R48/R47/R49
|
|
|
200
203
|
// That is precisely how the agents regression reached an open PR (task 1002485).
|
|
201
204
|
loader._reset();
|
|
202
205
|
const snap = loader.loaderSnapshot();
|
|
203
|
-
assert.deepEqual(snap.loaded, ['agents', 'autonomy', 'builder-settings', 'copy-desk', 'dev-box', 'discord', 'economy', 'government', 'grading', 'hall-ui', 'ideas', 'lifecycle', 'memory', 'onboarding', 'platform-identity', 'provisioning', 'security', 'sessions', 'status-ui']);
|
|
206
|
+
assert.deepEqual(snap.loaded, ['agents', 'autonomy', 'builder-settings', 'copy-desk', 'dev-box', 'discord', 'economy', 'government', 'grading', 'hall-ui', 'ideas', 'lifecycle', 'memory', 'onboarding', 'platform-identity', 'provisioning', 'security', 'sessions', 'status-ui', 'ui-design']);
|
|
204
207
|
assert.deepEqual(snap.errors, []);
|
|
205
208
|
assert.deepEqual(snap.skipped, []);
|
|
206
209
|
loader._reset();
|
package/tests/projects_hub.mjs
CHANGED
|
@@ -36,17 +36,23 @@ test('all three views ship as real markup, not built by script (C7)', () => {
|
|
|
36
36
|
assert.match(HUB, /Your projects/);
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
test('the
|
|
40
|
-
assert.
|
|
39
|
+
test('the Projects band carries the views; Home is gone and the mark is the way home (task 1003300)', () => {
|
|
40
|
+
assert.doesNotMatch(HUB, /data-nav="home"/, 'no Home item — the logo is home');
|
|
41
|
+
assert.match(HUB, /<a class="barMark" id="barMark" href="\/">/, 'the mark links home');
|
|
41
42
|
for (const v of ['map', 'mine', 'new']) {
|
|
42
43
|
assert.match(HUB, new RegExp(`href="\\?view=${v}"\\s+data-nav="${v}"`),
|
|
43
|
-
`the
|
|
44
|
+
`the band must deep-link ?view=${v}`);
|
|
44
45
|
}
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
assert.match(
|
|
49
|
-
|
|
46
|
+
// Sky and Table are two VIEWS of the map (direction record §7): both are the
|
|
47
|
+
// map deep link, and the sub-view rides on the item so setSub lights the one
|
|
48
|
+
// on stage; the Sky|Table seg and the toolbar row it sat in are retired
|
|
49
|
+
assert.match(HUB, /href="\?view=map" data-nav="map" data-sub="sky" aria-current="page">Sky<\/a>/);
|
|
50
|
+
assert.match(HUB, /href="\?view=map&sub=table" data-nav="map" data-sub="table">Table<\/a>/);
|
|
51
|
+
assert.match(SCRIPT, /get\('sub'\) === 'table'\) setSub\('table'\);/, 'the Table deep link lands on the table');
|
|
52
|
+
assert.match(HUB, /<a class="bandT" href="\/#about">About<\/a>/, 'About sits under the hairline (§5)');
|
|
53
|
+
assert.doesNotMatch(HUB, /id="sub-sky"|id="sub-table"|class="toolbar"|class="seg"/, 'the toolbar and its seg are retired');
|
|
54
|
+
assert.doesNotMatch(SCRIPT, /dataset\.nav === 'home'/, 'no dead Home branch in the router');
|
|
55
|
+
assert.match(SCRIPT, /if \(a\.dataset\.sub\) setSub\(a\.dataset\.sub\);/, 'a band item may pick the map sub-view');
|
|
50
56
|
});
|
|
51
57
|
|
|
52
58
|
test('?view= is the deep-link contract, read on boot and written with replaceState', () => {
|
|
@@ -113,24 +119,35 @@ test('the hall link is not labelled "Enter the Hall"', () => {
|
|
|
113
119
|
assert.doesNotMatch(HUB, /Enter the Hall/i);
|
|
114
120
|
});
|
|
115
121
|
|
|
116
|
-
test('the
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
assert.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
assert.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
assert.
|
|
131
|
-
|
|
132
|
-
assert.match(
|
|
133
|
-
|
|
122
|
+
test('the hub carries the static mark; the self-typing wordmark lives on the landing only (task 1003300, §7)', () => {
|
|
123
|
+
// The owner's ruling with the v2 pick: the typing wordmark "can probably only
|
|
124
|
+
// live on the landing page". Here the bongo-pair glyph and the product name
|
|
125
|
+
// are plain type; the mark is the only way home and is labelled from the
|
|
126
|
+
// product name a re-branded instance stamps, never from its visible text.
|
|
127
|
+
assert.match(HUB, /<a class="barMark" id="barMark" href="\/">\s*<svg viewBox="0 0 26 20" aria-hidden="true"/);
|
|
128
|
+
assert.match(HUB, /<span data-brand-product-inline>Cloud Bongos<\/span>\s*<\/a>/);
|
|
129
|
+
assert.doesNotMatch(HUB, /id="markLeft"|id="markPrefix"|id="markWord"|id="markMeasure"|class="caret"/, 'no typing apparatus on the hub');
|
|
130
|
+
assert.doesNotMatch(SCRIPT, /function typeMark|var PREFIXES/, 'no typing loop on the hub');
|
|
131
|
+
assert.match(SCRIPT, /\$\('barMark'\)\.setAttribute\('aria-label', productName \+ ' — home'\);/);
|
|
132
|
+
assert.doesNotMatch(COSMOS, /\.shell \.mark|\.shell \.caret|cbBlink|\.navrow/, 'the wordmark and nav-row rules left cosmos with the markup');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('the bands: one disclosure, two instances — Escape returns focus, the card yields, the search hides off the map (task 1003300)', () => {
|
|
136
|
+
assert.match(HUB, /<button class="trig" type="button" id="projTrig" data-menu="projects" aria-expanded="false" aria-controls="menuProjects">Projects/);
|
|
137
|
+
assert.match(HUB, /<button class="trig who" type="button" id="who" data-menu="profile" aria-expanded="false" aria-controls="menuProfile" hidden><\/button>/, 'signed out the profile trigger does not exist');
|
|
138
|
+
assert.match(HUB, /<div class="menuBand" id="menuProjects" data-menu="projects" hidden>/);
|
|
139
|
+
assert.match(HUB, /<div class="menuBand" id="menuProfile" data-menu="profile" hidden>/);
|
|
140
|
+
assert.doesNotMatch(HUB, /role="menu"|aria-haspopup/, 'plain links in a disclosure — announced as what ships');
|
|
141
|
+
assert.match(SCRIPT, /function closeMenus\(\) \{[\s\S]*?if \(inside && trig\) trig\.focus\(\);/, 'focus restoration is a property of the close, not of one caller');
|
|
142
|
+
assert.match(SCRIPT, /if \(ev\.key === 'Escape' && !openBand\(\)\) closeCard\(\);/, 'the card yields Escape to an open band');
|
|
143
|
+
assert.match(SCRIPT, /function setView\(v, focus\) \{[\s\S]*?\$\('qWrap'\)\.hidden = \$\('qToggle'\)\.hidden = \(v !== 'map'\);/, 'the search hides off the map in the router');
|
|
144
|
+
const identity = (SCRIPT.match(/function paintShellIdentity\(\) \{([\s\S]*?)\n \}/) || [, ''])[1];
|
|
145
|
+
assert.ok(identity, 'paintShellIdentity must exist');
|
|
146
|
+
assert.doesNotMatch(identity, /qWrap|mapQ/, 'the search is public — never gated on identity');
|
|
147
|
+
assert.match(identity, /querySelectorAll\('\[data-nav="mine"\]'\)/, 'every mine item, never the singular query (§6)');
|
|
148
|
+
assert.match(HUB, /<a class="bandT signout" href="\/api\/bongos\/auth\/logout">Sign out<\/a>/, 'sign out is the honest hover target');
|
|
149
|
+
assert.match(SCRIPT, /\$\('menuProfile'\)\.addEventListener\('click', function \(ev\) \{\s*var a = ev\.target\.closest && ev\.target\.closest\('a\.signout'\);/, 'upgraded to the POST by delegation on the band');
|
|
150
|
+
assert.match(HUB, /<!--apex-only:start--><a data-link-hall href="\{\{buildersOrigin\}\}">Builders' hall<\/a><!--apex-only:end--><\/nav><\/footer>/, 'the hall link lives in the footer, as on the landing');
|
|
134
151
|
});
|
|
135
152
|
|
|
136
153
|
test('orb accents are tokens, never literal hexes (§6)', () => {
|
|
@@ -157,19 +174,9 @@ test('accents are assigned by canonical position, not by hashing the name', () =
|
|
|
157
174
|
'name-hashed accents collide — assign from a canonical order');
|
|
158
175
|
});
|
|
159
176
|
|
|
160
|
-
test('the mark holds "bongos" and types the assistant, not the other way round', () => {
|
|
161
|
-
// `bongos` is the word a person types; the assistant is what varies. This is
|
|
162
|
-
// the same correction the landing hero needed.
|
|
163
|
-
assert.match(HUB, /<span id="markWord">bongos<\/span>/);
|
|
164
|
-
const prefixes = (SCRIPT.match(/var PREFIXES = \[([^\]]*)\]/) || [, ''])[1];
|
|
165
|
-
assert.ok(prefixes.length, 'the typed prefix list must be findable');
|
|
166
|
-
assert.doesNotMatch(prefixes, /'bongos'/, 'bongos is the constant, never a typed variant');
|
|
167
|
-
assert.match(prefixes, /'claude'/);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
177
|
test('the typing and the map both respect prefers-reduced-motion (C9)', () => {
|
|
171
178
|
assert.match(SCRIPT, /prefers-reduced-motion: reduce/);
|
|
172
|
-
assert.match(SCRIPT, /if \(REDUCED
|
|
179
|
+
assert.match(SCRIPT, /if \(REDUCED\) stopSky\(\); else startSky\(\);/, 'a reduce-motion flip stops the sky');
|
|
173
180
|
});
|
|
174
181
|
|
|
175
182
|
test('the owner poll is view-guarded — it must not run on the map (C8)', () => {
|
|
@@ -393,44 +400,16 @@ test('the count includes the platform when it is shown', () => {
|
|
|
393
400
|
assert.match(SCRIPT, /var shown = list\.length \+ \(selfShown \? 1 : 0\)/);
|
|
394
401
|
});
|
|
395
402
|
|
|
396
|
-
/* ── The wordmark's cadence (task
|
|
397
|
-
The
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
// the regression shape: one interval that flips direction the moment it
|
|
404
|
-
// completes a word, so there is no dwell at full length
|
|
405
|
-
assert.doesNotMatch(mark, /setInterval/,
|
|
406
|
-
'a single reversing interval cannot hold — it turns around on the tick it completes');
|
|
407
|
-
assert.match(mark, /HOLD\s*=\s*\d+/, 'the hold must be a named duration, not an accident of timing');
|
|
408
|
-
// and it must actually be a pause worth reading, not a couple of frames
|
|
409
|
-
const hold = Number((mark.match(/HOLD\s*=\s*(\d+)/) || [, 0])[1]);
|
|
410
|
-
assert.ok(hold >= 600, `the hold must be legible; got ${hold}ms`);
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
test('the wordmark rests on the real product name, not on a joke', () => {
|
|
414
|
-
const mark = (SCRIPT.match(/function typeMark\(\)([\s\S]*?)\n \}\)\(\);/) || [, ''])[1];
|
|
415
|
-
assert.match(mark, /w === 'cloud' \? HOLD_HOME : HOLD/,
|
|
416
|
-
'the canonical prefix should dwell longest — it is the wordmark, the others are the gag');
|
|
417
|
-
const home = Number((mark.match(/HOLD_HOME\s*=\s*(\d+)/) || [, 0])[1]);
|
|
418
|
-
const hold = Number((mark.match(/HOLD\s*=\s*(\d+)/) || [, 0])[1]);
|
|
419
|
-
assert.ok(home > hold, 'the resting state must outlast the alternates');
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
test('the hub wordmark keeps its own typing cadence; the landing hero no longer types (task 1003298)', () => {
|
|
423
|
-
// The landing's self-typing eyebrow ("type bongos in any AI") retired with the
|
|
424
|
-
// chrome world: its hero is the h1, the lede and the doors, and its only motion is
|
|
425
|
-
// the CSS breath. The hub's typing wordmark stays; a shared cadence is no longer a
|
|
426
|
-
// thing to keep in step, so this pins the hub half and the landing's silence.
|
|
403
|
+
/* ── The wordmark's cadence left the hub (task 1003300) ─────────────────────
|
|
404
|
+
The hub carries the static mark since the v2 pick; the typing loop, its hold
|
|
405
|
+
and its rest-on-cloud rule (task 1002765) are landing concerns now (direction
|
|
406
|
+
record §7). What stays pinned is the landing hero's silence (task 1003298)
|
|
407
|
+
and the hub's. ── */
|
|
408
|
+
|
|
409
|
+
test('neither the landing hero nor the hub types (tasks 1003298, 1003300)', () => {
|
|
427
410
|
const LANDING = fs.readFileSync(path.join(ROOT, 'public-landing', 'index.html'), 'utf8');
|
|
428
411
|
assert.doesNotMatch(LANDING, /id="typed"|setTimeout\(type\b/, 'the landing hero does not type');
|
|
429
|
-
|
|
430
|
-
assert.ok(mark, 'the hub wordmark still types');
|
|
431
|
-
for (const name of ['HOLD_HOME', 'HOLD']) {
|
|
432
|
-
assert.match(mark, new RegExp(name + '\\s*=\\s*\\d+'), `the hub declares ${name}`);
|
|
433
|
-
}
|
|
412
|
+
assert.doesNotMatch(SCRIPT, /function typeMark\(\)|HOLD_HOME|setTimeout\(type\b/, 'the hub has no typing loop');
|
|
434
413
|
});
|
|
435
414
|
|
|
436
415
|
/* ── The project card (task 1002770) ────────────────────────────────────────
|
|
@@ -198,12 +198,14 @@ test('Back moves between views — a deliberate view change is a history entry',
|
|
|
198
198
|
assert.match(SCRIPT, /else if \(v === 'manage' && a\.dataset\.nav === 'mine'\) a\.setAttribute\('aria-current', 'page'\)/, 'manage keeps the Your-projects pill lit');
|
|
199
199
|
});
|
|
200
200
|
|
|
201
|
-
test('the
|
|
202
|
-
assert.match(COSMOS,
|
|
203
|
-
assert.match(COSMOS, /@media \(
|
|
204
|
-
assert.
|
|
201
|
+
test('the bar is the page ground closed by a hairline, holds one row on a phone, and the chip truncates (task 1003300)', () => {
|
|
202
|
+
assert.match(COSMOS, /\.bar\{display:flex;align-items:center;gap:22px;height:var\(--top-h\);padding:0 var\(--gutter\);\n\s*background:var\(--bar-bg\);border-bottom:1px solid var\(--rule\);\}/, 'the bar is the page ground closed by one hairline');
|
|
203
|
+
assert.match(COSMOS, /@media \(max-width:900px\)\{\n\s*:root\{--top-h:60px;/, 'one 60px row under 900');
|
|
204
|
+
assert.match(COSMOS, /@media \(max-width:900px\)\{[\s\S]*?\.barMark span\{display:none;\}/, 'the wordmark folds away, the glyph stays');
|
|
205
|
+
assert.match(COSMOS, /@media \(max-width:480px\)\{[\s\S]*?\.qToggle\{display:inline-flex;\}[\s\S]*?\.qWrap\{display:none;\}/, 'under 480 the search folds into the magnifier');
|
|
206
|
+
assert.doesNotMatch(COSMOS, /\.navrow|\.shell-right|\.shell \.mark/, 'the nav row and the equal-flank machinery left with the markup');
|
|
205
207
|
assert.match(COSMOS, /\.who>span\{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:14ch;\}/, 'a long login truncates');
|
|
206
|
-
assert.match(COSMOS, /\.search:focus-within\{box-shadow:/, 'keyboard focus on the search
|
|
208
|
+
assert.match(COSMOS, /\.search:focus-within\{box-shadow:/, 'keyboard focus on the mine search pill is visible');
|
|
207
209
|
assert.match(COSMOS, /\.mineBar \.search \.count\{display:none;\}/, 'the count leaves the placeholder alone at 390px');
|
|
208
210
|
assert.match(COSMOS, /\.projMeta>span\+span::before\{content:'·'/, 'the meta separator rides its item');
|
|
209
211
|
assert.match(HUB, /#view-mine \.hero,#view-manage \.hero\{max-width:none;\}/, 'titles align with their cards');
|
|
@@ -449,7 +451,7 @@ test('the hub has one main landmark, a heading for the map, a footer, and a skip
|
|
|
449
451
|
assert.ok(main > 0 && main < map && close > HUB.indexOf('<section id="view-new"') && foot > close, 'the four views sit inside #hubMain, the footer after it');
|
|
450
452
|
assert.equal((HUB.match(/<main\b/g) || []).length, 1, 'exactly one main');
|
|
451
453
|
assert.match(HUB, /<section id="view-map">\n\s*<h1 class="vh">The project map<\/h1>/, 'the map view has a heading');
|
|
452
|
-
assert.match(HUB, /<a class="skip vh" href="#hubMain">Skip to content<\/a>\n<header class="shell">/, 'the skip link is the first thing before the shell');
|
|
454
|
+
assert.match(HUB, /<a class="skip vh" href="#hubMain">Skip to content<\/a>\n<header class="shell bar">/, 'the skip link is the first thing before the shell');
|
|
453
455
|
assert.match(HUB, /\.skip:focus\{position:fixed;top:8px;left:8px;/, 'it becomes a real pill on focus');
|
|
454
456
|
assert.doesNotMatch(HUB, /<main class="card">/, 'the old inner main is a div');
|
|
455
457
|
});
|
|
@@ -480,10 +482,10 @@ test('control nits: orb cursor, caret under reduced motion, mode label, home lab
|
|
|
480
482
|
assert.doesNotMatch(COSMOS, /\.orb\{flex:0 0 54px;[^}]*cursor:pointer/, 'the base orb is not a pointer');
|
|
481
483
|
assert.match(COSMOS, /\na\.orb\{cursor:pointer;\}/, 'only the linked orb is');
|
|
482
484
|
assert.doesNotMatch(COSMOS, /\.orb\.ghost\{[^}]*cursor:default/);
|
|
483
|
-
assert.
|
|
485
|
+
assert.doesNotMatch(HUB, /\.shell \.caret/, 'no caret to hide — the hub mark is static (task 1003300)');
|
|
484
486
|
assert.match(fn('paintModeToggle'), /el\.setAttribute\('aria-label', dark \? 'Switch to light mode' : 'Switch to dark mode'\);/);
|
|
485
487
|
assert.doesNotMatch(fn('paintModeToggle'), /aria-pressed/);
|
|
486
|
-
assert.match(SCRIPT, /\$\('
|
|
488
|
+
assert.match(SCRIPT, /\$\('barMark'\)\.setAttribute\('aria-label', productName \+ ' — home'\);/, 'the mark — the only way home — is labelled from the product name');
|
|
487
489
|
assert.match(SCRIPT, /var REDUCED_MQ = window\.matchMedia \? window\.matchMedia\('\(prefers-reduced-motion: reduce\)'\) : null;/);
|
|
488
490
|
assert.match(SCRIPT, /if \(REDUCED\) stopSky\(\); else startSky\(\);/, 'a mid-session flip stops or restarts the sky');
|
|
489
491
|
assert.match(SCRIPT, /REDUCED_MQ\.addEventListener\('change', onReducedChange\)/);
|