@bongos/core 1.24.80 → 1.24.81

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 (106) hide show
  1. package/.bongos-core.json +125 -110
  2. package/docs/adr/0402-helm-is-the-halls-look-and-the-hall-is-dark-only.md +142 -0
  3. package/docs/adr/README.md +1 -0
  4. package/docs/copy-inventory.md +26 -31
  5. package/docs/copy-registry.json +29 -86
  6. package/docs/module-api-changelog.md +2 -0
  7. package/docs/page-inventory.json +1 -3
  8. package/docs/page-readings.json +94 -94
  9. package/modules/hall-ui/public/ai-studio.html +5 -3
  10. package/modules/hall-ui/public/ai-studio.states.json +1 -0
  11. package/modules/hall-ui/public/atlas.html +3 -3
  12. package/modules/hall-ui/public/atlas.states.json +1 -0
  13. package/modules/hall-ui/public/blockers.html +3 -3
  14. package/modules/hall-ui/public/board-room.html +3 -3
  15. package/modules/hall-ui/public/board-room.states.json +1 -0
  16. package/modules/hall-ui/public/city.css +1 -3
  17. package/modules/hall-ui/public/city.html +5 -8
  18. package/modules/hall-ui/public/city.js +10 -55
  19. package/modules/hall-ui/public/city.states.json +7 -32
  20. package/modules/hall-ui/public/collab.html +3 -3
  21. package/modules/hall-ui/public/collab.states.json +1 -0
  22. package/modules/hall-ui/public/commands.html +3 -3
  23. package/modules/hall-ui/public/commands.states.json +1 -0
  24. package/modules/hall-ui/public/controls.css +63 -11
  25. package/modules/hall-ui/public/copy-desk.html +3 -3
  26. package/modules/hall-ui/public/copy-desk.states.json +1 -0
  27. package/modules/hall-ui/public/deploy.html +3 -3
  28. package/modules/hall-ui/public/deploy.states.json +1 -0
  29. package/modules/hall-ui/public/goal-map.html +3 -3
  30. package/modules/hall-ui/public/goals.html +3 -3
  31. package/modules/hall-ui/public/goals.states.json +1 -0
  32. package/modules/hall-ui/public/government.html +3 -3
  33. package/modules/hall-ui/public/government.states.json +1 -0
  34. package/modules/hall-ui/public/idea.html +3 -3
  35. package/modules/hall-ui/public/idea.states.json +1 -0
  36. package/modules/hall-ui/public/ideas.html +3 -3
  37. package/modules/hall-ui/public/ideas.states.json +1 -0
  38. package/modules/hall-ui/public/index-founding.states.json +1 -0
  39. package/modules/hall-ui/public/index-genesis-decide.states.json +1 -0
  40. package/modules/hall-ui/public/index-genesis-demo.states.json +1 -0
  41. package/modules/hall-ui/public/index-genesis.states.json +1 -0
  42. package/modules/hall-ui/public/index.html +4 -4
  43. package/modules/hall-ui/public/index.states.json +1 -0
  44. package/modules/hall-ui/public/market-watch-item.html +3 -3
  45. package/modules/hall-ui/public/market-watch-item.states.json +1 -0
  46. package/modules/hall-ui/public/market-watch-rules.html +3 -3
  47. package/modules/hall-ui/public/market-watch-rules.states.json +1 -0
  48. package/modules/hall-ui/public/market-watch.html +3 -3
  49. package/modules/hall-ui/public/market-watch.states.json +1 -0
  50. package/modules/hall-ui/public/modules.html +3 -3
  51. package/modules/hall-ui/public/modules.states.json +1 -0
  52. package/modules/hall-ui/public/money.html +3 -3
  53. package/modules/hall-ui/public/money.states.json +1 -0
  54. package/modules/hall-ui/public/my-agents.html +3 -3
  55. package/modules/hall-ui/public/my-agents.states.json +1 -0
  56. package/modules/hall-ui/public/primer.html +3 -3
  57. package/modules/hall-ui/public/primer.states.json +1 -0
  58. package/modules/hall-ui/public/profile.html +3 -3
  59. package/modules/hall-ui/public/profile.states.json +1 -0
  60. package/modules/hall-ui/public/project-settings.html +3 -3
  61. package/modules/hall-ui/public/roster.html +3 -3
  62. package/modules/hall-ui/public/roster.states.json +1 -0
  63. package/modules/hall-ui/public/settings.html +3 -3
  64. package/modules/hall-ui/public/settings.states.json +1 -0
  65. package/modules/hall-ui/public/shell-game.css +87 -42
  66. package/modules/hall-ui/public/shell-game.js +41 -8
  67. package/modules/hall-ui/public/shell.js +8 -40
  68. package/modules/hall-ui/public/street-card.states.json +1 -0
  69. package/modules/hall-ui/public/street.css +1 -5
  70. package/modules/hall-ui/public/street.html +5 -9
  71. package/modules/hall-ui/public/street.js +8 -59
  72. package/modules/hall-ui/public/street.states.json +7 -32
  73. package/modules/hall-ui/public/studio.html +3 -3
  74. package/modules/hall-ui/public/studio.states.json +1 -0
  75. package/modules/hall-ui/public/style.css +124 -65
  76. package/modules/hall-ui/public/task.html +3 -3
  77. package/modules/hall-ui/public/task.states.json +1 -0
  78. package/modules/hall-ui/public/thinking.html +3 -3
  79. package/modules/hall-ui/public/thinking.states.json +1 -0
  80. package/modules/hall-ui/public/tweak-editor.html +3 -3
  81. package/modules/hall-ui/public/tweak-editor.states.json +1 -0
  82. package/modules/hall-ui/public/watch.html +3 -3
  83. package/modules/hall-ui/public/watch.states.json +1 -0
  84. package/modules/hall-ui/public/work-founding.states.json +1 -0
  85. package/modules/hall-ui/public/work.html +3 -3
  86. package/modules/hall-ui/public/work.states.json +1 -0
  87. package/modules/hall-ui/records/start-screen.md +6 -2
  88. package/modules/hall-ui/specimens/components.states.json +2 -1
  89. package/modules/ui-design/public/brand.html +2 -2
  90. package/package-lock.json +2 -2
  91. package/package.json +1 -1
  92. package/release-notes.json +6 -0
  93. package/scripts/hall-preview/README.md +2 -3
  94. package/src/bongos/serve-internal.js +7 -2
  95. package/src/hall-helm.js +79 -0
  96. package/src/module-api.js +1 -1
  97. package/tests/design_reference.mjs +1 -1
  98. package/tests/hall_builders_page_pass.mjs +2 -1
  99. package/tests/hall_city.mjs +15 -29
  100. package/tests/hall_controls.mjs +52 -4
  101. package/tests/hall_helm.mjs +187 -0
  102. package/tests/hall_record_world.mjs +4 -2
  103. package/tests/hall_start_screen.mjs +65 -2
  104. package/tests/hall_street.mjs +13 -26
  105. package/tests/hall_topbar_320.mjs +4 -2
  106. package/tests/nav_permission_atoms.mjs +1 -1
