@bongos/core 1.21.20 → 1.21.22
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 +63 -63
- package/docs/adr/0361-merges-publish-as-candidates-release-decides-what-others-are-offered.md +1 -0
- package/docs/copy-inventory.md +29 -25
- package/docs/copy-registry.json +87 -30
- package/docs/module-api-changelog.md +4 -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 +12 -0
- package/scripts/gds/provision-repo.js +9 -2
- 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
- package/tests/provision.mjs +7 -2
|
@@ -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.22",
|
|
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.22",
|
|
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.22",
|
|
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,17 @@
|
|
|
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
|
+
}
|
|
8885
|
+
],
|
|
8886
|
+
"1.21.22": [
|
|
8887
|
+
{
|
|
8888
|
+
"id": "1004556",
|
|
8889
|
+
"text": "New projects are now always started on the latest released version of Cloud Bongos, even when the service that sets them up is running an older one. Until now, a lagging setup service passed its own old version to every new pr"
|
|
8890
|
+
}
|
|
8879
8891
|
]
|
|
8880
8892
|
}
|
|
@@ -515,8 +515,14 @@ function runningCoreVersion({ instanceRoot, coreV }) {
|
|
|
515
515
|
// publish as candidates, the platform hall runs ahead of the registry's `latest` label by
|
|
516
516
|
// design — and the ladder below vends whatever the hall runs, so every new project would
|
|
517
517
|
// be born on a version nobody released. When the hall is AHEAD of the release, the project
|
|
518
|
-
// gets the release instead. Equal
|
|
519
|
-
//
|
|
518
|
+
// gets the release instead. Equal → the running core, byte-exact, as before.
|
|
519
|
+
//
|
|
520
|
+
// BEHIND the release is real too (task 1004556): the runner that scaffolds is not always
|
|
521
|
+
// the platform hall. On cloudbongos.com it ran from a patch-channel install that could not
|
|
522
|
+
// cross the 1.21.0 minor, so it sat on 1.20.82 and every new project was born there,
|
|
523
|
+
// without founding mode (1.21+). The release is what ADR 0361 D5 promises a new project,
|
|
524
|
+
// and a released core has already run on the platform hall, so the runner's lag must not
|
|
525
|
+
// set the project's version: behind → the release, packed from the registry, too.
|
|
520
526
|
//
|
|
521
527
|
// No readable label → the running core, deliberately (fail-open). Until candidates are
|
|
522
528
|
// armed the running core IS the release; vendoring is the path that works with no
|
|
@@ -525,6 +531,7 @@ function runningCoreVersion({ instanceRoot, coreV }) {
|
|
|
525
531
|
function projectCoreDecision({ running, released }) {
|
|
526
532
|
if (!released || !isStable(released)) return { version: running, source: 'running', reason: 'no readable released (latest) label — using the core this hall runs' };
|
|
527
533
|
if (compareSemver(running, released) > 0) return { version: released, source: 'released', reason: `this hall runs ${running}, ahead of the release` };
|
|
534
|
+
if (compareSemver(running, released) < 0) return { version: released, source: 'released', reason: `this runner is on ${running}, behind the release` };
|
|
528
535
|
return { version: running, source: 'running', reason: `this hall runs the released core ${released}` };
|
|
529
536
|
}
|
|
530
537
|
|
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.22'; // 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);
|
package/tests/provision.mjs
CHANGED
|
@@ -1078,12 +1078,17 @@ await ta('scaffoldStandaloneRepo: a REGISTRY-pinned consumer control plane PACKS
|
|
|
1078
1078
|
// ADR 0361: merges publish as candidates and the platform hall runs them, so the core
|
|
1079
1079
|
// this hall runs is routinely AHEAD of the registry's `latest` label. A new project —
|
|
1080
1080
|
// and the pin refresh that runs in the same provisioning pass — must get the release.
|
|
1081
|
-
t('projectCoreDecision: the hall ahead of the release → the release; level or unreadable → the running core', () => {
|
|
1081
|
+
t('projectCoreDecision: the hall ahead of OR behind the release → the release; level or unreadable → the running core', () => {
|
|
1082
1082
|
assert.deepEqual(
|
|
1083
1083
|
(({ version, source }) => ({ version, source }))(RP.projectCoreDecision({ running: '1.19.60', released: '1.19.50' })),
|
|
1084
1084
|
{ version: '1.19.50', source: 'released' });
|
|
1085
1085
|
assert.equal(RP.projectCoreDecision({ running: '1.19.50', released: '1.19.50' }).source, 'running', 'level → byte-exact running core, no registry pack');
|
|
1086
|
-
|
|
1086
|
+
// task 1004556: a runner BEHIND the release (a patch-channel install stuck on 1.20.82
|
|
1087
|
+
// while 1.21.x was released) must not hand its stale core to a new project
|
|
1088
|
+
assert.deepEqual(
|
|
1089
|
+
(({ version, source }) => ({ version, source }))(RP.projectCoreDecision({ running: '1.20.82', released: '1.21.20' })),
|
|
1090
|
+
{ version: '1.21.20', source: 'released' }, 'behind the release → the release, across a minor too');
|
|
1091
|
+
assert.equal(RP.projectCoreDecision({ running: '1.19.40', released: '1.19.50' }).source, 'released', 'and within a minor');
|
|
1087
1092
|
assert.equal(RP.projectCoreDecision({ running: '1.19.60', released: null }).version, '1.19.60', 'no label → fail-open to the running core');
|
|
1088
1093
|
assert.equal(RP.projectCoreDecision({ running: '1.19.60', released: '1.19.50-rc.1' }).source, 'running', 'a prerelease label is not a release');
|
|
1089
1094
|
assert.equal(RP.projectCoreDecision({ running: '1.19.60', released: '1.19.50;touch /tmp/x' }).source, 'running', 'a non-semver label never reaches a shell');
|