@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
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// modules/builder-settings/hall-view-prefs.js — which view of the hall a builder
|
|
2
|
+
// uses: the GAME or CLASSIC (task 1005200, BONGOS-V3; owner, 2026-11-11).
|
|
3
|
+
//
|
|
4
|
+
// The two views never mix. Classic is the hall as it always was; game is full
|
|
5
|
+
// screen, the game places only, with the visor (modules/hall-ui/public/shell-game.js).
|
|
6
|
+
// A builder picks one at every sign-in and switches only in Options → Preferences
|
|
7
|
+
// → View, and the choice follows them to any browser — so it is SERVER-side, not
|
|
8
|
+
// localStorage (the browser keeps a copy only so a page can paint the right view
|
|
9
|
+
// before this read answers).
|
|
10
|
+
//
|
|
11
|
+
// Storage: one reserved key ('hall_view') inside the `behavior_prefs` jsonb on
|
|
12
|
+
// `builders` (migration 119 — the blob the wandering/render/interaction knobs
|
|
13
|
+
// share), so this adds NO migration. A missing key means "never chosen": the
|
|
14
|
+
// sign-in chooser asks, and nothing else assumes a view for them.
|
|
15
|
+
//
|
|
16
|
+
// This is a look, never authority: it hides no route and gates nothing (ADR 0016).
|
|
17
|
+
|
|
18
|
+
const RESERVED_KEY = 'hall_view';
|
|
19
|
+
const VIEWS = ['game', 'classic'];
|
|
20
|
+
|
|
21
|
+
// The stored view, or null when the builder has never chosen.
|
|
22
|
+
function storedView(behaviorPrefs) {
|
|
23
|
+
const raw = behaviorPrefs && behaviorPrefs[RESERVED_KEY];
|
|
24
|
+
return VIEWS.includes(raw) ? raw : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function describeForApi(behaviorPrefs) {
|
|
28
|
+
return { view: storedView(behaviorPrefs), views: VIEWS.slice() };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// A PATCH body: { view: 'game' | 'classic' }. Anything else → 422.
|
|
32
|
+
function validatePatch(body) {
|
|
33
|
+
if (body == null || typeof body !== 'object' || Array.isArray(body)) {
|
|
34
|
+
return { ok: false, errors: [{ error: 'body_must_be_object' }] };
|
|
35
|
+
}
|
|
36
|
+
const errors = [];
|
|
37
|
+
for (const key of Object.keys(body)) {
|
|
38
|
+
if (key !== 'view') errors.push({ key, error: 'unknown_key', allowed: ['view'] });
|
|
39
|
+
}
|
|
40
|
+
if (!VIEWS.includes(body.view)) errors.push({ key: 'view', error: 'invalid_value', value: body.view, allowed: VIEWS });
|
|
41
|
+
if (errors.length > 0) return { ok: false, errors };
|
|
42
|
+
return { ok: true, view: body.view };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Pure: the builder's behavior_prefs with the view set; sibling keys untouched.
|
|
46
|
+
function applyView(existing, view) {
|
|
47
|
+
return { ...(existing || {}), [RESERVED_KEY]: view };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = { RESERVED_KEY, VIEWS, storedView, describeForApi, validatePatch, applyView };
|
|
@@ -31,6 +31,7 @@ const lawLocks = require('../law-locks');
|
|
|
31
31
|
const renderPrefs = require('../render-prefs');
|
|
32
32
|
const profileNudgePrefs = require('../profile-nudge-prefs');
|
|
33
33
|
const coreMoveSeen = require('../core-move-seen');
|
|
34
|
+
const hallViewPrefs = require('../hall-view-prefs'); // task 1005200
|
|
34
35
|
const checkoutFreshness = require('../checkout-freshness'); // task 1005021
|
|
35
36
|
const displayName = require('../display-name');
|
|
36
37
|
const filterViews = require('../filter-views');
|
|
@@ -518,6 +519,37 @@ module.exports = function buildBuilderSettingsRouter() {
|
|
|
518
519
|
}
|
|
519
520
|
});
|
|
520
521
|
|
|
522
|
+
// Which view of the hall this builder uses, game or classic (task 1005200,
|
|
523
|
+
// hall-view-prefs.js): asked at every sign-in, switched in Options → View, and
|
|
524
|
+
// kept on the account so it follows them to any browser. A look, never authority.
|
|
525
|
+
// Body: { view: 'game' | 'classic' }.
|
|
526
|
+
//
|
|
527
|
+
// rank: any-builder — own view choice; same posture as /me/profile-nudge.
|
|
528
|
+
router.get('/me/hall-view', api.requireBuilder, async (req, res) => {
|
|
529
|
+
try {
|
|
530
|
+
res.json(hallViewPrefs.describeForApi(await db.getBuilderBehaviorPrefs(req.builder.id)));
|
|
531
|
+
} catch (err) {
|
|
532
|
+
log.error('GET /me/hall-view', err);
|
|
533
|
+
res.fail('read_failed', { status: 500, message: 'internal error' });
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
// rank: any-builder — own view choice; same posture as /me/profile-nudge.
|
|
537
|
+
router.patch('/me/hall-view', api.requireBuilder, async (req, res) => {
|
|
538
|
+
if (api.validateOrRespond(req, res, {
|
|
539
|
+
view: { required: true, type: 'string', maxLength: 16 },
|
|
540
|
+
}, { strict: true })) return undefined;
|
|
541
|
+
try {
|
|
542
|
+
const v = hallViewPrefs.validatePatch(req.body || {});
|
|
543
|
+
if (!v.ok) return res.fail('invalid_patch', 422, { errors: v.errors });
|
|
544
|
+
const existing = await db.getBuilderBehaviorPrefs(req.builder.id);
|
|
545
|
+
const saved = await db.setBuilderBehaviorPrefs(req.builder.id, hallViewPrefs.applyView(existing, v.view));
|
|
546
|
+
res.json(hallViewPrefs.describeForApi(saved));
|
|
547
|
+
} catch (err) {
|
|
548
|
+
log.error('PATCH /me/hall-view', err);
|
|
549
|
+
res.fail('write_failed', { status: 500, message: 'internal error' });
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
|
|
521
553
|
// How far behind this builder's local project folder is (task 1005021,
|
|
522
554
|
// checkout-freshness.js). Written by their own session start; read back by GET /me,
|
|
523
555
|
// which tells the person (and their agent) when the folder could not update itself.
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
<title>Studio — {{worldName}}</title>
|
|
9
9
|
<meta name="description" content="Make something with AI in the studio: bring sketches, pictures or a PDF, say what you want, and get an answer and pictures back.">
|
|
10
10
|
<link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
|
|
11
|
-
<link rel="
|
|
11
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
12
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
13
|
+
<link rel="stylesheet" href="/builders/style.css?v=2026-11-11-view2">
|
|
12
14
|
<link rel="stylesheet" href="/builders/studio.css?v=2026-10-09-day">
|
|
13
15
|
<link rel="stylesheet" href="/builders/ai-studio.css?v=2026-10-29-ais1">
|
|
14
16
|
</head>
|
|
@@ -87,10 +89,10 @@
|
|
|
87
89
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
88
90
|
|
|
89
91
|
<script src="/builders/dom-utils.js?v=2026-11-05-numbers"></script>
|
|
90
|
-
<script src="/builders/shell-game.js?v=2026-11-
|
|
92
|
+
<script src="/builders/shell-game.js?v=2026-11-11-view2"></script>
|
|
91
93
|
<script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
|
|
92
94
|
<script src="/builders/craft-mode.js?v=2026-11-10-visor"></script>
|
|
93
|
-
<script src="/builders/shell.js?v=2026-11-
|
|
95
|
+
<script src="/builders/shell.js?v=2026-11-11-view2"></script>
|
|
94
96
|
<script src="/builders/hall-dialog.js"></script>
|
|
95
97
|
<script src="/builders/palette.js" defer></script>
|
|
96
98
|
<script src="/builders/tweak-editor-lib.js?v=2026-09-28-tw23"></script>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_": "The studio's AI pages (task 1004921, goal 1000136), built 1:1 on option C of the AI Studio options canvas plus the owner's addition (change the request on the result and make it again). Against the kit's own stub the page renders its could-not-read states (no ai-studio feeds). TO SEE IT POPULATED, point the kit at the hall-preview harness with the module on: `node scripts/hall-preview/server.js --port 4637 --fixture-me --modules-on ai-studio` then `render.js --base-in http://builders.localhost:4637 --base-out http://builders.localhost:4637`. The harness answers keys, balance, the top-up quote and the request list from the ai-studio__*.json fixtures; a request by id (1002 running, 1003 failed, 1004 own Gemini key, any other the finished result on the balance); an upload, a request and a top-up from its CANNED WRITES; and every file as a stand-in picture. ?from=1001 opens the steps on that request's files and words, which is how the populated steps render without an upload.",
|
|
3
3
|
"page": "/builders/ai-studio",
|
|
4
|
+
"modes": ["dark"],
|
|
4
5
|
"_storage": "The hall asks a first-visit question (the session mode picker, craft-mode.js) over every page; seeding \"already asked\" renders the page a returning builder sees (task 1005080).",
|
|
5
6
|
"storage": { "session": { "hall-craft-asked": "1" } },
|
|
6
7
|
"surface": "hall-ui",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
|
|
9
9
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
10
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
-
<link rel="stylesheet" href="/builders/style.css?v=2026-11-
|
|
11
|
+
<link rel="stylesheet" href="/builders/style.css?v=2026-11-11-view2">
|
|
12
12
|
<!-- task 1003472: the island index under the map is a run of oversight rows,
|
|
13
13
|
and the detail panel's facts are the family's lines, so the family sheet
|
|
14
14
|
loads first and atlas.css holds only the scene. -->
|
|
@@ -90,10 +90,10 @@
|
|
|
90
90
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
91
91
|
|
|
92
92
|
<script src="/builders/dom-utils.js?v=2026-11-05-numbers"></script>
|
|
93
|
-
<script src="/builders/shell-game.js?v=2026-11-
|
|
93
|
+
<script src="/builders/shell-game.js?v=2026-11-11-view2"></script>
|
|
94
94
|
<script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
|
|
95
95
|
<script src="/builders/craft-mode.js?v=2026-11-10-visor"></script>
|
|
96
|
-
<script src="/builders/shell.js?v=2026-11-
|
|
96
|
+
<script src="/builders/shell.js?v=2026-11-11-view2"></script>
|
|
97
97
|
<script src="/builders/hall-dialog.js"></script>
|
|
98
98
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
99
99
|
<script src="/builders/palette.js" defer></script>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_": "Repo atlas — the self-drawing module map. Its SEMANTIC node palette is fixed by ADR 0094 and is not the token tier's business; everything around it (the panel, the view switch, the caption, the detail card, the legend, the island index) is chrome and rides the world. Written with the re-skin (task 1003307) so this page is MEASURED at three widths in both modes rather than looked at once; the redesign (task 1003472) added the `truth`, `detail` and `index` states. Populated look: the hall-preview harness serves modules/hall-ui/public/atlas.json when scripts/bongos/gen-atlas.js has written it (the file is generated on deploy and gitignored, so run the generator first).",
|
|
3
3
|
"page": "/builders/atlas",
|
|
4
|
+
"modes": ["dark"],
|
|
4
5
|
"_storage": "The hall asks a first-visit question (the session mode picker, craft-mode.js) over every page; seeding \"already asked\" renders the page a returning builder sees (task 1005080).",
|
|
5
6
|
"storage": { "session": { "hall-craft-asked": "1" } },
|
|
6
7
|
"surface": "hall-ui",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
|
|
9
9
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
10
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
-
<link rel="stylesheet" href="/builders/style.css?v=2026-11-
|
|
11
|
+
<link rel="stylesheet" href="/builders/style.css?v=2026-11-11-view2">
|
|
12
12
|
<link rel="stylesheet" href="/builders/hall-ask.css">
|
|
13
13
|
<!-- task 1003455: the row shape (title + facts) is the oversight family's,
|
|
14
14
|
the same archetype collab/copy-desk/watch/gate already ride. -->
|
|
@@ -59,10 +59,10 @@
|
|
|
59
59
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
60
60
|
chrome exists before the page script runs. -->
|
|
61
61
|
<script src="/builders/dom-utils.js?v=2026-11-05-numbers"></script>
|
|
62
|
-
<script src="/builders/shell-game.js?v=2026-11-
|
|
62
|
+
<script src="/builders/shell-game.js?v=2026-11-11-view2"></script>
|
|
63
63
|
<script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
|
|
64
64
|
<script src="/builders/craft-mode.js?v=2026-11-10-visor"></script>
|
|
65
|
-
<script src="/builders/shell.js?v=2026-11-
|
|
65
|
+
<script src="/builders/shell.js?v=2026-11-11-view2"></script>
|
|
66
66
|
<script src="/builders/hall-dialog.js"></script>
|
|
67
67
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
68
68
|
<script src="/builders/palette.js" defer></script>
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
|
|
9
9
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
10
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
-
<link rel="stylesheet" href="/builders/style.css?v=2026-11-
|
|
11
|
+
<link rel="stylesheet" href="/builders/style.css?v=2026-11-11-view2">
|
|
12
12
|
<link rel="stylesheet" href="/builders/hall-ask.css">
|
|
13
13
|
<link rel="stylesheet" href="/builders/oversight.css">
|
|
14
14
|
<!-- The room's own sheet: the sitting card and the ballot controls moved here
|
|
@@ -61,10 +61,10 @@
|
|
|
61
61
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
62
62
|
|
|
63
63
|
<script src="/builders/dom-utils.js?v=2026-11-05-numbers"></script>
|
|
64
|
-
<script src="/builders/shell-game.js?v=2026-11-
|
|
64
|
+
<script src="/builders/shell-game.js?v=2026-11-11-view2"></script>
|
|
65
65
|
<script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
|
|
66
66
|
<script src="/builders/craft-mode.js?v=2026-11-10-visor"></script>
|
|
67
|
-
<script src="/builders/shell.js?v=2026-11-
|
|
67
|
+
<script src="/builders/shell.js?v=2026-11-11-view2"></script>
|
|
68
68
|
<script src="/builders/hall-dialog.js"></script>
|
|
69
69
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
70
70
|
<script src="/builders/palette.js" defer></script>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_": "Board Room — the sittings, the ballot and the vote form, carved out of the /government tab strip onto its own page by ADR 0266. Unlike its neighbours in the Government group this page is gated on the `board.vote.cast` atom (xenos floor) rather than page.view.government, so the sealed state here means the permission was withheld, not that the reader stands below a rung.",
|
|
3
3
|
"page": "/builders/board-room",
|
|
4
|
+
"modes": ["dark"],
|
|
4
5
|
"_storage": "The hall asks a first-visit question (the session mode picker, craft-mode.js) over every page; seeding \"already asked\" renders the page a returning builder sees (task 1005080).",
|
|
5
6
|
"storage": { "session": { "hall-craft-asked": "1" } },
|
|
6
7
|
"surface": "hall-ui",
|
|
@@ -78,8 +78,6 @@ html:has(body[data-page="city"]) { height: 100%; }
|
|
|
78
78
|
/* the header: wordmark left, the sun/moon switch right */
|
|
79
79
|
.city-top { position: absolute; left: 32px; right: 32px; top: 0; height: 64px; display: flex; align-items: center; justify-content: space-between; z-index: 3; }
|
|
80
80
|
.city-mark { font-weight: 500; font-size: 17px; letter-spacing: -0.015em; text-transform: lowercase; }
|
|
81
|
-
.city-mode { background: none; border: 1px solid var(--rule); border-radius: 999px; height: 36px; padding: 0 11px; display: flex; align-items: center; gap: 8px; cursor: pointer; color: var(--ink); font-family: var(--font-body); }
|
|
82
|
-
.city-mode svg { display: block; stroke-width: 1.4; }
|
|
83
81
|
|
|
84
82
|
/* THE STAGE IN THE HALL (task 1004544, the sky's mechanism from task 1004327).
|
|
85
83
|
<main class="city-main"> is the box under the hall's top bar and beside its
|
|
@@ -139,7 +137,7 @@ html[data-fullscreen="on"] .city-top { right: 184px; }
|
|
|
139
137
|
.city-start { display: inline-flex; align-items: center; box-sizing: border-box; font-family: var(--font-body); font-size: 17px; font-weight: 500; height: 56px; padding: 0 38px; border-radius: 999px; border: 0; cursor: pointer; background: var(--accent); color: var(--accent-on); text-transform: lowercase; text-decoration: none; transition: transform 200ms cubic-bezier(.2,.7,.3,1); }
|
|
140
138
|
.city-start:hover { background: var(--accent-lift); transform: translateY(-2px); }
|
|
141
139
|
.city-line { margin: 0; font-size: 14px; font-weight: 400; line-height: 1.4; color: var(--ink-soft); font-variant-numeric: tabular-nums; text-align: center; }
|
|
142
|
-
.city-
|
|
140
|
+
.city-start:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
|
|
143
141
|
|
|
144
142
|
@media (max-width: 560px) {
|
|
145
143
|
.city-top { left: 16px; right: 16px; }
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
links, injected into this head by the server like every hall page's
|
|
12
12
|
(tests/hall_tokens.mjs: no hall page hardcodes a web-font stylesheet).
|
|
13
13
|
The prototype carried its own Google Fonts link for the same two faces. -->
|
|
14
|
-
<link rel="stylesheet" href="/builders/style.css?v=2026-11-
|
|
14
|
+
<link rel="stylesheet" href="/builders/style.css?v=2026-11-11-view2">
|
|
15
15
|
<link rel="stylesheet" href="/builders/visor.css?v=2026-11-10-visor">
|
|
16
|
-
<link rel="stylesheet" href="/builders/city.css?v=2026-11-
|
|
16
|
+
<link rel="stylesheet" href="/builders/city.css?v=2026-11-11-helm">
|
|
17
17
|
<!-- the ask card in the glass language, shared with the street's deck (task 1004702) -->
|
|
18
18
|
<link rel="stylesheet" href="/builders/street-deck.css?v=2026-10-17-tidy">
|
|
19
19
|
</head>
|
|
@@ -70,9 +70,6 @@
|
|
|
70
70
|
<main id="hall-main" class="city-main">
|
|
71
71
|
<div class="city-top">
|
|
72
72
|
<span class="city-mark">{{productName}}</span>
|
|
73
|
-
<button type="button" class="city-mode" id="cityMode" aria-label="Switch to light mode" title="Switch to light mode">
|
|
74
|
-
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" aria-hidden="true"><path id="cityModeIcon" d=""></path></svg>
|
|
75
|
-
</button>
|
|
76
73
|
</div>
|
|
77
74
|
<div class="city-plaque" id="cityPlaque" aria-label="The government at a glance" hidden></div>
|
|
78
75
|
|
|
@@ -107,13 +104,13 @@
|
|
|
107
104
|
</div>
|
|
108
105
|
|
|
109
106
|
<script src="/builders/dom-utils.js?v=2026-11-05-numbers"></script>
|
|
110
|
-
<script src="/builders/shell-game.js?v=2026-11-
|
|
107
|
+
<script src="/builders/shell-game.js?v=2026-11-11-view2" defer></script>
|
|
111
108
|
<script src="/builders/shell-trail.js?v=2026-11-05-numbers" defer></script>
|
|
112
109
|
<script src="/builders/craft-mode.js?v=2026-11-10-visor" defer></script>
|
|
113
|
-
<script src="/builders/shell.js?v=2026-11-
|
|
110
|
+
<script src="/builders/shell.js?v=2026-11-11-view2" defer></script>
|
|
114
111
|
<script src="/builders/hall-dialog.js" defer></script>
|
|
115
112
|
<!-- task 1005148: the visor (glass over the scene, game mode only; ADR 0399), after the shell and hall-dialog.js -->
|
|
116
|
-
<script src="/builders/visor.js?v=2026-11-
|
|
113
|
+
<script src="/builders/visor.js?v=2026-11-11-view2" defer></script>
|
|
117
114
|
<script src="/builders/palette.js" defer></script>
|
|
118
115
|
<script src="/builders/gov-living.js?v=2026-10-09-positions1" defer></script>
|
|
119
116
|
<script src="/builders/gov-feeds.js?v=2026-10-09-living3" defer></script>
|
|
@@ -129,6 +126,6 @@
|
|
|
129
126
|
<script src="/builders/street-deck.js?v=2026-10-13-glass1" defer></script>
|
|
130
127
|
<script src="/builders/city-decide.js?v=2026-10-23-decide12" defer></script>
|
|
131
128
|
<script src="/builders/city-switchboard.js?v=2026-10-23-board5" defer></script>
|
|
132
|
-
<script src="/builders/city.js?v=2026-11-
|
|
129
|
+
<script src="/builders/city.js?v=2026-11-11-helm" defer></script>
|
|
133
130
|
</body>
|
|
134
131
|
</html>
|
|
@@ -24,11 +24,9 @@
|
|
|
24
24
|
// A feed that fails leaves its part of the city out; it never draws sample
|
|
25
25
|
// data. Without gov-living.js the page draws the city it drew before.
|
|
26
26
|
//
|
|
27
|
-
// THE
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
// in, and a change here carries back to them. The drawing's faces come from
|
|
31
|
-
// the mode, so a toggle redraws the city from the last feeds (no refetch).
|
|
27
|
+
// THE MODE. The hall is dark only (task 1005192, ADR 0402), so the city has no
|
|
28
|
+
// light/dark switch and reads no stored choice: it draws its night faces. Day
|
|
29
|
+
// and night come back as art (the city and its sky), never as a control.
|
|
32
30
|
//
|
|
33
31
|
// MOTION is CSS only (city.css); nothing here animates. The scale write on
|
|
34
32
|
// resize is layout, not motion; the card changes a class.
|
|
@@ -42,9 +40,6 @@
|
|
|
42
40
|
'use strict';
|
|
43
41
|
var City = window.GovCity;
|
|
44
42
|
var SVG_NS = 'http://www.w3.org/2000/svg';
|
|
45
|
-
var THEME_KEY = 'hall-theme';
|
|
46
|
-
var SUN = 'M7 1.5V3M7 11v1.5M1.5 7H3M11 7h1.5M3.1 3.1l1 1M9.9 9.9l1 1M3.1 10.9l1-1M9.9 4.1l1-1M9.5 7A2.5 2.5 0 1 1 4.5 7A2.5 2.5 0 1 1 9.5 7Z';
|
|
47
|
-
var MOON = 'M11.5 8.6A5 5 0 0 1 5.4 2.5A5 5 0 1 0 11.5 8.6Z';
|
|
48
43
|
var FEEDS = {
|
|
49
44
|
docket: '/api/bongos/government/docket',
|
|
50
45
|
goals: '/api/bongos/goals',
|
|
@@ -72,54 +67,18 @@
|
|
|
72
67
|
};
|
|
73
68
|
|
|
74
69
|
var $ = function (id) { return document.getElementById(id); };
|
|
75
|
-
var media = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
|
|
76
70
|
var data = { docket: null, goals: [], rollups: [], loaded: false, raw: {} };
|
|
77
71
|
var model = null;
|
|
78
72
|
var openKey = null;
|
|
79
73
|
var drewLiving = false;
|
|
80
74
|
|
|
81
|
-
// ----------
|
|
82
|
-
//
|
|
83
|
-
// holds for this page view.
|
|
84
|
-
var chosen = null;
|
|
85
|
-
function storedTheme() {
|
|
86
|
-
try { var v = window.localStorage.getItem(THEME_KEY); if (v === 'dark' || v === 'light') return v; } catch (_) { /* blocked */ }
|
|
87
|
-
return chosen;
|
|
88
|
-
}
|
|
89
|
-
function effectiveTheme() { return storedTheme() || (media && media.matches ? 'dark' : 'light'); }
|
|
90
|
-
function applyTheme() {
|
|
91
|
-
var t = effectiveTheme();
|
|
92
|
-
document.documentElement.setAttribute('data-theme', t);
|
|
93
|
-
var btn = $('cityMode');
|
|
94
|
-
var label = t === 'dark' ? 'Switch to light mode' : 'Switch to dark mode';
|
|
95
|
-
if (btn) { btn.setAttribute('aria-label', label); btn.setAttribute('title', label); }
|
|
96
|
-
var icon = $('cityModeIcon');
|
|
97
|
-
if (icon) icon.setAttribute('d', t === 'dark' ? SUN : MOON);
|
|
98
|
-
return t;
|
|
99
|
-
}
|
|
100
|
-
function toggleTheme() {
|
|
101
|
-
var next = effectiveTheme() === 'dark' ? 'light' : 'dark';
|
|
102
|
-
chosen = next;
|
|
103
|
-
try { window.localStorage.setItem(THEME_KEY, next); } catch (_) { /* private mode: this page only */ }
|
|
104
|
-
draw(applyTheme());
|
|
105
|
-
syncShell();
|
|
106
|
-
}
|
|
107
|
-
// IN THE HALL (task 1004544) the shell's top bar carries the hall's own theme
|
|
108
|
-
// switch beside this one. Both write the same 'hall-theme' preference and the
|
|
109
|
-
// same html[data-theme]; each repaints when the other flips it, so the two
|
|
110
|
-
// icons and the drawing's faces never disagree.
|
|
75
|
+
// ---------- the mode: dark only (task 1005192, ADR 0402) ----------
|
|
76
|
+
// The drawing's faces come from the mode, and the hall has one.
|
|
111
77
|
var painted = null;
|
|
112
|
-
function
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
function watchShellTheme() {
|
|
118
|
-
if (typeof MutationObserver !== 'function') return;
|
|
119
|
-
new MutationObserver(function () {
|
|
120
|
-
var t = document.documentElement.getAttribute('data-theme');
|
|
121
|
-
if ((t === 'dark' || t === 'light') && t !== painted) { painted = t; chosen = t; draw(applyTheme()); }
|
|
122
|
-
}).observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
|
|
78
|
+
function effectiveTheme() { return 'dark'; }
|
|
79
|
+
function applyTheme() {
|
|
80
|
+
document.documentElement.setAttribute('data-theme', 'dark');
|
|
81
|
+
return 'dark';
|
|
123
82
|
}
|
|
124
83
|
|
|
125
84
|
// ---------- fit the 1440 x 900 artboard to the window ----------
|
|
@@ -747,9 +706,6 @@
|
|
|
747
706
|
if (start) start.setAttribute('href', '#decide');
|
|
748
707
|
if (start) start.addEventListener('click', function (e) { if (window.GovCityDecide && window.GovDeck) { if (e.preventDefault) e.preventDefault(); openDecide(null); } });
|
|
749
708
|
window.addEventListener('hashchange', function () { var h = window.location && window.location.hash; if (/^#decide/.test(h || '')) openDecide(h.split('=')[1] || null); });
|
|
750
|
-
var btn = $('cityMode');
|
|
751
|
-
if (btn) btn.addEventListener('click', toggleTheme);
|
|
752
|
-
if (media && media.addEventListener) media.addEventListener('change', function () { if (!storedTheme()) draw(applyTheme()); });
|
|
753
709
|
window.addEventListener('resize', fit);
|
|
754
710
|
var card = $('cityCard');
|
|
755
711
|
// the card's keys work wherever focus is while it is open (Tab never walks its buttons)
|
|
@@ -771,11 +727,10 @@
|
|
|
771
727
|
fit();
|
|
772
728
|
painted = applyTheme();
|
|
773
729
|
draw(painted);
|
|
774
|
-
watchShellTheme();
|
|
775
730
|
return load().then(function (r) { var h = window.location && window.location.hash; if (/^#decide/.test(h || '')) openDecide(h.split('=')[1] || null); return r; });
|
|
776
731
|
}
|
|
777
732
|
|
|
778
|
-
window.GovCityPage = { boot: boot, draw: draw, load: load, fit: fit, openCard: openCard, closeCard: closeCard, openDecide: function (scope, how) { openDecide(scope, how); return decide; }, FEEDS: FEEDS, LIVING_FEEDS: LIVING_FEEDS
|
|
733
|
+
window.GovCityPage = { boot: boot, draw: draw, load: load, fit: fit, openCard: openCard, closeCard: closeCard, openDecide: function (scope, how) { openDecide(scope, how); return decide; }, FEEDS: FEEDS, LIVING_FEEDS: LIVING_FEEDS };
|
|
779
734
|
window.GovCityPage.ready = boot();
|
|
780
735
|
// task 1005148: the Governor City as the visor's scene. No camera until R13, so no movement
|
|
781
736
|
// actions (ADR 0399 §2.2); the card's own keys are listed for the sheet.
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_": "THE GOVERNOR CITY's home (task 1004524, goal 1000125) through modules/ui-design/kit/render.js. The page draws GET /government/docket, GET /goals and GET /goals/rollups; against the kit's own stub none exists, so every state renders the city with no districts and no pins and the line saying the docket could not be read: a real state the floors must hold in. To look at the populated city, run it against the hall-preview harness, whose government__docket.json fixture carries the reference design's seven sample matters (3 need you, 2 moving, 2 settled) and whose goals fixtures carry the open goals:\n\n node scripts/hall-preview/server.js --port 4624 --fixture-me\n node modules/ui-design/kit/render.js --states modules/hall-ui/public/city.states.json --base-in http://localhost:4624 --base-out http://localhost:4624\n\nThe
|
|
2
|
+
"_": "THE GOVERNOR CITY's home (task 1004524, goal 1000125) through modules/ui-design/kit/render.js. The page draws GET /government/docket, GET /goals and GET /goals/rollups; against the kit's own stub none exists, so every state renders the city with no districts and no pins and the line saying the docket could not be read: a real state the floors must hold in. To look at the populated city, run it against the hall-preview harness, whose government__docket.json fixture carries the reference design's seven sample matters (3 need you, 2 moving, 2 settled) and whose goals fixtures carry the open goals:\n\n node scripts/hall-preview/server.js --port 4624 --fixture-me\n node modules/ui-design/kit/render.js --states modules/hall-ui/public/city.states.json --base-in http://localhost:4624 --base-out http://localhost:4624\n\nThe hall is dark only (task 1005192, ADR 0402): the page has no light/dark switch, so this file names the one mode.",
|
|
3
3
|
"page": "/builders/city",
|
|
4
|
+
"modes": ["dark"],
|
|
4
5
|
"_storage": "The hall asks a first-visit question (the session mode picker, craft-mode.js) over every page; seeding \"already asked\" renders the page a returning builder sees (task 1005080).",
|
|
5
|
-
"storage": {
|
|
6
|
+
"storage": {
|
|
7
|
+
"session": {
|
|
8
|
+
"hall-craft-asked": "1"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
6
11
|
"surface": "hall-ui",
|
|
7
12
|
"stub": {
|
|
8
13
|
"prefix": "/builders/"
|
|
@@ -38,7 +43,6 @@
|
|
|
38
43
|
"#cityStage",
|
|
39
44
|
"#cityStart",
|
|
40
45
|
"#cityLine",
|
|
41
|
-
"#cityMode",
|
|
42
46
|
"#app-topbar",
|
|
43
47
|
"#fullscreen-toggle"
|
|
44
48
|
],
|
|
@@ -75,7 +79,6 @@
|
|
|
75
79
|
"visible": [
|
|
76
80
|
"#cityStage",
|
|
77
81
|
"#cityStart",
|
|
78
|
-
"#cityMode",
|
|
79
82
|
"#fullscreen-toggle"
|
|
80
83
|
],
|
|
81
84
|
"hidden": [
|
|
@@ -91,34 +94,6 @@
|
|
|
91
94
|
]
|
|
92
95
|
}
|
|
93
96
|
},
|
|
94
|
-
"switched": {
|
|
95
|
-
"auth": true,
|
|
96
|
-
"_": "The sun/moon switch pressed once: the other mode, the city redrawn in it.",
|
|
97
|
-
"actions": [
|
|
98
|
-
[
|
|
99
|
-
"wait",
|
|
100
|
-
600
|
|
101
|
-
],
|
|
102
|
-
[
|
|
103
|
-
"clickIfVisible",
|
|
104
|
-
"[data-craft-close]"
|
|
105
|
-
],
|
|
106
|
-
[
|
|
107
|
-
"click",
|
|
108
|
-
"#cityMode"
|
|
109
|
-
],
|
|
110
|
-
[
|
|
111
|
-
"wait",
|
|
112
|
-
400
|
|
113
|
-
]
|
|
114
|
-
],
|
|
115
|
-
"expect": {
|
|
116
|
-
"visible": [
|
|
117
|
-
"#cityStage",
|
|
118
|
-
"#cityStart"
|
|
119
|
-
]
|
|
120
|
-
}
|
|
121
|
-
},
|
|
122
97
|
"focus": {
|
|
123
98
|
"auth": true,
|
|
124
99
|
"_": "Keyboard: the start button focused, its ring drawn.",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
|
|
9
9
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
10
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
-
<link rel="stylesheet" href="/builders/style.css?v=2026-11-
|
|
11
|
+
<link rel="stylesheet" href="/builders/style.css?v=2026-11-11-view2">
|
|
12
12
|
<link rel="stylesheet" href="/builders/oversight.css">
|
|
13
13
|
<link rel="stylesheet" href="/builders/collab.css">
|
|
14
14
|
</head>
|
|
@@ -160,10 +160,10 @@
|
|
|
160
160
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
161
161
|
|
|
162
162
|
<script src="/builders/dom-utils.js?v=2026-11-05-numbers"></script>
|
|
163
|
-
<script src="/builders/shell-game.js?v=2026-11-
|
|
163
|
+
<script src="/builders/shell-game.js?v=2026-11-11-view2"></script>
|
|
164
164
|
<script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
|
|
165
165
|
<script src="/builders/craft-mode.js?v=2026-11-10-visor"></script>
|
|
166
|
-
<script src="/builders/shell.js?v=2026-11-
|
|
166
|
+
<script src="/builders/shell.js?v=2026-11-11-view2"></script>
|
|
167
167
|
<script src="/builders/hall-dialog.js"></script>
|
|
168
168
|
<script src="/builders/palette.js" defer></script>
|
|
169
169
|
<script src="/builders/collab-lib.js?v=2026-09-24-archive"></script>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_": "Collab — the two-sided ask board. ASKED OF YOU comes first because it is the half where someone is waiting, and the empty state of that half is the page's most common render. Written with the re-skin (task 1003307) so this page is MEASURED at three widths in both modes rather than looked at once; the redesign (task 1003471) put four asks and two of the viewer's own in the harness so the rows can be looked at.",
|
|
3
3
|
"page": "/builders/collab",
|
|
4
|
+
"modes": ["dark"],
|
|
4
5
|
"_storage": "The hall asks a first-visit question (the session mode picker, craft-mode.js) over every page; seeding \"already asked\" renders the page a returning builder sees (task 1005080).",
|
|
5
6
|
"storage": { "session": { "hall-craft-asked": "1" } },
|
|
6
7
|
"surface": "hall-ui",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
|
|
9
9
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
10
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
-
<link rel="stylesheet" href="/builders/style.css?v=2026-11-
|
|
11
|
+
<link rel="stylesheet" href="/builders/style.css?v=2026-11-11-view2">
|
|
12
12
|
<link rel="stylesheet" href="/builders/commands.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body data-page="commands" data-page-title="Commands">
|
|
@@ -52,10 +52,10 @@
|
|
|
52
52
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
53
53
|
|
|
54
54
|
<script src="/builders/dom-utils.js?v=2026-11-05-numbers"></script>
|
|
55
|
-
<script src="/builders/shell-game.js?v=2026-11-
|
|
55
|
+
<script src="/builders/shell-game.js?v=2026-11-11-view2"></script>
|
|
56
56
|
<script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
|
|
57
57
|
<script src="/builders/craft-mode.js?v=2026-11-10-visor"></script>
|
|
58
|
-
<script src="/builders/shell.js?v=2026-11-
|
|
58
|
+
<script src="/builders/shell.js?v=2026-11-11-view2"></script>
|
|
59
59
|
<script src="/builders/hall-dialog.js"></script>
|
|
60
60
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
61
61
|
<script src="/builders/palette.js" defer></script>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_": "The Commands page (task 1004473): every skill and every bongos CLI verb as two grouped ledgers, filled from GET /specialities/installed-skills and GET /cli/commands. Against the kit's stub both reads 404, so the page renders its 'not available' state, which the floors must hold too. The populated look was verified against a real local server (bongos dev), since both lists are read from disk.",
|
|
3
3
|
"page": "/builders/commands",
|
|
4
|
+
"modes": ["dark"],
|
|
4
5
|
"_storage": "The hall asks a first-visit question (the session mode picker, craft-mode.js) over every page; seeding \"already asked\" renders the page a returning builder sees (task 1005080).",
|
|
5
6
|
"storage": { "session": { "hall-craft-asked": "1" } },
|
|
6
7
|
"surface": "hall-ui",
|