@@ -45,13 +45,13 @@
45
45
  <h2 class="scroll__h" id="ref-colour-h">Colour</h2>
46
46
  <p class="scroll__lede">The thirteen colours of the branding pack. An instance replaces them in its own pack; in dark mode only the neutrals flip, and the accent, sea and terracotta stay the pack’s.</p>
47
47
 
48
- <ul class="ref-list" role="list"><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--bg)" aria-hidden="true"></span><code class="ref-name">--bg</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#edecee</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>#000000</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--bg-card)" aria-hidden="true"></span><code class="ref-name">--bg-card</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#fbfafc</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>#101012</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--bg-deep)" aria-hidden="true"></span><code class="ref-name">--bg-deep</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#e2e1e4</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>#000000</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--bg-frame)" aria-hidden="true"></span><code class="ref-name">--bg-frame</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#f6f5f7</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>#08080a</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--ink)" aria-hidden="true"></span><code class="ref-name">--ink</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#17181a</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>#ffffff</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--ink-soft)" aria-hidden="true"></span><code class="ref-name">--ink-soft</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#55565a</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>#c0c0c0</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--ink-faint)" aria-hidden="true"></span><code class="ref-name">--ink-faint</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#65656a</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>#a8a8a8</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--rule)" aria-hidden="true"></span><code class="ref-name">--rule</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#c6c6c8</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>rgba(255, 255, 255, .16)</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--rule-soft)" aria-hidden="true"></span><code class="ref-name">--rule-soft</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#dcdbdd</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>rgba(255, 255, 255, .08)</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--accent)" aria-hidden="true"></span><code class="ref-name">--accent</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#fb9569</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>same as light</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--accent-deep)" aria-hidden="true"></span><code class="ref-name">--accent-deep</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#a3481a</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>same as light</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--sea)" aria-hidden="true"></span><code class="ref-name">--sea</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#5b6fd0</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>same as light</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--terracotta)" aria-hidden="true"></span><code class="ref-name">--terracotta</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#c9512c</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>same as light</code></span></li></ul>
48
+ <ul class="ref-list" role="list"><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--bg)" aria-hidden="true"></span><code class="ref-name">--bg</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#edecee</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>#050d16</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--bg-card)" aria-hidden="true"></span><code class="ref-name">--bg-card</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#fbfafc</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>#0a1622</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--bg-deep)" aria-hidden="true"></span><code class="ref-name">--bg-deep</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#e2e1e4</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>#050d16</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--bg-frame)" aria-hidden="true"></span><code class="ref-name">--bg-frame</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#f6f5f7</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>#07111b</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--ink)" aria-hidden="true"></span><code class="ref-name">--ink</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#17181a</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>#ffffff</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--ink-soft)" aria-hidden="true"></span><code class="ref-name">--ink-soft</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#55565a</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>#c8f1f7</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--ink-faint)" aria-hidden="true"></span><code class="ref-name">--ink-faint</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#65656a</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>#a9cdd4</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--rule)" aria-hidden="true"></span><code class="ref-name">--rule</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#c6c6c8</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>rgba(200, 241, 247, .4)</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--rule-soft)" aria-hidden="true"></span><code class="ref-name">--rule-soft</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#dcdbdd</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>rgba(200, 241, 247, .2)</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--accent)" aria-hidden="true"></span><code class="ref-name">--accent</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#fb9569</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>same as light</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--accent-deep)" aria-hidden="true"></span><code class="ref-name">--accent-deep</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#a3481a</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>same as light</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--sea)" aria-hidden="true"></span><code class="ref-name">--sea</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#5b6fd0</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>same as light</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--terracotta)" aria-hidden="true"></span><code class="ref-name">--terracotta</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#c9512c</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>same as light</code></span></li></ul>
49
49
  <h3 class="ref-sub">The status vocabulary</h3>
50
50
  <p class="ref-note">Fixed, not taken from the pack, and the one exception to the fifteen tokens: a status is always a word as well as a colour.</p>
