@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.
Files changed (56) hide show
  1. package/.bongos-core.json +60 -60
  2. package/docs/copy-inventory.md +29 -25
  3. package/docs/copy-registry.json +87 -30
  4. package/docs/module-api-changelog.md +2 -0
  5. package/docs/page-inventory.json +9 -3
  6. package/docs/page-readings.json +402 -390
  7. package/modules/hall-ui/public/atlas.html +1 -1
  8. package/modules/hall-ui/public/blockers.html +1 -1
  9. package/modules/hall-ui/public/board-room.html +1 -1
  10. package/modules/hall-ui/public/city.css +16 -2
  11. package/modules/hall-ui/public/city.html +29 -9
  12. package/modules/hall-ui/public/city.js +25 -2
  13. package/modules/hall-ui/public/city.states.json +7 -1
  14. package/modules/hall-ui/public/collab.html +1 -1
  15. package/modules/hall-ui/public/commands.html +1 -1
  16. package/modules/hall-ui/public/copy-desk.html +1 -1
  17. package/modules/hall-ui/public/deploy.html +1 -1
  18. package/modules/hall-ui/public/diagrams.html +1 -1
  19. package/modules/hall-ui/public/drachmae.html +1 -1
  20. package/modules/hall-ui/public/fleet.html +1 -1
  21. package/modules/hall-ui/public/gate.html +1 -1
  22. package/modules/hall-ui/public/goal-map.html +1 -1
  23. package/modules/hall-ui/public/goals.html +1 -1
  24. package/modules/hall-ui/public/government.html +1 -1
  25. package/modules/hall-ui/public/idea.html +1 -1
  26. package/modules/hall-ui/public/ideas.html +1 -1
  27. package/modules/hall-ui/public/index.html +1 -1
  28. package/modules/hall-ui/public/modules.html +1 -1
  29. package/modules/hall-ui/public/primer.html +1 -1
  30. package/modules/hall-ui/public/profile.html +1 -1
  31. package/modules/hall-ui/public/project-settings.html +1 -1
  32. package/modules/hall-ui/public/ranks.html +1 -1
  33. package/modules/hall-ui/public/roadmap.html +1 -1
  34. package/modules/hall-ui/public/roster.html +1 -1
  35. package/modules/hall-ui/public/sessions.html +1 -1
  36. package/modules/hall-ui/public/settings.html +1 -1
  37. package/modules/hall-ui/public/shell.js +3 -0
  38. package/modules/hall-ui/public/street.css +17 -4
  39. package/modules/hall-ui/public/street.html +26 -8
  40. package/modules/hall-ui/public/street.js +16 -2
  41. package/modules/hall-ui/public/street.states.json +17 -1
  42. package/modules/hall-ui/public/studio.html +1 -1
  43. package/modules/hall-ui/public/task.html +1 -1
  44. package/modules/hall-ui/public/thinking.html +1 -1
  45. package/modules/hall-ui/public/tweak-editor.html +1 -1
  46. package/modules/hall-ui/public/watch.html +1 -1
  47. package/modules/hall-ui/public/work.html +1 -1
  48. package/package-lock.json +2 -2
  49. package/package.json +1 -1
  50. package/release-notes.json +6 -0
  51. package/src/module-api.js +1 -1
  52. package/tests/hall_audit.mjs +7 -12
  53. package/tests/hall_city.mjs +44 -4
  54. package/tests/hall_street.mjs +37 -5
  55. package/tests/hall_tweak_editor.mjs +1 -1
  56. 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-01-look"></script>
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-01-look"></script>
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-01-look"></script>
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-01-look"></script>
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-01-look"></script>
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-01-look"></script>
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.20",
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.20",
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.20",
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",
@@ -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.20'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
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');
@@ -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. Whether it should join the chrome
379
- // behind the full-screen switch, as the sky did in task 1004327, is the
380
- // owner's call, named in that task's ship notes.
381
- // THE STREET (task 1004525) follows the city, for the same reason: the
382
- // reference design's street view shows nothing on the page but its own header
383
- // (the wordmark back to the city, needs you, moving, your share, the switch),
384
- // the street and the six readings. Same terms as the city: style.css and the
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 = [];
@@ -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 ctx = vm.createContext({ window, document, fetch, Promise, Math, String, Number, Array, Object, JSON, console });
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);
@@ -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 shared kit loads first, then the card (task 1004528), the interiors (task 1004526) and the drafted amendment (task 1004530)');
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 ctx = vm.createContext({ window, document, fetch, Promise, Math, String, Number, Array, Object, JSON, Date, console, encodeURIComponent, decodeURIComponent, isNaN });
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-01-look/);
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 = '51a13ea563542113';
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);