@bongos/core 1.21.20 → 1.21.21
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 +60 -60
- package/docs/copy-inventory.md +29 -25
- package/docs/copy-registry.json +87 -30
- package/docs/module-api-changelog.md +2 -0
- package/docs/page-inventory.json +9 -3
- package/docs/page-readings.json +402 -390
- package/modules/hall-ui/public/atlas.html +1 -1
- package/modules/hall-ui/public/blockers.html +1 -1
- package/modules/hall-ui/public/board-room.html +1 -1
- package/modules/hall-ui/public/city.css +16 -2
- package/modules/hall-ui/public/city.html +29 -9
- package/modules/hall-ui/public/city.js +25 -2
- package/modules/hall-ui/public/city.states.json +7 -1
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/commands.html +1 -1
- package/modules/hall-ui/public/copy-desk.html +1 -1
- package/modules/hall-ui/public/deploy.html +1 -1
- package/modules/hall-ui/public/diagrams.html +1 -1
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/fleet.html +1 -1
- package/modules/hall-ui/public/gate.html +1 -1
- package/modules/hall-ui/public/goal-map.html +1 -1
- package/modules/hall-ui/public/goals.html +1 -1
- package/modules/hall-ui/public/government.html +1 -1
- package/modules/hall-ui/public/idea.html +1 -1
- package/modules/hall-ui/public/ideas.html +1 -1
- package/modules/hall-ui/public/index.html +1 -1
- package/modules/hall-ui/public/modules.html +1 -1
- package/modules/hall-ui/public/primer.html +1 -1
- package/modules/hall-ui/public/profile.html +1 -1
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/ranks.html +1 -1
- package/modules/hall-ui/public/roadmap.html +1 -1
- package/modules/hall-ui/public/roster.html +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings.html +1 -1
- package/modules/hall-ui/public/shell.js +3 -0
- package/modules/hall-ui/public/street.css +17 -4
- package/modules/hall-ui/public/street.html +26 -8
- package/modules/hall-ui/public/street.js +16 -2
- package/modules/hall-ui/public/street.states.json +17 -1
- package/modules/hall-ui/public/studio.html +1 -1
- package/modules/hall-ui/public/task.html +1 -1
- package/modules/hall-ui/public/thinking.html +1 -1
- package/modules/hall-ui/public/tweak-editor.html +1 -1
- package/modules/hall-ui/public/watch.html +1 -1
- package/modules/hall-ui/public/work.html +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +6 -0
- package/src/module-api.js +1 -1
- package/tests/hall_audit.mjs +7 -12
- package/tests/hall_city.mjs +44 -4
- package/tests/hall_street.mjs +37 -5
- package/tests/hall_tweak_editor.mjs +1 -1
- package/tests/nav_permission_atoms.mjs +1 -1
|
@@ -176,7 +176,7 @@
|
|
|
176
176
|
|
|
177
177
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
178
178
|
|
|
179
|
-
<script src="/builders/shell.js?v=2026-10-
|
|
179
|
+
<script src="/builders/shell.js?v=2026-10-02-theme"></script>
|
|
180
180
|
<script src="/builders/palette.js" defer></script>
|
|
181
181
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
182
182
|
<!-- The kit, for its ONE renderer: the full-size ship-time visual figure
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
|
|
58
58
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
59
59
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
60
|
-
<script src="/builders/shell.js?v=2026-10-
|
|
60
|
+
<script src="/builders/shell.js?v=2026-10-02-theme"></script>
|
|
61
61
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
62
62
|
<script src="/builders/palette.js" defer></script>
|
|
63
63
|
<!-- shared frontend toolkit (window.OTB) — loaded FIRST so task.js can pull from it. -->
|
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
</div>
|
|
139
139
|
</div>
|
|
140
140
|
|
|
141
|
-
<script src="/builders/shell.js?v=2026-10-
|
|
141
|
+
<script src="/builders/shell.js?v=2026-10-02-theme"></script>
|
|
142
142
|
<script src="/builders/palette.js" defer></script>
|
|
143
143
|
|
|
144
144
|
<script src="/builders/idea-objects.js?v=2026-09-27-split"></script>
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
|
|
141
141
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
142
142
|
|
|
143
|
-
<script src="/builders/shell.js?v=2026-10-
|
|
143
|
+
<script src="/builders/shell.js?v=2026-10-02-theme"></script>
|
|
144
144
|
<script src="/builders/palette.js" defer></script>
|
|
145
145
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
146
146
|
<script src="/builders/brand-holes.js?v=2026-09-29-aqfix"></script>
|
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
|
|
163
163
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
164
164
|
chrome exists before the page script runs. -->
|
|
165
|
-
<script src="/builders/shell.js?v=2026-10-
|
|
165
|
+
<script src="/builders/shell.js?v=2026-10-02-theme"></script>
|
|
166
166
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
167
167
|
<script src="/builders/palette.js" defer></script>
|
|
168
168
|
<!-- shared frontend toolkit (window.OTB) — loaded before watch.js, which pulls from it. -->
|
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
|
|
172
172
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
173
173
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
174
|
-
<script src="/builders/shell.js?v=2026-10-
|
|
174
|
+
<script src="/builders/shell.js?v=2026-10-02-theme"></script>
|
|
175
175
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
176
176
|
<script src="/builders/palette.js" defer></script>
|
|
177
177
|
<!-- shared frontend toolkit (window.OTB) — loaded FIRST so work.js can pull from it. -->
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.21",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.21.
|
|
9
|
+
"version": "1.21.21",
|
|
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.21.
|
|
3
|
+
"version": "1.21.21",
|
|
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
|
@@ -8876,5 +8876,11 @@
|
|
|
8876
8876
|
"id": "1004527",
|
|
8877
8877
|
"text": "Inside every Governor City building, the floors now show real numbers instead of \"not recorded yet\". The Charter reads each section from the rules in force. People shows who sits on the council, the testers, the ranks and newc"
|
|
8878
8878
|
}
|
|
8879
|
+
],
|
|
8880
|
+
"1.21.21": [
|
|
8881
|
+
{
|
|
8882
|
+
"id": "1004544",
|
|
8883
|
+
"text": "The governor city and the street now open inside the normal hall, with the sidebar and top bar around them like every other page. A full-screen button in the corner hides both so the city or the street fills the window again,"
|
|
8884
|
+
}
|
|
8879
8885
|
]
|
|
8880
8886
|
}
|
package/src/module-api.js
CHANGED
|
@@ -75,7 +75,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
75
75
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
76
76
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
77
77
|
|
|
78
|
-
const CORE_VERSION = '1.21.
|
|
78
|
+
const CORE_VERSION = '1.21.21'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
79
79
|
|
|
80
80
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
81
81
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|
package/tests/hall_audit.mjs
CHANGED
|
@@ -375,18 +375,13 @@ test('the module doc still records where the 2179 density pass landed', () => {
|
|
|
375
375
|
// else is on the page but the wordmark, a sun/moon switch, start governing and
|
|
376
376
|
// one line. It still loads style.css (the hall's tokens, so no private palette:
|
|
377
377
|
// city.css spends var(--bg), var(--ink), var(--accent) …), and reads and writes
|
|
378
|
-
// the hall's own 'hall-theme' preference.
|
|
379
|
-
//
|
|
380
|
-
//
|
|
381
|
-
//
|
|
382
|
-
//
|
|
383
|
-
//
|
|
384
|
-
|
|
385
|
-
// hall's tokens, the hall's own 'hall-theme' preference, no private palette.
|
|
386
|
-
const STANDALONE_EXEMPT = {
|
|
387
|
-
'city.html': 'the governor city home: nothing on the page but the prototype home (task 1004524)',
|
|
388
|
-
'street.html': 'the governor street: nothing on the page but the prototype street view (task 1004525)',
|
|
389
|
-
};
|
|
378
|
+
// the hall's own 'hall-theme' preference. THE STREET (task 1004525) followed
|
|
379
|
+
// it for the same reason. Both left this list in task 1004544 (owner decision
|
|
380
|
+
// 2026-10-02): they sit in the hall chrome now, behind the shell's full-screen
|
|
381
|
+
// switch, exactly as the sky did in task 1004327, so the list is empty again.
|
|
382
|
+
// Neither has a sidebar link (the owner's call): the city is reached by URL and
|
|
383
|
+
// the street from the city's start governing.
|
|
384
|
+
const STANDALONE_EXEMPT = {};
|
|
390
385
|
|
|
391
386
|
test('every hall page loads style.css and shell.js', () => {
|
|
392
387
|
const missing = [];
|
package/tests/hall_city.mjs
CHANGED
|
@@ -241,10 +241,27 @@ test('the page holds only the wordmark, the switch, the city, one button and one
|
|
|
241
241
|
assert.equal((html.match(/<a\b/g) || []).length, 1, 'one door');
|
|
242
242
|
assert.equal((html.match(/<button\b/g) || []).length, 1, 'one switch');
|
|
243
243
|
assert.match(html, /class="city-mark">\{\{productName\}\}</);
|
|
244
|
-
assert.doesNotMatch(html, /app-sidebar|shell\.js/, 'no hall chrome on the city');
|
|
245
244
|
assert.equal(City.STREET_HREF, '/builders/street');
|
|
246
245
|
});
|
|
247
246
|
|
|
247
|
+
// task 1004544 (owner decision 2026-10-02): the city sits in the hall chrome
|
|
248
|
+
// behind the shell's full-screen switch, the sky's mechanism (task 1004327),
|
|
249
|
+
// and still has no sidebar link.
|
|
250
|
+
test('the city sits in the hall chrome, behind the shell\'s full-screen switch', () => {
|
|
251
|
+
const html = read('city.html').replace(/<!--[\s\S]*?-->/g, '');
|
|
252
|
+
assert.match(html, /<body data-page="city"[^>]*\sdata-fullscreen-switch>/, 'the bare attribute: the page lays itself out for full screen');
|
|
253
|
+
for (const id of ['app-sidebar', 'app-scrim', 'app-topbar']) assert.match(html, new RegExp(`id="${id}"`), id);
|
|
254
|
+
assert.match(html, /<main id="hall-main" class="city-main">\s*<div class="city-top">/, 'the stage is the one <main>, the strip inside it');
|
|
255
|
+
const scripts = [...html.matchAll(/<script src="\/builders\/([a-z-]+)\.js/g)].map((m) => m[1]);
|
|
256
|
+
assert.deepEqual(scripts, ['shell', 'palette', 'city-draw', 'city'], 'the shell first, as on every hall page');
|
|
257
|
+
const css = read('city.css').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
258
|
+
assert.match(css, /\.city-main \{ position: fixed; top: var\(--topbar-h, 56px\); left: var\(--sidebar-w-rail, 64px\);[^}]*contain: layout paint;/);
|
|
259
|
+
assert.match(css, /html\[data-fullscreen="on"\] \.city-main \{ top: 0; left: 0; \}/);
|
|
260
|
+
assert.match(css, /html\[data-fullscreen="on"\] \.city-top \{ right: 184px; \}/, 'the strip steps clear of the corner exit');
|
|
261
|
+
const shell = read('shell.js');
|
|
262
|
+
assert.doesNotMatch(shell, /href:\s*'(\/builders)?\/(city|street)'/, 'no sidebar link (owner: leave it off the sidebar)');
|
|
263
|
+
});
|
|
264
|
+
|
|
248
265
|
test('motion is CSS only and dies under reduced motion; no page :root; no innerHTML', () => {
|
|
249
266
|
const css = read('city.css').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
250
267
|
assert.doesNotMatch(css, /:root/);
|
|
@@ -293,7 +310,7 @@ function fakeDom() {
|
|
|
293
310
|
return { document, byId, html };
|
|
294
311
|
}
|
|
295
312
|
|
|
296
|
-
async function bootPage({ feeds = {}, stored = null, osDark = true } = {}) {
|
|
313
|
+
async function bootPage({ feeds = {}, stored = null, osDark = true, shell = undefined, stage = null } = {}) {
|
|
297
314
|
const { document, byId, html } = fakeDom();
|
|
298
315
|
const store = new Map(stored ? [['hall-theme', stored]] : []);
|
|
299
316
|
const calls = [];
|
|
@@ -308,14 +325,18 @@ async function bootPage({ feeds = {}, stored = null, osDark = true } = {}) {
|
|
|
308
325
|
const body = feeds[url];
|
|
309
326
|
return Promise.resolve(body === undefined ? { ok: false, json: async () => ({}) } : { ok: true, json: async () => body });
|
|
310
327
|
};
|
|
311
|
-
const
|
|
328
|
+
const observers = [];
|
|
329
|
+
class MutationObserver { constructor(fn) { this.fn = fn; observers.push(this); } observe() {} }
|
|
330
|
+
window.OTBShell = shell;
|
|
331
|
+
if (stage) { const m = document.createElement('main'); m.setAttribute('id', 'hall-main'); Object.assign(m, stage); }
|
|
332
|
+
const ctx = vm.createContext({ window, document, fetch, Promise, Math, String, Number, Array, Object, JSON, console, MutationObserver });
|
|
312
333
|
ctx.globalThis = ctx;
|
|
313
334
|
window.GovCity = undefined;
|
|
314
335
|
vm.runInContext(read('city-draw.js'), ctx);
|
|
315
336
|
window.GovCity = ctx.GovCity || ctx.window.GovCity;
|
|
316
337
|
vm.runInContext(read('city.js'), ctx);
|
|
317
338
|
await window.GovCityPage.ready;
|
|
318
|
-
return { window, byId, html, store, calls };
|
|
339
|
+
return { window, byId, html, store, calls, observers };
|
|
319
340
|
}
|
|
320
341
|
|
|
321
342
|
const FEEDS = (ms, goals = [goal(1)], rollups = [rollup(1, { done: 2, active: 1 })]) => ({
|
|
@@ -363,6 +384,25 @@ test('the switch reads and writes the hall\'s own theme preference', async () =>
|
|
|
363
384
|
assert.equal(q.html.attrs['data-theme'], 'dark', 'no stored choice follows the OS');
|
|
364
385
|
});
|
|
365
386
|
|
|
387
|
+
test('in the hall, the city and the top bar\'s theme switch never disagree (task 1004544)', async () => {
|
|
388
|
+
let shellPaints = 0;
|
|
389
|
+
const p = await bootPage({ feeds: FEEDS([]), stored: 'light', osDark: true, shell: { applyTheme() { shellPaints++; } } });
|
|
390
|
+
assert.equal(p.observers.length, 1, 'the page watches html[data-theme]');
|
|
391
|
+
p.byId.get('cityMode').fire('click');
|
|
392
|
+
assert.equal(shellPaints, 1, 'this switch repaints the shell\'s');
|
|
393
|
+
const before = JSON.stringify(p.byId.get('cityFlat').children[0].attrs);
|
|
394
|
+
p.store.set('hall-theme', 'light'); p.html.setAttribute('data-theme', 'light'); // the shell's switch
|
|
395
|
+
p.observers[0].fn();
|
|
396
|
+
assert.equal(p.byId.get('cityMode').attrs['aria-label'], 'Switch to dark mode', 'this switch follows the shell\'s');
|
|
397
|
+
assert.notEqual(JSON.stringify(p.byId.get('cityFlat').children[0].attrs), before, 'and the city redraws in that mode');
|
|
398
|
+
assert.equal(shellPaints, 1, 'following the shell does not echo back to it');
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test('the artboard fits the stage the hall chrome leaves, not the window (task 1004544)', async () => {
|
|
402
|
+
const p = await bootPage({ feeds: FEEDS([]), stage: { clientWidth: 688, clientHeight: 900 } });
|
|
403
|
+
assert.equal(p.byId.get('cityStage').style['--city-scale'], '0.688');
|
|
404
|
+
});
|
|
405
|
+
|
|
366
406
|
test('docket text lands as text: a title with markup is never parsed', async () => {
|
|
367
407
|
const p = await bootPage({ feeds: FEEDS([matter({ title: '<img src=x onerror=alert(1)>', short: '<b>x</b>' })]) });
|
|
368
408
|
assert.equal(p.byId.get('cityPins').children.length, 1);
|
package/tests/hall_street.mjs
CHANGED
|
@@ -375,12 +375,27 @@ test('the page holds the prototype\'s header, the street, the readings and the c
|
|
|
375
375
|
assert.equal((html.match(/<h1\b/g) || []).length, 1);
|
|
376
376
|
assert.match(html, /<a class="street-mark" id="streetHome" href="\/builders\/city"[^>]*>\{\{productName\}\}</);
|
|
377
377
|
for (const id of ['hudNeeds', 'hudMoving', 'hudShare', 'streetMode', 'streetSigns', 'streetPins', 'streetDoors', 'streetCard']) assert.match(html, new RegExp(`id="${id}"`), id);
|
|
378
|
-
assert.doesNotMatch(html, /app-sidebar|shell\.js/, 'no hall chrome, as the city');
|
|
379
378
|
const scripts = [...html.matchAll(/<script src="\/builders\/([a-z-]+)\.js/g)].map((m) => m[1]);
|
|
380
|
-
assert.deepEqual(scripts, ['city-draw', 'street-draw', 'street-card', 'street-inside', 'street-amend', 'street'], 'the
|
|
379
|
+
assert.deepEqual(scripts, ['shell', 'palette', 'city-draw', 'street-draw', 'street-card', 'street-inside', 'street-amend', 'street'], 'the hall shell (task 1004544), the shared kit, then the card (task 1004528), the interiors (task 1004526) and the drafted amendment (task 1004530)');
|
|
381
380
|
assert.equal(City.STREET_HREF, '/builders/street', 'the city\'s door is this page');
|
|
382
381
|
});
|
|
383
382
|
|
|
383
|
+
// task 1004544 (owner decision 2026-10-02): the street sits in the hall chrome
|
|
384
|
+
// behind the shell's full-screen switch, as the city and the sky (task 1004327),
|
|
385
|
+
// and still has no sidebar link.
|
|
386
|
+
test('the street sits in the hall chrome, behind the shell\'s full-screen switch', () => {
|
|
387
|
+
const html = read('street.html').replace(/<!--[\s\S]*?-->/g, '');
|
|
388
|
+
assert.match(html, /<body data-page="street"[^>]*\sdata-fullscreen-switch>/, 'the bare attribute: the page lays itself out for full screen');
|
|
389
|
+
for (const id of ['app-sidebar', 'app-scrim', 'app-topbar']) assert.match(html, new RegExp(`id="${id}"`), id);
|
|
390
|
+
assert.match(html, /<main id="hall-main" class="street-stage">\s*<div class="street-top">/, 'the stage is the one <main>, the strip inside it');
|
|
391
|
+
assert.match(html, /<div class="street-window" id="streetWindow">/, 'the street band is a <div> inside the stage');
|
|
392
|
+
const css = read('street.css').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
393
|
+
assert.match(css, /\.street-stage \{ position: fixed; top: var\(--topbar-h, 56px\); left: var\(--sidebar-w-rail, 64px\);[^}]*contain: layout paint;/);
|
|
394
|
+
assert.match(css, /html\[data-fullscreen="on"\] \.street-stage \{ top: 0; left: 0; \}/);
|
|
395
|
+
assert.match(css, /html\[data-fullscreen="on"\] \.street-top \{ padding-right: 184px; \}/, 'the strip steps clear of the corner exit');
|
|
396
|
+
assert.doesNotMatch(read('shell.js'), /href:\s*'(\/builders)?\/(city|street)'/, 'no sidebar link (owner: leave it off the sidebar)');
|
|
397
|
+
});
|
|
398
|
+
|
|
384
399
|
test('motion is CSS only and dies under reduced motion; no page :root; no innerHTML', () => {
|
|
385
400
|
const css = read('street.css').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
386
401
|
assert.doesNotMatch(css, /:root/);
|
|
@@ -428,7 +443,7 @@ function fakeDom() {
|
|
|
428
443
|
return { document, byId, html, focused };
|
|
429
444
|
}
|
|
430
445
|
|
|
431
|
-
async function bootPage({ feeds = {}, stored = null, osDark = true, hash = '' } = {}) {
|
|
446
|
+
async function bootPage({ feeds = {}, stored = null, osDark = true, hash = '', shell = undefined } = {}) {
|
|
432
447
|
const { document, byId, html, focused } = fakeDom();
|
|
433
448
|
const store = new Map(stored ? [['hall-theme', stored]] : []);
|
|
434
449
|
const calls = [];
|
|
@@ -444,7 +459,10 @@ async function bootPage({ feeds = {}, stored = null, osDark = true, hash = '' }
|
|
|
444
459
|
const body = feeds[url];
|
|
445
460
|
return Promise.resolve(body === undefined ? { ok: false, json: async () => ({}) } : { ok: true, json: async () => body });
|
|
446
461
|
};
|
|
447
|
-
const
|
|
462
|
+
const observers = [];
|
|
463
|
+
class MutationObserver { constructor(fn) { this.fn = fn; observers.push(this); } observe() {} }
|
|
464
|
+
window.OTBShell = shell;
|
|
465
|
+
const ctx = vm.createContext({ window, document, fetch, Promise, Math, String, Number, Array, Object, JSON, Date, console, encodeURIComponent, decodeURIComponent, isNaN, MutationObserver });
|
|
448
466
|
ctx.globalThis = ctx;
|
|
449
467
|
// each file hangs its api on `window`, as the browser loads them
|
|
450
468
|
vm.runInContext(read('city-draw.js'), ctx);
|
|
@@ -453,7 +471,7 @@ async function bootPage({ feeds = {}, stored = null, osDark = true, hash = '' }
|
|
|
453
471
|
vm.runInContext(read('street.js'), ctx);
|
|
454
472
|
await window.GovStreetPage.ready;
|
|
455
473
|
const key = (t, e) => (listeners[t] || []).forEach((fn) => fn(e));
|
|
456
|
-
return { window, byId, html, store, calls, key, focused };
|
|
474
|
+
return { window, byId, html, store, calls, key, focused, observers };
|
|
457
475
|
}
|
|
458
476
|
|
|
459
477
|
const FEEDS = (ms, goals = [goal(1)], rollups = [rollup(1, { done: 2, active: 1 })]) => ({
|
|
@@ -509,6 +527,20 @@ test('the switch reads and writes the hall\'s own theme preference and redraws t
|
|
|
509
527
|
assert.notEqual(JSON.stringify(p.byId.get('streetBlds').children[0].children[0].attrs), before, 'the street redraws in the new mode');
|
|
510
528
|
});
|
|
511
529
|
|
|
530
|
+
test('in the hall, the street and the top bar\'s theme switch never disagree (task 1004544)', async () => {
|
|
531
|
+
let shellPaints = 0;
|
|
532
|
+
const p = await bootPage({ feeds: FEEDS([]), stored: 'light', osDark: true, shell: { applyTheme() { shellPaints++; } } });
|
|
533
|
+
assert.equal(p.observers.length, 1, 'the page watches html[data-theme]');
|
|
534
|
+
p.byId.get('streetMode').fire('click');
|
|
535
|
+
assert.equal(shellPaints, 1, 'this switch repaints the shell\'s');
|
|
536
|
+
const before = JSON.stringify(p.byId.get('streetBlds').children[0].children[0].attrs);
|
|
537
|
+
p.store.set('hall-theme', 'light'); p.html.setAttribute('data-theme', 'light'); // the shell's switch
|
|
538
|
+
p.observers[0].fn();
|
|
539
|
+
assert.equal(p.byId.get('streetModeWord').textContent, 'dark', 'this switch follows the shell\'s');
|
|
540
|
+
assert.notEqual(JSON.stringify(p.byId.get('streetBlds').children[0].children[0].attrs), before, 'and the street redraws in that mode');
|
|
541
|
+
assert.equal(shellPaints, 1, 'following the shell does not echo back to it');
|
|
542
|
+
});
|
|
543
|
+
|
|
512
544
|
test('arriving through a door marks that building, and a failed docket says so', async () => {
|
|
513
545
|
const p = await bootPage({ feeds: FEEDS([]), hash: '#building=treasury' });
|
|
514
546
|
const here = p.byId.get('streetDoors').children.filter((a) => a.attrs['aria-current'] === 'location');
|
|
@@ -635,7 +635,7 @@ try {
|
|
|
635
635
|
assert.match(HTML, /<body data-page="tweak-editor"[^>]*data-fullscreen-switch/);
|
|
636
636
|
assert.equal((HTML.match(/<h1\b/g) || []).length, 1);
|
|
637
637
|
assert.ok(HTML.indexOf('tweak-editor-lib.js') < HTML.indexOf('tweak-editor.js?'), 'the lib loads first');
|
|
638
|
-
assert.match(HTML, /shell\.js\?v=2026-10-
|
|
638
|
+
assert.match(HTML, /shell\.js\?v=2026-10-02-theme/);
|
|
639
639
|
assert.equal(/fonts\.googleapis\.com\/css/.test(HTML), false);
|
|
640
640
|
for (const id of ['tw-bar', 'tw-counter', 'tw-open', 'tw-open-more', 'tw-divider', 'tw-openwrap', 'tw-open-menu', 'tw-open-tab', 'tw-open-split', 'tw-frame', 'tw-frame-view', 'tw-split-close', 'tw-download', 'tw-upload', 'tw-file', 'tw-submit', 'tw-paper', 'tw-doc', 'tw-notice', 'tw-done', 'tw-take', 'tw-pick', 'tw-picks', 'tw-refused', 'tw-refused-title', 'tw-refused-list', 'tw-refused-next', 'tw-refused-download', 'tw-refused-close']) {
|
|
641
641
|
assert.ok(new RegExp(`id="${id}"`).test(HTML), `#${id} is in the markup`);
|
|
@@ -87,7 +87,7 @@ test('the hall-preview fixture carries every atom /me resolves', () => {
|
|
|
87
87
|
// the file and left the query alone, so a returning browser kept the cached copy —
|
|
88
88
|
// a fix that reaches nobody looks identical to a fix that does not work.
|
|
89
89
|
|
|
90
|
-
const SHELL_SHA = '
|
|
90
|
+
const SHELL_SHA = 'a8dc2884930ca19a';
|
|
91
91
|
|
|
92
92
|
test('shell.js has not changed without its cache-bust being bumped', () => {
|
|
93
93
|
const actual = crypto.createHash('sha256').update(SHELL).digest('hex').slice(0, 16);
|