51
51
  <ul class="ref-list" role="list"><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--ok)" aria-hidden="true"></span><code class="ref-name">--ok</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#187444</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>#4ade80</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--ok-weak)" aria-hidden="true"></span><code class="ref-name">--ok-weak</code><span class="ref-val"><span class="ref-val__k">light</span> <code>color-mix(in srgb, #187444 12%, var(--bg-card))</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>color-mix(in srgb, #4ade80 14%, var(--bg-card))</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--warn)" aria-hidden="true"></span><code class="ref-name">--warn</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#895c00</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>#fbbf24</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--warn-weak)" aria-hidden="true"></span><code class="ref-name">--warn-weak</code><span class="ref-val"><span class="ref-val__k">light</span> <code>color-mix(in srgb, #895c00 14%, var(--bg-card))</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>color-mix(in srgb, #fbbf24 14%, var(--bg-card))</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--danger)" aria-hidden="true"></span><code class="ref-name">--danger</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#b42318</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>#f87171</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--danger-weak)" aria-hidden="true"></span><code class="ref-name">--danger-weak</code><span class="ref-val"><span class="ref-val__k">light</span> <code>color-mix(in srgb, #b42318 10%, var(--bg-card))</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>color-mix(in srgb, #f87171 14%, var(--bg-card))</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--info)" aria-hidden="true"></span><code class="ref-name">--info</code><span class="ref-val"><span class="ref-val__k">light</span> <code>color-mix(in srgb, var(--sea) 86%, #000000)</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>color-mix(in srgb, var(--sea) 80%, #ffffff)</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--info-weak)" aria-hidden="true"></span><code class="ref-name">--info-weak</code><span class="ref-val"><span class="ref-val__k">light</span> <code>color-mix(in srgb, var(--info) 9%, var(--bg-card))</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>color-mix(in srgb, var(--info) 14%, var(--bg-card))</code></span></li></ul>
52
52
  <h3 class="ref-sub">Derived colours</h3>
53
53
  <p class="ref-note">Mixed from the thirteen, never typed as a new colour. Black and white with alpha appear only as a scrim, a shadow or glass.</p>
54
- <ul class="ref-list" role="list"><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--glass)" aria-hidden="true"></span><code class="ref-name">--glass</code><span class="ref-val"><span class="ref-val__k">light</span> <code>rgba(23, 24, 26, .05)</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>rgba(255, 255, 255, .11)</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--glass-strong)" aria-hidden="true"></span><code class="ref-name">--glass-strong</code><span class="ref-val"><span class="ref-val__k">light</span> <code>rgba(23, 24, 26, .08)</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>rgba(255, 255, 255, .17)</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--glass-edge)" aria-hidden="true"></span><code class="ref-name">--glass-edge</code><span class="ref-val"><span class="ref-val__k">light</span> <code>rgba(23, 24, 26, .12)</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>rgba(255, 255, 255, .13)</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--void)" aria-hidden="true"></span><code class="ref-name">--void</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#050508</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>#000000</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--void-ink)" aria-hidden="true"></span><code class="ref-name">--void-ink</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#ffffff</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>var(--ink)</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--void-edge)" aria-hidden="true"></span><code class="ref-name">--void-edge</code><span class="ref-val"><span class="ref-val__k">light</span> <code>rgba(23, 24, 26, .12)</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>rgba(255, 255, 255, .16)</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--skeleton)" aria-hidden="true"></span><code class="ref-name">--skeleton</code><span class="ref-val"><span class="ref-val__k">light</span> <code>color-mix(in srgb, var(--ink) 10%, var(--bg-card))</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>color-mix(in srgb, var(--ink) 10%, var(--bg-card))</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--accent-ink)" aria-hidden="true"></span><code class="ref-name">--accent-ink</code><span class="ref-val"><span class="ref-val__k">light</span> <code>color-mix(in srgb, var(--accent) 45%, #240e00)</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>var(--accent)</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--accent-lift)" aria-hidden="true"></span><code class="ref-name">--accent-lift</code><span class="ref-val"><span class="ref-val__k">light</span> <code>color-mix(in srgb, var(--accent) 58%, #ffffff)</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>color-mix(in srgb, var(--accent) 58%, #ffffff)</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--accent-tint)" aria-hidden="true"></span><code class="ref-name">--accent-tint</code><span class="ref-val"><span class="ref-val__k">light</span> <code>color-mix(in srgb, var(--accent) 30%, #fffdf9)</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>color-mix(in srgb, var(--accent) 22%, #0b0a09)</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--accent-on)" aria-hidden="true"></span><code class="ref-name">--accent-on</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#2a1305</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>#2a1305</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--focus)" aria-hidden="true"></span><code class="ref-name">--focus</code><span class="ref-val"><span class="ref-val__k">light</span> <code>color-mix(in srgb, var(--accent-deep) 82%, #3a1400)</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>var(--accent)</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--scrim)" aria-hidden="true"></span><code class="ref-name">--scrim</code><span class="ref-val"><span class="ref-val__k">light</span> <code>rgba(0, 0, 0, .55)</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>rgba(0, 0, 0, .55)</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--surface-2)" aria-hidden="true"></span><code class="ref-name">--surface-2</code><span class="ref-val"><span class="ref-val__k">light</span> <code>color-mix(in srgb, var(--ink) 5%, var(--bg-card))</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>color-mix(in srgb, var(--ink) 6%, var(--bg-card))</code></span></li></ul>
54
+ <ul class="ref-list" role="list"><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--glass)" aria-hidden="true"></span><code class="ref-name">--glass</code><span class="ref-val"><span class="ref-val__k">light</span> <code>rgba(23, 24, 26, .05)</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>rgba(200, 241, 247, .1)</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--glass-strong)" aria-hidden="true"></span><code class="ref-name">--glass-strong</code><span class="ref-val"><span class="ref-val__k">light</span> <code>rgba(23, 24, 26, .08)</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>rgba(200, 241, 247, .16)</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--glass-edge)" aria-hidden="true"></span><code class="ref-name">--glass-edge</code><span class="ref-val"><span class="ref-val__k">light</span> <code>rgba(23, 24, 26, .12)</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>rgba(200, 241, 247, .2)</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--void)" aria-hidden="true"></span><code class="ref-name">--void</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#050508</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>#050d16</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--void-ink)" aria-hidden="true"></span><code class="ref-name">--void-ink</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#ffffff</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>var(--ink)</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--void-edge)" aria-hidden="true"></span><code class="ref-name">--void-edge</code><span class="ref-val"><span class="ref-val__k">light</span> <code>rgba(23, 24, 26, .12)</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>rgba(200, 241, 247, .4)</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--skeleton)" aria-hidden="true"></span><code class="ref-name">--skeleton</code><span class="ref-val"><span class="ref-val__k">light</span> <code>color-mix(in srgb, var(--ink) 10%, var(--bg-card))</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>color-mix(in srgb, var(--ink) 10%, var(--bg-card))</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--accent-ink)" aria-hidden="true"></span><code class="ref-name">--accent-ink</code><span class="ref-val"><span class="ref-val__k">light</span> <code>color-mix(in srgb, var(--accent) 45%, #240e00)</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>var(--accent)</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--accent-lift)" aria-hidden="true"></span><code class="ref-name">--accent-lift</code><span class="ref-val"><span class="ref-val__k">light</span> <code>color-mix(in srgb, var(--accent) 58%, #ffffff)</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>color-mix(in srgb, var(--accent) 58%, #ffffff)</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--accent-tint)" aria-hidden="true"></span><code class="ref-name">--accent-tint</code><span class="ref-val"><span class="ref-val__k">light</span> <code>color-mix(in srgb, var(--accent) 30%, #fffdf9)</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>color-mix(in srgb, var(--accent) 22%, #050d16)</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--accent-on)" aria-hidden="true"></span><code class="ref-name">--accent-on</code><span class="ref-val"><span class="ref-val__k">light</span> <code>#2a1305</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>#2a1305</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--focus)" aria-hidden="true"></span><code class="ref-name">--focus</code><span class="ref-val"><span class="ref-val__k">light</span> <code>color-mix(in srgb, var(--accent-deep) 82%, #3a1400)</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>var(--accent)</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--scrim)" aria-hidden="true"></span><code class="ref-name">--scrim</code><span class="ref-val"><span class="ref-val__k">light</span> <code>rgba(0, 0, 0, .55)</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>rgba(0, 0, 0, .55)</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--surface-2)" aria-hidden="true"></span><code class="ref-name">--surface-2</code><span class="ref-val"><span class="ref-val__k">light</span> <code>color-mix(in srgb, var(--ink) 5%, var(--bg-card))</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>color-mix(in srgb, var(--ink) 6%, var(--bg-card))</code></span></li><li class="ref-row ref-row--colour"><span class="ref-swatch" style="background: var(--night-glass)" aria-hidden="true"></span><code class="ref-name">--night-glass</code><span class="ref-val"><span class="ref-val__k">light</span> <code>color-mix(in srgb, var(--bg) 72%, transparent)</code></span><span class="ref-val"><span class="ref-val__k">dark</span> <code>color-mix(in srgb, var(--bg) 72%, transparent)</code></span></li></ul>
55
55
  </section>
