@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.
Files changed (48) hide show
  1. package/.bongos-core.json +108 -58
  2. package/.claude/skills/design/SKILL.md +68 -0
  3. package/.claude/skills/design-sync/SKILL.md +2 -2
  4. package/.claude/skills/figma-design-sync/SKILL.md +3 -3
  5. package/config/design-tokens.neutral.json +1 -1
  6. package/config/design-tokens.schema.json +1 -1
  7. package/docs/adr/0197-the-ui-design-module.md +52 -0
  8. package/docs/adr/README.md +1 -0
  9. package/docs/architecture.md +1 -1
  10. package/docs/copy-inventory.md +249 -236
  11. package/docs/copy-registry.json +543 -324
  12. package/docs/file-map.md +2 -0
  13. package/docs/modules-contract.md +1 -0
  14. package/docs/onboarding/slash-commands.md +1 -1
  15. package/docs/recipes/claude-design-loop.md +3 -3
  16. package/docs/recipes/figma-design-loop.md +4 -4
  17. package/modules/ui-design/CLAUDE.md +15 -0
  18. package/{src/ui → modules/ui-design}/adapters/claude-design/index.js +1 -1
  19. package/{src/ui → modules/ui-design}/adapters/figma/index.js +3 -3
  20. package/{docs → modules/ui-design/docs}/design-contract.md +12 -12
  21. package/modules/ui-design/kit/README.md +3 -0
  22. package/modules/ui-design/module.json +11 -0
  23. package/modules/ui-design/scripts/design-sync.js +65 -0
  24. package/modules/ui-design/scripts/figma-design-sync.js +64 -0
  25. package/modules/ui-design/scripts/validate-design.js +258 -0
  26. package/modules/ui-design/skills/README.md +3 -0
  27. package/modules/ui-design/styles/README.md +3 -0
  28. package/package-lock.json +2 -2
  29. package/package.json +1 -1
  30. package/public-landing/assets/cosmos.css +235 -103
  31. package/public-landing/index.html +228 -75
  32. package/public-landing/projects.html +256 -140
  33. package/scripts/gds/copy-inventory.js +7 -5
  34. package/scripts/gds/design-sync.js +9 -59
  35. package/scripts/gds/doc-cli-guard.js +11 -5
  36. package/scripts/gds/figma-design-sync.js +9 -59
  37. package/scripts/gds/fitness-checks-identity.js +5 -3
  38. package/scripts/gds/publish-manifest.js +1 -1
  39. package/scripts/gds/validate-design.js +7 -249
  40. package/src/bongos/module-scope-map.js +18 -0
  41. package/src/module-api.js +5 -1
  42. package/tests/design_tokens_sync.mjs +1 -1
  43. package/tests/doc_cli_guard.mjs +19 -0
  44. package/tests/figma_adapter.mjs +1 -1
  45. package/tests/landing_page.mjs +72 -2
  46. package/tests/module_loader.mjs +4 -1
  47. package/tests/projects_hub.mjs +52 -73
  48. package/tests/projects_hub_pre_uat.mjs +10 -8
@@ -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 header nav pill and the footer entry
199
- assert.ok(halls.length >= 2, 'the header and the footer both point at the hall');
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&amp;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
+ });
@@ -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();
@@ -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 nav carries Home plus the three views, and Home is not a view', () => {
40
- assert.match(HUB, /data-nav="home"[^>]*>Home|<a href="\/" class="navHome" data-nav="home">Home/);
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 nav must deep-link ?view=${v}`);
44
+ `the band must deep-link ?view=${v}`);
44
45
  }
45
- // Home navigates to the landing page; it must never take the current-view pill
46
- assert.match(SCRIPT, /if \(a\.dataset\.nav === 'home'\) return;/,
47
- 'the view router must leave Home as a real navigation');
48
- assert.match(SCRIPT, /a\.dataset\.nav !== 'home' && a\.dataset\.nav === v/,
49
- 'Home must be excluded from the aria-current pill');
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&amp;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 wordmark holds bongos still and colors it apart (task 1002793)', () => {
117
- // Owner-reported: the block caret glyph + a literal space read as two
118
- // characters of air; every typed letter shifted bongos; both words wore the
119
- // accent. The caret is now a thin drawn bar (no glyph), the prefix sits in a
120
- // width-pinned box measured per word BEFORE typing, and only bongos keeps
121
- // the accent — the landing's static mark already colors only its bongos
122
- // span, so the two shells read as one system.
123
- assert.match(HUB,
124
- /<span class="markLeft" id="markLeft"><span id="markPrefix">cloud<\/span><span class="caret" aria-hidden="true"><\/span><\/span> <span id="markWord">bongos<\/span>/,
125
- 'prefix + caret share the pinned box; a single literal space reaches bongos; the caret carries no glyph');
126
- assert.match(SCRIPT, /meas\.textContent = w/,
127
- 'the coming word is measured before it is typed');
128
- assert.match(SCRIPT, /box\.style\.width = \(meas\.offsetWidth \+ 4\) \+ 'px'/,
129
- 'the box is pinned to the full word so bongos cannot move mid-word');
130
- assert.ok(COSMOS.includes('.shell .mark #markPrefix{color:var(--chrome-ink);}'),
131
- 'the typed word wears the base ink — the shared span rule (which the landing relies on) stays');
132
- assert.match(COSMOS, /\.shell \.caret\{[^}]*width:2px[^}]*background:var\(--chrome-accent\)/,
133
- 'the caret is a drawn 2px bar, not a full character cell');
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 \|\|/, 'the typing loop must be skipped under reduced motion');
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 1002765) ──────────────────────────────────
397
- The mark completed a word and erased it on the same tick, so the word the
398
- animation exists to show was never legible. Owner-reported, twice. ── */
399
-
400
- test('the wordmark HOLDS the finished word before erasing it', () => {
401
- const mark = (SCRIPT.match(/function typeMark\(\)([\s\S]*?)\n \}\)\(\);/) || [, ''])[1];
402
- assert.ok(mark, 'typeMark() must exist');
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
- const mark = (SCRIPT.match(/function typeMark\(\)([\s\S]*?)\n \}\)\(\);/) || [, ''])[1];
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 signed-in header shrinks inside a phone and stays one row on a laptop', () => {
202
- assert.match(COSMOS, /@media \(max-width:1079px\)\{[\s\S]*?\.shell-right\{flex:0 1 auto;min-width:0;max-width:100%;/, 'the cluster may shrink and wrap');
203
- assert.match(COSMOS, /@media \(min-width:1080px\)\{[\s\S]*?\.shell \.navrow\{position:absolute;left:50%;top:50%;transform:translate\(-50%,-50%\);\}/, 'the nav is centred absolutely, flanks size to content');
204
- assert.doesNotMatch(COSMOS, /@media \(max-width:820px\)\{\n\s*\.shell\{justify-content:center;\}/, 'no band is left between the two rules');
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 pills is visible');
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.match(HUB, /@media \(prefers-reduced-motion:reduce\)\{\.shell \.caret\{display:none;\}\}/, 'the caret hides instead of freezing solid');
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, /\$\('markLeft'\)\.parentNode\.setAttribute\('aria-label', productName \+ ' — home'\);\n\s*if \(REDUCED \|\|/, 'the home link is labelled before the typing loop’s early return');
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\)/);