@bongos/core 1.24.80 → 1.24.82
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 +154 -124
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +4 -0
- package/clients/bongos-client/index.cjs +4 -0
- package/clients/bongos-client/index.d.ts +5 -0
- package/clients/bongos-client/index.mjs +4 -0
- package/docs/adr/0402-helm-is-the-halls-look-and-the-hall-is-dark-only.md +142 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +86 -2
- package/docs/api-reference.md +4 -2
- package/docs/copy-inventory.md +51 -50
- package/docs/copy-registry.json +121 -123
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +2 -3
- package/docs/page-readings.json +226 -220
- package/modules/builder-settings/hall-view-prefs.js +50 -0
- package/modules/builder-settings/routes/settings.js +32 -0
- package/modules/hall-ui/public/ai-studio.html +5 -3
- package/modules/hall-ui/public/ai-studio.states.json +1 -0
- package/modules/hall-ui/public/atlas.html +3 -3
- package/modules/hall-ui/public/atlas.states.json +1 -0
- package/modules/hall-ui/public/blockers.html +3 -3
- package/modules/hall-ui/public/board-room.html +3 -3
- package/modules/hall-ui/public/board-room.states.json +1 -0
- package/modules/hall-ui/public/city.css +1 -3
- package/modules/hall-ui/public/city.html +6 -9
- package/modules/hall-ui/public/city.js +10 -55
- package/modules/hall-ui/public/city.states.json +7 -32
- package/modules/hall-ui/public/collab.html +3 -3
- package/modules/hall-ui/public/collab.states.json +1 -0
- package/modules/hall-ui/public/commands.html +3 -3
- package/modules/hall-ui/public/commands.states.json +1 -0
- package/modules/hall-ui/public/controls.css +63 -11
- package/modules/hall-ui/public/copy-desk.html +3 -3
- package/modules/hall-ui/public/copy-desk.states.json +1 -0
- package/modules/hall-ui/public/deploy.html +3 -3
- package/modules/hall-ui/public/deploy.states.json +1 -0
- package/modules/hall-ui/public/goal-map.html +4 -4
- package/modules/hall-ui/public/goals.html +3 -3
- package/modules/hall-ui/public/goals.states.json +1 -0
- package/modules/hall-ui/public/government.html +3 -3
- package/modules/hall-ui/public/government.states.json +1 -0
- package/modules/hall-ui/public/idea.html +3 -3
- package/modules/hall-ui/public/idea.states.json +1 -0
- package/modules/hall-ui/public/ideas.html +3 -3
- package/modules/hall-ui/public/ideas.states.json +1 -0
- package/modules/hall-ui/public/index-founding.states.json +1 -0
- package/modules/hall-ui/public/index-genesis-decide.states.json +1 -0
- package/modules/hall-ui/public/index-genesis-demo.states.json +1 -0
- package/modules/hall-ui/public/index-genesis.states.json +1 -0
- package/modules/hall-ui/public/index.html +5 -5
- package/modules/hall-ui/public/index.states.json +1 -0
- package/modules/hall-ui/public/market-watch-item.html +3 -3
- package/modules/hall-ui/public/market-watch-item.states.json +1 -0
- package/modules/hall-ui/public/market-watch-rules.html +3 -3
- package/modules/hall-ui/public/market-watch-rules.states.json +1 -0
- package/modules/hall-ui/public/market-watch.html +3 -3
- package/modules/hall-ui/public/market-watch.states.json +1 -0
- package/modules/hall-ui/public/modules.html +3 -3
- package/modules/hall-ui/public/modules.states.json +1 -0
- package/modules/hall-ui/public/money.html +3 -3
- package/modules/hall-ui/public/money.states.json +1 -0
- package/modules/hall-ui/public/my-agents.html +3 -3
- package/modules/hall-ui/public/my-agents.states.json +1 -0
- package/modules/hall-ui/public/options-menu.js +2 -1
- package/modules/hall-ui/public/primer.html +3 -3
- package/modules/hall-ui/public/primer.states.json +1 -0
- package/modules/hall-ui/public/profile.html +3 -3
- package/modules/hall-ui/public/profile.states.json +1 -0
- package/modules/hall-ui/public/project-settings.html +3 -3
- package/modules/hall-ui/public/roster.html +3 -3
- package/modules/hall-ui/public/roster.states.json +1 -0
- package/modules/hall-ui/public/settings-view.js +75 -0
- package/modules/hall-ui/public/settings.html +17 -4
- package/modules/hall-ui/public/settings.states.json +1 -0
- package/modules/hall-ui/public/shell-game.css +97 -42
- package/modules/hall-ui/public/shell-game.js +138 -40
- package/modules/hall-ui/public/shell.js +12 -43
- package/modules/hall-ui/public/street-card.states.json +1 -0
- package/modules/hall-ui/public/street.css +1 -5
- package/modules/hall-ui/public/street.html +5 -9
- package/modules/hall-ui/public/street.js +8 -59
- package/modules/hall-ui/public/street.states.json +7 -32
- package/modules/hall-ui/public/studio.html +3 -3
- package/modules/hall-ui/public/studio.states.json +1 -0
- package/modules/hall-ui/public/style.css +126 -65
- package/modules/hall-ui/public/task.html +3 -3
- package/modules/hall-ui/public/task.states.json +1 -0
- package/modules/hall-ui/public/thinking.html +4 -4
- package/modules/hall-ui/public/thinking.states.json +1 -0
- package/modules/hall-ui/public/tweak-editor.html +3 -3
- package/modules/hall-ui/public/tweak-editor.states.json +1 -0
- package/modules/hall-ui/public/visor.js +7 -1
- package/modules/hall-ui/public/watch.html +3 -3
- package/modules/hall-ui/public/watch.states.json +1 -0
- package/modules/hall-ui/public/work-founding.states.json +1 -0
- package/modules/hall-ui/public/work.html +3 -3
- package/modules/hall-ui/public/work.states.json +1 -0
- package/modules/hall-ui/records/game-mode.md +5 -4
- package/modules/hall-ui/records/start-screen.md +10 -2
- package/modules/hall-ui/specimens/components.states.json +2 -1
- package/modules/ui-design/public/brand.html +2 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/hall-preview/README.md +2 -3
- package/scripts/hall-preview/pref-writes.js +8 -1
- package/src/bongos/serve-internal.js +7 -2
- package/src/hall-helm.js +79 -0
- package/src/module-api.js +1 -1
- package/tests/design_reference.mjs +1 -1
- package/tests/hall_builders_page_pass.mjs +2 -1
- package/tests/hall_city.mjs +15 -29
- package/tests/hall_controls.mjs +52 -4
- package/tests/hall_fullscreen.mjs +42 -21
- package/tests/hall_helm.mjs +187 -0
- package/tests/hall_record_world.mjs +4 -2
- package/tests/hall_start_screen.mjs +103 -5
- package/tests/hall_street.mjs +13 -26
- package/tests/hall_topbar_320.mjs +4 -2
- package/tests/hall_view_prefs.mjs +42 -0
- package/tests/hall_visor.mjs +3 -2
- package/tests/nav_permission_atoms.mjs +1 -1
|
@@ -11,11 +11,19 @@ Covers: Home (`shell-game.js` `start()`, `shell-game.css`, the hand-off in `welc
|
|
|
11
11
|
- **Tips rotate** (`localStorage 'hall-game-tip'` counts them), so each load shows the next one. A tip must be true of the hall today; the first one is the Escape rule from `game-mode.md` (rewritten by task 1005144: Escape opens the options menu).
|
|
12
12
|
- **The craft picker waits for the world.** While the start screen is up the shell asks no picker (`onAccess` returned true). `done` runs once the screen is gone and asks `OTBCraftMode.onAccess` then.
|
|
13
13
|
- **Keys.** While either screen is up it keeps every key: the arrows and Tab walk the menu, Escape folds Extras away and does nothing else, so the shell's Escape cannot flip the bars underneath. Once the world shows, the last Escape opens the options menu (task 1005144); the screen's capture listener calls `preventDefault`, so the menu never opens from under it.
|
|
14
|
-
- **The look.** The
|
|
14
|
+
- **The look is Helm (task 1005192, [ADR 0402](../../../docs/adr/0402-helm-is-the-halls-look-and-the-hall-is-dark-only.md)).** The start screen is one full-height night panel over the painting (`--night-glass`, an ice hairline left and right at `--line-strong`), `--helm-panel-w` wide. On a desk it stands off the left edge by `--helm-inset`; that token is a `clamp`, so on a phone the panel is the whole width and the sheet still has no `@media`. In it: the mark (the project's logo when it has one) over the name in the name voice; Start as a full-width accent bar under `--accent-on` that names its key ("Enter", drawn by CSS so the choice's own text stays "Start"); the other four as quiet rows on `--rule-soft` hairlines; and at the foot, over the strong hairline, the tip the load screen will show next (`#gs-line`), with the Quit note under it when the browser keeps the tab. The rows are list rows in a panel, so they are bars, not cut buttons. A row's focus is the inset two-tone ring (`--press-focus`), which is what makes focus visible on the accent bar.
|
|
15
|
+
- **The load screen is the same panel, centred** over the painting under the hall's `--scrim`: the week in the name voice (`#gs-when`), one segment per week of the history (`#gs-segs`, the shared `.segs` / `.seg`; filled up to the week being shown, counted the way the sentence words it, and hidden from a screen reader because the sentence says the same), the city, and the tip under a hairline. With no city there are no segments.
|
|
16
|
+
- **The literal floor is zero.** Every colour, size, corner, shadow, face and measure in `shell-game.css` is a hall-tier token (`style.css`); `tests/hall_start_screen.mjs` fails on a typed one. The fade into the world is one CSS opacity transition, stopped by the hall's reduced-motion rule.
|
|
15
17
|
|
|
16
18
|
## For the tasks after this one
|
|
17
19
|
|
|
18
20
|
- R04 (task 1005144): point Options at the options menu and move Achievements/Extras targets if those pages lose their interfaces. Escape on the start screen is free for the menu to use.
|
|
19
21
|
- `shell-game.js` is loaded on every hall page but only Home calls `start()`; `shell-game.css` is linked on Home only. A second page that wants the start screen links the sheet and calls `start()` with its own still.
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
- New art for the start screen (the city in the clouds) is task 1005196; `welcome-boot.js` still hands in the welcome's painting.
|
|
24
|
+
|
|
25
|
+
Tests: `tests/hall_start_screen.mjs` (the hand-off and pure parts in a vm; both screens, the panel's place at desk and phone widths, the segments, Quit, the no-city and reduced-motion paths in a real browser).
|
|
26
|
+
|
|
27
|
+
## The view chooser (task 1005200, owner 2026-11-11)
|
|
28
|
+
|
|
29
|
+
Every sign-in now opens on a chooser before the start screen: **Game** or **Classic**, each with one line saying what it is. The view chosen last time (the browser's `hall-game` copy) is marked "Last time", lit with the accent on its edge, and focused, so Enter keeps it. Game goes on to the start screen as before; Classic closes the screen and the hall is as it always was. Either choice is saved to the account (`PATCH /api/bongos/me/hall-view`). The Extras list lost The docs: it is not a game place. Tests: `tests/hall_start_screen.mjs`.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_": "The hall's shared components, each in each tone and state (task 1005086, goal 1000138): the status pill, the panel, the empty state, the loading state, the toast per kind, the tabs and view toggle, the buttons, the field and the dialog. Rendered by the ui-design kit
|
|
2
|
+
"_": "The hall's shared components, each in each tone and state (task 1005086, goal 1000138): the status pill, the panel, the empty state, the loading state, the toast per kind, the tabs and view toggle, the buttons, the field and the dialog. Rendered by the ui-design kit, dark only like the hall (task 1005192), at 1440/390/320: node modules/ui-design/kit/render.js --states modules/hall-ui/specimens/components.states.json. The public design-system reference (task 1005083) is meant to build on this.",
|
|
3
3
|
"page": "/components",
|
|
4
|
+
"modes": ["dark"],
|
|
4
5
|
"surface": "modules/hall-ui/specimens",
|
|
5
6
|
"stub": {
|
|
6
7
|
"prefix": "/"
|
|
@@ -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>#
|
|
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(
|
|
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.
|
|
3
|
+
"version": "1.24.82",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.24.
|
|
9
|
+
"version": "1.24.82",
|
|
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.
|
|
3
|
+
"version": "1.24.82",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
package/release-notes.json
CHANGED
|
@@ -11583,5 +11583,17 @@
|
|
|
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
|
+
}
|
|
11592
|
+
],
|
|
11593
|
+
"1.24.82": [
|
|
11594
|
+
{
|
|
11595
|
+
"id": "1005200",
|
|
11596
|
+
"text": "The hall now has two separate views, Game and Classic, and never mixes them. You choose one each time you sign in and can switch only in Options. Game mode stays full screen and only opens the game's places."
|
|
11597
|
+
}
|
|
11586
11598
|
]
|
|
11587
11599
|
}
|
|
@@ -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 **{
|
|
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.
|
|
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
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
//
|
|
15
15
|
// What it is NOT: storage. Nothing is written to disk and a restart forgets
|
|
16
16
|
// everything; each recorded write is logged so the harness console shows what the
|
|
17
|
-
// page sent. Only these
|
|
17
|
+
// page sent. Only these routes are recorded — every other write is refused
|
|
18
18
|
// exactly as before.
|
|
19
19
|
'use strict';
|
|
20
20
|
|
|
@@ -27,6 +27,7 @@ const wandering = lib('wandering-prefs');
|
|
|
27
27
|
const models = lib('model-alloc-prefs');
|
|
28
28
|
const render = lib('render-prefs');
|
|
29
29
|
const skills = lib('skill-prefs');
|
|
30
|
+
const hallView = lib('hall-view-prefs'); // task 1005200: game or classic
|
|
30
31
|
|
|
31
32
|
// apiPath → the /me slice it answers for (null: the skill prefs, not on /me)
|
|
32
33
|
const ROUTES = {
|
|
@@ -35,6 +36,7 @@ const ROUTES = {
|
|
|
35
36
|
'me/model-allocation': 'model_allocation',
|
|
36
37
|
'me/render': 'render',
|
|
37
38
|
'me/skill-prefs': null,
|
|
39
|
+
'me/hall-view': 'hall_view',
|
|
38
40
|
};
|
|
39
41
|
|
|
40
42
|
function createPrefRecorder({ log = console.log } = {}) {
|
|
@@ -63,6 +65,7 @@ function createPrefRecorder({ log = console.log } = {}) {
|
|
|
63
65
|
if (slice === 'wandering') return wandering.describeForApi(behavior, rank);
|
|
64
66
|
if (slice === 'model_allocation') return models.describeForApi(behavior, instanceModels);
|
|
65
67
|
if (slice === 'render') return render.describeForApi(behavior);
|
|
68
|
+
if (slice === 'hall_view') return hallView.describeForApi(behavior);
|
|
66
69
|
return {
|
|
67
70
|
prefs: skillPrefs, effective: skills.listEffective(skillPrefs), defaults: skills.SKILL_DEFAULTS,
|
|
68
71
|
known_skills: skills.KNOWN_SKILLS, allowed_models: skills.ALLOWED_MODELS,
|
|
@@ -96,6 +99,10 @@ function createPrefRecorder({ log = console.log } = {}) {
|
|
|
96
99
|
v = render.validatePatch(b);
|
|
97
100
|
if (!v.ok) return invalid(v.errors);
|
|
98
101
|
behavior = render.applyPatch(behavior, v.patch);
|
|
102
|
+
} else if (slice === 'hall_view') {
|
|
103
|
+
v = hallView.validatePatch(b);
|
|
104
|
+
if (!v.ok) return invalid(v.errors);
|
|
105
|
+
behavior = hallView.applyView(behavior, v.view);
|
|
99
106
|
} else {
|
|
100
107
|
v = skills.validatePatch(b);
|
|
101
108
|
if (!v.ok) return invalid(v.errors);
|
|
@@ -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
|
-
|
|
130
|
-
|
|
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
|
|
package/src/hall-helm.js
ADDED
|
@@ -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.
|
|
84
|
+
const CORE_VERSION = '1.24.82'; // 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>#
|
|
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
|
-
|
|
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.
|
package/tests/hall_city.mjs
CHANGED
|
@@ -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
|
-
// *
|
|
16
|
-
//
|
|
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
|
|
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> & <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,
|
|
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 ['
|
|
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
|
|
425
|
-
const p = await bootPage({ feeds: FEEDS([]), stored: 'light', osDark:
|
|
426
|
-
assert.equal(p.html.attrs['data-theme'], '
|
|
427
|
-
assert.equal(p.
|
|
428
|
-
|
|
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.
|
|
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
|
-
|
|
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 } });
|
package/tests/hall_controls.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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),
|
|
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') ===
|
|
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
|
});
|