56
56
  <section class="scroll ref-sec" id="ref-type" aria-labelledby="ref-type-h">
57
57
  <h2 class="scroll__h" id="ref-type-h">Type</h2>
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.24.80",
3
+ "version": "1.24.81",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.24.80",
9
+ "version": "1.24.81",
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.24.80",
3
+ "version": "1.24.81",
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",
@@ -11583,5 +11583,11 @@
11583
11583
  "id": "1005141",
11584
11584
  "text": "The builders' crafts now carry their game names: Engineers are Artisans, Ideators join the Artists, and Patron is the third character; old links and old records keep working."
11585
11585
  }
11586
+ ],
11587
+ "1.24.81": [
11588
+ {
11589
+ "id": "1005192",
11590
+ "text": "The hall now wears the new Helm look: deep night panels, ice-blue text, wide capital titles and cut-corner buttons, all from one shared design system. The start and load screens are rebuilt to match, and the hall is dark only."
11591
+ }
11586
11592
  ]
11587
11593
  }
@@ -135,15 +135,14 @@ overlay). A concept no table holds is not drawn: never add sample data for one.
135
135
 
136
136
  ## The evidence matrix (per page-redesign ship)
137
137
 
138
- Screenshot every changed page at **{light, dark} × {1280w, 390w}**, plus one
138
+ Screenshot every changed page at **{1280w, 390w}** (the hall is dark only since task 1005192, ADR 0402), plus one
139
139
  instance-pack run (`--brand config/branding.json`) proving the theming contract
140
140
  survives. Run the harness with **`--fixture-me`** for these — the pinned snapshot
141
141
  is what makes two runs comparable. With the Playwright MCP (`npx playwright install chrome` once):
142
142
 
143
143
  1. `browser_navigate` → the page. `browser_console_messages` must show **0 errors**.
144
144
  2. 1280w: `browser_resize 1280×900` → `browser_take_screenshot` (fullPage for tall pages).
145
- 3. dark: `browser_evaluate` `() => { localStorage.setItem('hall-theme','dark'); location.reload(); }` → screenshot. (Reset with `'light'`.)
146
- 4. 390w: `browser_resize 390×844` → light + dark screenshots.
145
+ 3. 390w: `browser_resize 390×844` → screenshot.
147
146
 
148
147
  ## Seam assertions (copy-paste `browser_evaluate` snippets)
149
148
 
@@ -45,6 +45,7 @@ const { buildInfo } = require('../build-info');
45
45
  const { buildBongosDownloads, installScriptReader } = require('../bongos-downloads');
46
46
  const { clientBranding, branding, applyBrandConditionals } = require('../branding');
47
47
  const { themeMarkup } = require('../theme-markup');
48
+ const { applyHelm } = require('../hall-helm');
48
49
  const { linkPreviewTags } = require('../link-preview');
49
50
  const { prelaunchGate } = require('./prelaunch-gate');
50
51
  const { platformVisibilityGate } = require('./platform-visibility-gate');
@@ -126,8 +127,12 @@ function injectBrandingGlobal(html, brand = clientBranding(), mods = clientModul
126
127
  // §4), injected the same way as __BRANDING__ so hall/status scripts can hide
127
128
  // UI sections for off modules synchronously (no fetch, no race). R60 wires the
128
129
  // actual section-hiding; this exposes the data. The link-preview card (task 1005091) rides along.
129
- const stamped = applyBrandTokens(html, brand);
130
- const tag = `${clientBundleScriptTag()}<script>window.__BRANDING__=${jsonForScript(brand)};window.__MODULES__=${jsonForScript(mods)};</script>${linkPreviewTags(stamped, brand, safePublicOrigin())}${themeMarkup(brand.theme)}`;
130
+ // A HALL page (one that links the hall's sheet) is pinned dark and, unless its pack
131
+ // chose faces of its own, takes the Helm faces (task 1005192, ADR 0402, src/hall-helm.js).
132
+ // Every other page comes back from applyHelm untouched.
133
+ const helm = applyHelm(applyBrandTokens(html, brand), brand.theme);
134
+ const stamped = helm.html;
135
+ const tag = `${clientBundleScriptTag()}<script>window.__BRANDING__=${jsonForScript(brand)};window.__MODULES__=${jsonForScript(mods)};</script>${linkPreviewTags(stamped, brand, safePublicOrigin())}${themeMarkup(brand.theme)}${helm.tail}`;
131
136
  return stamped.includes('</head>') ? stamped.replace('</head>', `${tag}</head>`) : tag + stamped;
132
137
  }
133
138
 
@@ -0,0 +1,79 @@
1
+ // src/hall-helm.js — Helm, the look the builders' hall wears (task 1005192, ADR 0402).
2
+ //
3
+ // The hall is the game: everything after sign-in. It is dark only, and it reads in
4
+ // the two Helm faces unless its pack chose faces of its own. Both are decided HERE,
5
+ // at serve time, for one reason each:
6
+ //
7
+ // * DARK ONLY. A fixed html[data-theme="dark"] in the served markup selects the
8
+ // hall sheet's dark tier before first paint, with nothing to flash and nothing
9
+ // for the OS preference to reach (the ADR 0204 move, on the hall's own
10
+ // attribute). shell.js sets the same value again for a page read without it.
11
+ // * THE FACES. The pack splices --font-display / --font-body as a plain :root
12
+ // after every sheet, for every surface, so the landing and the hall would
13
+ // always read the same face. The hall sheet carries the Helm stacks as its own
14
+ // tokens (--helm-display / --helm-body) and hands them to the two font tokens
15
+ // under html[data-faces="helm"], which outranks the pack's :root. This file
16
+ // stamps that attribute, and links the two families, only for a hall whose
17
+ // pack still names the platform's default faces. A pack that names its own
18
+ // (config/branding.json, or a look from the style library with faces of its
19
+ // own, ADR 0358) is never stamped and keeps them.
20
+ //
21
+ // A hall page is recognised by what makes it one: it links the hall's sheet. So one
22
+ // seam (serve-internal.js injectBrandingGlobal) covers the server, the preview
23
+ // harness and the design kit's stub, and the landing, the hub and the status page,
24
+ // which never link that sheet, are never touched. PURE given `defaults`.
25
+ 'use strict';
26
+
27
+ const fs = require('node:fs');
28
+ const path = require('node:path');
29
+ const { escapeHtml: esc } = require('./html-escape');
30
+
31
+ // Barlow Condensed (the name voice) and Barlow (everything said to you), swapped in.
32
+ const HELM_FONTS_HREF = 'https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@400;500;600&family=Barlow:wght@400;500&display=swap';
33
+ const HALL_SHEET_RE = /<link\b[^>]*\bhref="\/builders\/style\.css[^"]*"/i;
34
+ const HTML_TAG_RE = /<html\b([^>]*)>/i;
35
+
36
+ function isHallPage(html) {
37
+ return HALL_SHEET_RE.test(String(html || ''));
38
+ }
39
+
40
+ // The platform's default faces: the neutral pack's two stacks, read once. A hall
41
+ // whose resolved pack still carries exactly these has not chosen faces.
42
+ let _defaults = null;
43
+ function defaultFaces() {
44
+ if (_defaults) return _defaults;
45
+ try {
46
+ const { resolveCoreRoot } = require('./instance-config'); // eslint-disable-line global-require
47
+ const pack = JSON.parse(fs.readFileSync(path.join(resolveCoreRoot(), 'config', 'branding.neutral.json'), 'utf8'));
48
+ const fonts = (pack.theme && pack.theme.ui && pack.theme.ui.fonts) || {};
49
+ _defaults = { display: String(fonts.display || ''), body: String(fonts.body || '') };
50
+ } catch { _defaults = { display: '', body: '' }; }
51
+ return _defaults;
52
+ }
53
+
54
+ // Whether this hall takes the Helm faces: its pack names the default ones (or none).
55
+ function wearsHelmFaces(theme, defaults = defaultFaces()) {
56
+ const fonts = (theme && theme.fonts) || {};
57
+ const same = (mine, theirs) => !mine || mine === theirs;
58
+ return same(fonts.display, defaults.display) && same(fonts.body, defaults.body);
59
+ }
60
+
61
+ // Set one attribute on the document's <html> tag, replacing a value already there.
62
+ function stampHtml(html, name, value) {
63
+ return html.replace(HTML_TAG_RE, (tag, attrs) => {
64
+ const rest = attrs.replace(new RegExp(`\\s${name}(?:="[^"]*")?`, 'i'), '');
65
+ return `<html${rest} ${name}="${esc(value)}">`;
66
+ });
67
+ }
68
+
69
+ // The whole transform for one served page: a page that does not link the hall's
70
+ // sheet comes back untouched. `tail` is markup for the end of <head>.
71
+ function applyHelm(html, theme, defaults = defaultFaces()) {
72
+ if (!isHallPage(html)) return { html, tail: '' };
73
+ let out = stampHtml(html, 'data-theme', 'dark');
74
+ if (!wearsHelmFaces(theme, defaults)) return { html: out, tail: '' };
75
+ out = stampHtml(out, 'data-faces', 'helm');
76
+ return { html: out, tail: `<link rel="stylesheet" href="${esc(HELM_FONTS_HREF)}">` };
77
+ }
78
+
79
+ module.exports = { applyHelm, isHallPage, wearsHelmFaces, stampHtml, HELM_FONTS_HREF };
package/src/module-api.js CHANGED
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
81
81
  // MAJOR (see allowBoxScope below): passes the request through untouched.
82
82
  function deprecatedNoopMiddleware(_req, _res, next) { next(); }
83
83
 
84
- const CORE_VERSION = '1.24.80'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
84
+ const CORE_VERSION = '1.24.81'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
85
85
 
86
86
  // A namespaced logger so a module's log lines are attributable + consistent.
87
87
  // Usage: const log = api.logger('discord'); log.info('mounted');
@@ -76,7 +76,7 @@ test('every pack colour shows its light value and its dark value; the status voc
76
76
  assert.ok(r.includes(`light</span> <code>${light}</code>`), `${prop} light ${light}`);
77
77
  assert.match(r, /dark<\/span> <code>[^<]+<\/code>/, `${prop} has a dark value`);
78
78
  }
79
- assert.ok(rowOf(PAGE, '--bg').includes('dark</span> <code>#000000</code>'), 'the dark ground is read from the hall\'s dark tier');
79
+ assert.ok(rowOf(PAGE, '--bg').includes('dark</span> <code>#050d16</code>'), 'the dark ground is read from the hall\'s dark tier: the Helm night (task 1005192, ADR 0402)');
80
80
  assert.ok(rowOf(PAGE, '--ok').includes('light</span> <code>#187444</code>') && rowOf(PAGE, '--ok').includes('dark</span> <code>#4ade80</code>'));
81
81
  for (const s of ['--warn', '--danger', '--info', '--ok-weak', '--warn-weak', '--danger-weak', '--info-weak']) rowOf(PAGE, s);
82
82
  });
@@ -152,7 +152,8 @@ test('A: the quiet fill is a defined variant, not an inline colour', () => {
152
152
  const m = css.match(/([^{}]*\.landing__btn--quiet[\s,][^{}]*)\{([^{}]*)\}/);
153
153
  assert.ok(m, 'controls.css defines .landing__btn--quiet');
154
154
  const block = m[2];
155
- assert.match(block, /background:\s*transparent/, 'the quiet variant is the ghost');
155
+ // Helm (task 1005192, ADR 0402): the ghost takes the hall's press fill, and nothing without the hall's tier
156
+ assert.match(block, /background:\s*var\(--press-fill, transparent\)/, 'the quiet variant is the ghost');
156
157
  assert.deepEqual(block.match(/#[0-9a-f]{3,8}\b/gi) || [], [], 'the variant carries no literal colour');
157
158
  // Same geometry as the request button means it inherits .landing__btn's
158
159
  // min-height and padding rather than restating them.
@@ -12,8 +12,8 @@
12
12
  // the ring road, or a task with no building;
13
13
  // * the line under the button saying anything but the docket's own line;
14
14
  // * the start governing button going anywhere but the street;
15
- // * the switch not using the hall's own preference, so the city opens in a
16
- // different mode from every other hall page;
15
+ // * a light/dark switch coming back: the hall is dark only (task 1005192,
16
+ // ADR 0402), so the city draws its night faces and reads no stored choice;
17
17
  // * motion that escapes the Kill Switch, or docket text reaching innerHTML.
18
18
  //
19
19
  // Run: node tests/hall_city.mjs
@@ -263,7 +263,7 @@ test('parcels ride only the roads of plots that hold a district, and leave throu
263
263
 
264
264
  // ── the page ────────────────────────────────────────────────────────────────
265
265
 
266
- test('the page holds only the wordmark, the switch, the city, one button and one line', () => {
266
+ test('the page holds only the wordmark, the city, one door and one line, and no light/dark switch', () => {
267
267
  const html = read('city.html').replace(/<!--[\s\S]*?-->/g, ''); // the markup, not the prose about it
268
268
  assert.equal((html.match(/<header\b/g) || []).length, 1);
269
269
  assert.equal((html.match(/<main\b/g) || []).length, 1);
@@ -271,7 +271,8 @@ test('the page holds only the wordmark, the switch, the city, one button and one
271
271
  assert.match(html, /<a class="city-start" id="cityStart" href="#decide"/);
272
272
  assert.equal((html.match(/<a\b/g) || []).length, 3, 'one door, and the two faint archived links to the street');
273
273
  assert.ok(html.includes('<p class="city-archived">Archived: <a href="/builders/street">Street</a> &amp; <a href="/builders/street#building=charter">Building Level Views</a>.</p>'), 'the street is archived behind two faint links');
274
- assert.equal((html.match(/<button\b/g) || []).length, 1, 'one switch');
274
+ assert.equal((html.match(/<button\b/g) || []).length, 0, 'no switch: the hall is dark only (task 1005192, ADR 0402)');
275
+ assert.doesNotMatch(html, /cityMode/);
275
276
  assert.match(html, /class="city-mark">\{\{productName\}\}</);
276
277
  assert.equal(City.STREET_HREF, '/builders/street');
277
278
  });
@@ -346,7 +347,7 @@ function fakeDom() {
346
347
  return el;
347
348
  };
348
349
  const html = mk('html');
349
- for (const id of ['cityMode', 'cityModeIcon', 'cityStage', 'cityFlat', 'cityBlds', 'cityParcels', 'cityPins', 'cityLine', 'cityStart']) mk('x').setAttribute('id', id);
350
+ for (const id of ['cityStage', 'cityFlat', 'cityBlds', 'cityParcels', 'cityPins', 'cityLine', 'cityStart']) mk('x').setAttribute('id', id);
350
351
  const document = {
351
352
  documentElement: html,
352
353
  getElementById: (id) => byId.get(id) || null,
@@ -421,33 +422,18 @@ test('a failed docket read draws no pins and says the docket could not be read',
421
422
  assert.equal(p.byId.get('cityLine').textContent, City.FALLBACK_LINE);
422
423
  });
423
424
 
424
- test('the switch reads and writes the hall\'s own theme preference', async () => {
425
- const p = await bootPage({ feeds: FEEDS([]), stored: 'light', osDark: true });
426
- assert.equal(p.html.attrs['data-theme'], 'light', 'the stored hall choice beats the OS');
427
- assert.equal(p.byId.get('cityMode').attrs['aria-label'], 'Switch to dark mode');
428
- const before = JSON.stringify(p.byId.get('cityFlat').children[0].attrs);
429
- p.byId.get('cityMode').fire('click');
430
- assert.equal(p.store.get('hall-theme'), 'dark');
431
- assert.equal(p.html.attrs['data-theme'], 'dark');
432
- assert.equal(p.byId.get('cityMode').attrs['aria-label'], 'Switch to light mode');
433
- assert.notEqual(JSON.stringify(p.byId.get('cityFlat').children[0].attrs), before, 'the city redraws in the new mode');
425
+ test('the city is dark only: no stored choice and no OS setting reaches it (task 1005192, ADR 0402)', async () => {
426
+ const p = await bootPage({ feeds: FEEDS([]), stored: 'light', osDark: false, shell: { applyTheme() { throw new Error('the city no longer drives the shell'); } } });
427
+ assert.equal(p.html.attrs['data-theme'], 'dark', 'a stored light choice and a light OS are both ignored');
428
+ assert.equal(p.store.get('hall-theme'), 'light', 'and the old preference is never written');
429
+ assert.equal(p.observers.length, 0, 'nothing flips the theme, so nothing watches it');
434
430
  const q = await bootPage({ feeds: FEEDS([]), stored: null, osDark: true });
435
- assert.equal(q.html.attrs['data-theme'], 'dark', 'no stored choice follows the OS');
431
+ assert.equal(JSON.stringify(q.byId.get('cityFlat').children[0].attrs), JSON.stringify(p.byId.get('cityFlat').children[0].attrs), 'one drawing whatever was stored');
432
+ const src = read('city.js');
433
+ assert.doesNotMatch(src, /hall-theme|localStorage|prefers-color-scheme/, 'city.js reads no theme preference');
436
434
  });
437
435
 
438
- test('in the hall, the city and the top bar\'s theme switch never disagree (task 1004544)', async () => {
439
- let shellPaints = 0;
440
- const p = await bootPage({ feeds: FEEDS([]), stored: 'light', osDark: true, shell: { applyTheme() { shellPaints++; } } });
441
- assert.equal(p.observers.length, 1, 'the page watches html[data-theme]');
442
- p.byId.get('cityMode').fire('click');
443
- assert.equal(shellPaints, 1, 'this switch repaints the shell\'s');
444
- const before = JSON.stringify(p.byId.get('cityFlat').children[0].attrs);
445
- p.store.set('hall-theme', 'light'); p.html.setAttribute('data-theme', 'light'); // the shell's switch
446
- p.observers[0].fn();
447
- assert.equal(p.byId.get('cityMode').attrs['aria-label'], 'Switch to dark mode', 'this switch follows the shell\'s');
448
- assert.notEqual(JSON.stringify(p.byId.get('cityFlat').children[0].attrs), before, 'and the city redraws in that mode');
449
- assert.equal(shellPaints, 1, 'following the shell does not echo back to it');
450
- });
436
+
451
437
 
452
438
  test('the artboard fits the stage the hall chrome leaves, not the window (task 1004544)', async () => {
453
439
  const p = await bootPage({ feeds: FEEDS([]), stage: { clientWidth: 688, clientHeight: 900 } });
@@ -9,6 +9,11 @@
9
9
  // button (it loads later) without re-drawing it;
10
10
  // * THE SIZES — primary is DESIGN.md's 46–48px door, the row size and the
11
11
  // text button keep the 24px floor, the field clears it;
12
+ // * THE HELM SHAPE AND VOICE (task 1005192, ADR 0402) — in the hall a button
13
+ // is the cut corner in the name voice, every piece of it a hall-tier token
14
+ // spent with the old pill/lowercase as its fallback (the status page links
15
+ // this sheet without the hall's tier), and a cut button's focus ring is
16
+ // drawn inside the shape, because a clip-path clips an outline;
12
17
  // * CONTRAST, MEASURED — the button's ink on its fill (rest AND hover), the
13
18
  // ghost's and the text button's ink on both grounds, the field's text and
14
19
  // its edge (3:1, WCAG 1.4.11), in both modes, resolved from the real
@@ -144,11 +149,11 @@ test('style.css imports controls.css right after the shared token sheet', () =>
144
149
 
145
150
  // ── 2. the sizes ─────────────────────────────────────────────────────────────
146
151
 
147
- test('primary is the 46–48px pill; the row size and the text button keep the 24px floor', () => {
152
+ test('primary is the 46–48px door; the row size and the text button keep the 24px floor', () => {
148
153
  const base = sharedRule('.btn');
149
154
  const h = px(prop(base, 'min-height'));
150
155
  assert.ok(h >= 46 && h <= 48, `.btn is ${h}px — DESIGN.md's door is 46–48px`);
151
- assert.equal(prop(base, 'border-radius'), 'var(--radius-pill)', 'pill or nothing (DESIGN.md, Shapes)');
156
+ assert.equal(prop(base, 'border-radius'), 'var(--press-radius, var(--radius-pill))', 'the hall\'s corner, and the pill where the hall\'s tier is not loaded');
152
157
  assert.match(prop(base, 'border'), /transparent$/, 'the edge is reserved, so a modifier that sets only its colour draws');
153
158
  for (const sel of ['.btn--sm', '.btn--text']) {
154
159
  assert.ok(px(prop(sharedWith(sel, 'min-height'), 'min-height')) >= 24, `${sel} keeps 24px of height`);
@@ -203,6 +208,49 @@ test('no hall sheet puts white ink on an accent fill', () => {
203
208
  const PRIMARY = ['.btn', '.btn-accent', '.landing__btn', '.task__claim', '.setup-gate__save', '.td-btn--primary', '.pbtn--save', '.craft-save'];
204
209
  const GHOST = ['.btn--ghost', '.btn-ghost', '.landing__btn--quiet', '.td-btn', '.wip__release', '.pbtn', '.settings-btn'];
205
210
  const SMALL = ['.btn--sm', '.btn-ghost', '.task__claim', '.td-btn', '.wip__release'];
211
+ // the ghost's fill: the hall's press fill, and nothing at all without the hall's tier
212
+ const GHOST_FILL = 'var(--press-fill, transparent)';
213
+
214
+ // ── 3b. the Helm shape and voice (task 1005192, ADR 0402) ───────────────────
215
+
216
+ test('in the hall a button is the cut corner in the name voice, and without the hall\'s tier it is the pill it was', () => {
217
+ const base = sharedRule('.btn');
218
+ // every Helm piece is a token with the old look as its fallback
219
+ assert.equal(prop(base, 'clip-path'), 'var(--cut-path, none)');
220
+ assert.equal(prop(base, 'font-family'), 'var(--name-face, var(--font-body))');
221
+ assert.equal(prop(base, 'font-weight'), 'var(--name-weight, 500)');
222
+ assert.equal(prop(base, 'letter-spacing'), 'var(--name-space-tight, normal)');
223
+ assert.equal(prop(base, 'text-transform'), 'var(--name-case, lowercase)');
224
+ assert.equal(prop(sharedWith('.btn--sm', 'clip-path'), 'clip-path'), 'var(--cut-path-sm, none)', 'the row size takes the smaller cut');
225
+ // the tokens are the hall tier's, and the status page (which links this sheet) defines none of them
226
+ const status = fs.readFileSync(path.join(ROOT, 'modules', 'status-ui', 'public', 'style.css'), 'utf8');
227
+ for (const name of ['--cut-path', '--cut-path-sm', '--press-radius', '--press-fill', '--press-focus', '--name-face', '--name-weight', '--name-space-tight', '--name-case']) {
228
+ assert.ok(root[name], `${name} is in the hall's token block`);
229
+ assert.ok(!new RegExp(`${name}\\s*:`).test(status), `the status page does not define ${name}, so the fallback stands there`);
230
+ }
231
+ assert.equal(root['--cut-path'], 'polygon(var(--cut) 0, 100% 0, 100% calc(100% - var(--cut)), calc(100% - var(--cut)) 100%, 0 100%, 0 var(--cut))', 'top-left and bottom-right are cut');
232
+ assert.equal(root['--name-case'], 'uppercase');
233
+ // a link in a sentence is not a cut control and does not shout
234
+ const text = sharedWith('.btn--text', 'clip-path');
235
+ assert.equal(prop(text, 'clip-path'), 'none');
236
+ assert.equal(prop(text, 'text-transform'), 'none');
237
+ });
238
+
239
+ test('a cut button keeps a visible focus ring: inset, two-tone, 3:1 on its own fill', () => {
240
+ // clip-path clips the outline and the outer halo, so the base rule hands the shell's
241
+ // :focus-visible rule an inset ring instead; the text button (not cut) hands it back
242
+ assert.equal(prop(sharedRule('.btn'), '--focus-ring'), 'var(--press-focus)');
243
+ assert.equal(prop(sharedWith('.btn--text', '--focus-ring'), '--focus-ring'), 'initial');
244
+ const shell = rulesOf(styleSrc).find((x) => x.sels.length === 1 && x.sels[0] === ':focus-visible');
245
+ assert.ok(shell, 'the shell\'s ring rule');
246
+ assert.match(prop(shell.body, 'box-shadow'), /^var\(--focus-ring, /, 'the shell\'s ring spends what a cut control hands in');
247
+ assert.equal(root['--press-focus'], 'inset 0 0 0 2px var(--focus), inset 0 0 0 4px var(--bg)', 'the focus colour on the edge, then a 2px band of the ground');
248
+ // one of the two tones reads against each fill a cut button paints (WCAG 1.4.11, 3:1), in the mode the hall renders
249
+ for (const [what, fill] of [['primary', 'var(--accent)'], ['primary hover', 'var(--accent-lift)'], ['ghost on the page', onGround(GHOST_FILL, 'var(--bg)', 'dark')], ['ghost on a panel', onGround(GHOST_FILL, 'var(--bg-card)', 'dark')], ['danger', 'var(--danger)']]) {
250
+ const best = Math.max(contrast('var(--focus)', fill, 'dark'), contrast('var(--bg)', fill, 'dark'));
251
+ assert.ok(best >= 3, `dark ${what}: the ring's better tone is ${best.toFixed(2)}:1 on the fill`);
252
+ }
253
+ });
206
254
 
207
255
  test('every older button name sits in the shared rule it means', () => {
208
256
  const fill = (sel) => {
@@ -210,11 +258,11 @@ test('every older button name sits in the shared rule it means', () => {
210
258
  return r.length ? prop(r[0].body, 'background') : null;
211
259
  };
212
260
  for (const sel of PRIMARY) assert.equal(fill(sel), 'var(--accent)', `${sel} is a primary door`);
213
- for (const sel of GHOST) assert.equal(fill(sel), 'transparent', `${sel} is a ghost`);
261
+ for (const sel of GHOST) assert.equal(fill(sel), GHOST_FILL, `${sel} is a ghost`);
214
262
  for (const sel of SMALL) assert.equal(prop(sharedWith(sel, 'min-height'), 'min-height'), '30px', `${sel} is the row size`);
215
263
  // and the primary-on-a-ghost-base names come AFTER the ghost rule, or the ghost wins on order
216
264
  const list = rulesOf(controls);
217
- const ghostAt = list.findIndex((x) => x.sels.includes('.td-btn') && prop(x.body, 'background') === 'transparent');
265
+ const ghostAt = list.findIndex((x) => x.sels.includes('.td-btn') && prop(x.body, 'background') === GHOST_FILL);
218
266
  const primaryAt = list.findIndex((x) => x.sels.includes('.td-btn--primary') && prop(x.body, 'background'));
219
267
  assert.ok(primaryAt > ghostAt, '.td-btn--primary follows .td-btn, so the shared order paints it');
220
268
  });