@bongos/core 1.24.37 → 1.24.39

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 (62) hide show
  1. package/.bongos-core.json +66 -66
  2. package/docs/copy-inventory.md +21 -21
  3. package/docs/copy-registry.json +23 -23
  4. package/docs/module-api-changelog.md +4 -0
  5. package/docs/page-readings.json +814 -1015
  6. package/modules/hall-ui/public/ai-studio.html +2 -2
  7. package/modules/hall-ui/public/atlas.html +2 -2
  8. package/modules/hall-ui/public/blockers.html +2 -2
  9. package/modules/hall-ui/public/board-room.html +2 -2
  10. package/modules/hall-ui/public/city.html +2 -2
  11. package/modules/hall-ui/public/collab.html +2 -2
  12. package/modules/hall-ui/public/commands.html +2 -2
  13. package/modules/hall-ui/public/copy-desk.html +2 -2
  14. package/modules/hall-ui/public/craft-mode.js +5 -3
  15. package/modules/hall-ui/public/deploy.html +2 -2
  16. package/modules/hall-ui/public/gate.html +2 -2
  17. package/modules/hall-ui/public/goal-map.html +2 -2
  18. package/modules/hall-ui/public/goals.html +2 -2
  19. package/modules/hall-ui/public/government.html +2 -2
  20. package/modules/hall-ui/public/idea.html +2 -2
  21. package/modules/hall-ui/public/ideas.html +2 -2
  22. package/modules/hall-ui/public/index.html +2 -2
  23. package/modules/hall-ui/public/market-watch-item.html +2 -2
  24. package/modules/hall-ui/public/market-watch-rules.html +2 -2
  25. package/modules/hall-ui/public/market-watch.html +2 -2
  26. package/modules/hall-ui/public/modules.html +2 -2
  27. package/modules/hall-ui/public/money.html +2 -2
  28. package/modules/hall-ui/public/my-agents.html +2 -2
  29. package/modules/hall-ui/public/primer.html +2 -2
  30. package/modules/hall-ui/public/profile.html +2 -2
  31. package/modules/hall-ui/public/project-settings.html +2 -2
  32. package/modules/hall-ui/public/roadmap.html +2 -2
  33. package/modules/hall-ui/public/roster.html +2 -2
  34. package/modules/hall-ui/public/sessions.html +2 -2
  35. package/modules/hall-ui/public/settings.html +2 -2
  36. package/modules/hall-ui/public/shell.js +106 -89
  37. package/modules/hall-ui/public/street-desk.js +1 -1
  38. package/modules/hall-ui/public/street-draw.js +16 -5
  39. package/modules/hall-ui/public/street-inside.js +16 -0
  40. package/modules/hall-ui/public/street.css +36 -0
  41. package/modules/hall-ui/public/street.html +2 -2
  42. package/modules/hall-ui/public/street.js +29 -16
  43. package/modules/hall-ui/public/studio.html +2 -2
  44. package/modules/hall-ui/public/task.html +2 -2
  45. package/modules/hall-ui/public/thinking.html +2 -2
  46. package/modules/hall-ui/public/tweak-editor.html +2 -2
  47. package/modules/hall-ui/public/watch.html +2 -2
  48. package/modules/hall-ui/public/work.html +2 -2
  49. package/modules/hall-ui/records/governor-street.md +12 -0
  50. package/package-lock.json +2 -2
  51. package/package.json +1 -1
  52. package/release-notes.json +12 -0
  53. package/src/module-api.js +1 -1
  54. package/tests/craft_mode_nav.mjs +12 -10
  55. package/tests/goal_map_page.mjs +5 -2
  56. package/tests/hall_matter_card.mjs +14 -7
  57. package/tests/hall_nav.mjs +20 -13
  58. package/tests/hall_street.mjs +38 -32
  59. package/tests/hall_street_depth.mjs +8 -6
  60. package/tests/hall_street_desk.mjs +8 -5
  61. package/tests/hall_street_inside.mjs +18 -0
  62. package/tests/nav_permission_atoms.mjs +1 -1
@@ -106,3 +106,15 @@ Owner decision 2026-10-04: the street should read at a glance without words, so
106
106
  - **The evening view is unchanged** ("nothing waits on you." and its line still show when nothing needs you).
107
107
 
108
108
  Tests: `tests/hall_street.mjs` asserts the model has no hint field, the module exports no `HINT`, and none of the street's four files carries the line; putting the markup or the constant back reds it.
109
+
110
+ ## The hall is for work: the street as a square, the building as a place to work (owner, 2026-11-04)
111
+
112
+ The owner's direction: the builders' hall is where you go to do things, not to look at them. No menu on menu, no pop-ups (go to the place), no page header or footer that a web app does not need. On the street that meant:
113
+
114
+ - **The street is the governance square.** The working areas are the city's own buildings, so no district stands on the street: `streetModel()` draws one only with `districts: true` (the kit's tests still hold that row to the prototype), and without it the civic row is centred on the artboard. A wide window scales the street to the row (`FOCUS_W`, 1120), not the whole 1440 artboard, so the buildings are as large as the height allows and the haze either side is cropped; a phone still scrolls it sideways and opens at the first building.
115
+ - **The header strip and the six readings are gone** (`.street-top`, `.street-signs` are not displayed; the markup stays, so the counts, the deck link and the readings still compute). The shell's top bar names the page and the buildings carry their own state lines.
116
+ - **A pin takes you into its building** (`#building=<key>`), where each matter is a desk row you act on. It no longer opens the short card; the card is reached only by a pin with no building, and retiring it is a follow-up.
117
+ - **Inside a building the drawing is the floor picker.** On a wide window (`.inside--work`) the interior is laid out, not scaled as one 1440 x 708 picture: the section is cropped to the building (`CROP` in `fitInside`, 590 x 580 of the 800 x 708 section), at 0.5 to 0.72 so a floor stays a 24 px target, in its own column; the chosen floor's desk takes the rest of the window and scrolls on its own. The building's name sits on one line, then the desk, then the government layer, who holds it and how it decides, then the record. A phone keeps its column with the same order. A district keeps its scaled board.
118
+ - **A desk row is one full line.** The board's 20 / 26 / 24 caps were sized for a desk in a corner of the interior; `LIMITS` is now name 90 (14 words), fact 60, facts 48.
119
+
120
+ Tests: `tests/hall_street.mjs` (nine doors and no district on the page, a pin goes inside and fills no card, the row scaled and centred, a phone opening at the first building), `tests/hall_street_inside.mjs` (the cropped picker, never under half size, the board not scaled, a district untouched), `tests/hall_street_depth.mjs` (with no plot to fly from every agent rides the road), `tests/hall_street_desk.mjs` (the new limits).
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.24.37",
3
+ "version": "1.24.39",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.24.37",
9
+ "version": "1.24.39",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.24.37",
3
+ "version": "1.24.39",
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",
@@ -11185,5 +11185,17 @@
11185
11185
  "id": "1005047",
11186
11186
  "text": "Owners now have one Money page in the Government group: what the project spent and paid out, its treasury, and what each hosted project costs, all in one place. Project settings and the Primer link to it instead of repeating t"
11187
11187
  }
11188
+ ],
11189
+ "1.24.38": [
11190
+ {
11191
+ "id": "1005071",
11192
+ "text": "The hall sidebar is now seven labelled sections (Work, Make, Community, You, Govern, Run the project, Help), and every craft can reach Help."
11193
+ }
11194
+ ],
11195
+ "1.24.39": [
11196
+ {
11197
+ "id": "1005077",
11198
+ "text": "The governor street is now a work-first square: the working areas left the street, the extra header and footer are gone, a pin takes you straight into its building, and inside a building the drawing is a compact floor picker s"
11199
+ }
11188
11200
  ]
11189
11201
  }
package/src/module-api.js CHANGED
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
81
81
  // MAJOR (see allowBoxScope below): passes the request through untouched.
82
82
  function deprecatedNoopMiddleware(_req, _res, next) { next(); }
83
83
 
84
- const CORE_VERSION = '1.24.37'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
84
+ const CORE_VERSION = '1.24.39'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
85
85
 
86
86
  // A namespaced logger so a module's log lines are attributable + consistent.
87
87
  // Usage: const log = api.logger('discord'); log.info('mounted');
@@ -25,7 +25,7 @@ const CRAFT = read('craft-mode.js');
25
25
 
26
26
  // Every NAV id, and the Government group's, read from shell.js itself.
27
27
  const NAV_IDS = [...SHELL.matchAll(/\{ id: '([a-z-]+)', label:/g)].map((m) => m[1]);
28
- const GOV_BLOCK = SHELL.slice(SHELL.indexOf("{ group: 'Government'"));
28
+ const GOV_BLOCK = SHELL.slice(SHELL.indexOf("{ group: 'Run the project'"));
29
29
  const GOV_IDS = [...GOV_BLOCK.slice(0, GOV_BLOCK.indexOf('] }')).matchAll(/\{ id: '([a-z-]+)'/g)].map((m) => m[1]);
30
30
 
31
31
  // A small element. Elements parsed from the sidebar's markup are built ONCE, so
@@ -132,8 +132,9 @@ test('THE ALWAYS-PRESENT LIST: one shared list, Builders first, then Collab and
132
132
  const { craft } = boot();
133
133
  // task 1004685 (the living-city prototype): My agents joins as a PROPOSAL for the
134
134
  // owner — every craft has agents, and round-3 board 6 pairs it with the city.
135
- assert.deepEqual([...craft.ALWAYS_TABS], ['roster', 'collab', 'profile', 'my-agents'],
136
- 'Builders (NAV id roster) heads the list (owner, 2026-10-01); Collab and Profile join it (owner, 2026-10-03); My agents (prototype, task 1004685)');
135
+ // task 1005071 (owner, 2026-11-04): Help — the old Docs group — joins, so every craft has the Primer.
136
+ assert.deepEqual([...craft.ALWAYS_TABS], ['roster', 'collab', 'profile', 'my-agents', 'primer', 'commands', 'atlas', 'modules', 'apidocs'],
137
+ 'Builders (NAV id roster) heads the list (owner, 2026-10-01); Collab and Profile join it (owner, 2026-10-03); My agents (prototype, task 1004685); Help (task 1005071)');
137
138
  for (const m of CRAFTS) {
138
139
  const tabs = craft.tabsFor(m);
139
140
  assert.ok(Array.isArray(tabs), `${m} has a named tab list`);
@@ -180,7 +181,7 @@ test('each mode shows its own tabs, and switching updates the nav live', () => {
180
181
  assert.equal(p.root.dataset.craft, m);
181
182
  assert.deepEqual(new Set(p.showing()), new Set(p.craft.tabsFor(m)), `${m} shows exactly its list`);
182
183
  for (const id of ['roster', 'collab', 'profile']) assert.ok(p.showing().includes(id), `${id} stays in ${m}`);
183
- assert.equal(p.headShowing('government'), m === 'governing', `the Government head shows only where its tabs do (${m})`);
184
+ assert.equal(p.headShowing('run-the-project'), m === 'governing', `the Government head shows only where its tabs do (${m})`);
184
185
  }
185
186
  p.craft.choose('building');
186
187
  assert.deepEqual(new Set(p.showing()), new Set(p.craft.tabsFor('building')), 'back to Building, its tabs return without a reload');
@@ -190,11 +191,12 @@ test('a mode never shows a tab the gate hid, and a later gate pass keeps the mod
190
191
  const p = boot({ session: { 'hall-craft': 'governing' } });
191
192
  p.signIn('thetes');
192
193
  assert.ok(!p.showing().includes('watch'), 'a thetes in Governing does not get the metic-floored Watch tab');
193
- assert.ok(!p.headShowing('government'), 'nor its group head');
194
- assert.deepEqual(p.showing(), ['blockers', 'board-room', 'city', 'profile', 'my-agents', 'collab', 'roster'], 'what a thetes board member in Governing sees');
194
+ assert.ok(!p.headShowing('run-the-project'), 'nor its group head');
195
+ // task 1005071: sidebar order — Work, Community, You, Govern, then Help (every craft).
196
+ assert.deepEqual(p.showing(), ['blockers', 'collab', 'roster', 'profile', 'my-agents', 'city', 'board-room', 'primer', 'commands', 'atlas', 'modules', 'apidocs'], 'what a thetes board member in Governing sees');
195
197
  p.signIn('archon');
196
198
  assert.ok(p.showing().includes('watch'), 'promoted, the gate lets it through and the mode keeps it');
197
- assert.ok(p.headShowing('government'), 'and its group head comes back with it, which only a mode pass after the gate can know');
199
+ assert.ok(p.headShowing('run-the-project'), 'and its group head comes back with it, which only a mode pass after the gate can know');
198
200
  assert.ok(!p.showing().includes('work'), 'and the gate pass did not bring back a tab the mode left out');
199
201
  });
200
202
 
@@ -257,9 +259,9 @@ function bootTree({ mode = null, activeId = null } = {}) {
257
259
  let into = group;
258
260
  if (label) {
259
261
  const head = el('BUTTON', 'nav-group__head');
260
- head.dataset.group = label.toLowerCase();
262
+ head.dataset.group = label.toLowerCase().replace(/\s+/g, '-');
261
263
  group.appendChild(head);
262
- into = group.appendChild(el('DIV', 'nav-items', 'nav-items-' + label.toLowerCase()));
264
+ into = group.appendChild(el('DIV', 'nav-items', 'nav-items-' + label.toLowerCase().replace(/\s+/g, '-')));
263
265
  }
264
266
  for (const m of block.matchAll(/\{ id: '([a-z-]+)', label:/g)) {
265
267
  into.appendChild(el('A', 'nav-item' + (m[1] === activeId ? ' nav-item--active' : ''), 'nav-' + m[1]));
@@ -306,7 +308,7 @@ test('GOVERNING LEADS WITH THE CITY: its other tabs fold into one dropdown right
306
308
  assert.equal(head.getAttribute('aria-controls'), 'nav-items-craft-fold', 'the head names the region it opens');
307
309
  assert.match(head.innerHTML, /More governing/);
308
310
  assert.ok(head.hasAttribute('data-craft-off') === false, 'its head shows while a tab inside does');
309
- assert.ok(p.sidebar.querySelectorAll('.nav-group__head').find((h) => h.dataset.group === 'government').hasAttribute('data-craft-off'),
311
+ assert.ok(p.sidebar.querySelectorAll('.nav-group__head').find((h) => h.dataset.group === 'run-the-project').hasAttribute('data-craft-off'),
310
312
  'the emptied Government head hides, so no label sits over nothing');
311
313
  p.craft.choose('building');
312
314
  assert.equal(p.fold(), null, 'leaving Governing removes the dropdown');
@@ -415,8 +415,11 @@ await test('Sky sits directly under Goals in the nav, member-gated, at the URL G
415
415
  const shell = read('shell.js');
416
416
  const item = shell.indexOf("{ id: 'goal-map', label: 'Sky', icon: 'sky', href: '/goal-map', gate: 'member' }");
417
417
  assert.ok(item > -1, 'the nav item, with the slot the map was denied (ADR 0227 §11)');
418
- const goals = shell.indexOf("{ id: 'goals', label: 'Goals'"); const work = shell.indexOf("{ id: 'work', label: 'Tasks'");
419
- assert.ok(goals > -1 && goals < item && item < work, 'under Goals, above Tasks');
418
+ // task 1005071 moved Tasks up to second in Work (owner, 2026-11-04); Sky stays the
419
+ // item straight after Goals.
420
+ const goals = shell.indexOf("{ id: 'goals', label: 'Goals'");
421
+ const between = shell.slice(goals, item);
422
+ assert.ok(goals > -1 && goals < item && (between.match(/\{ id: '/g) || []).length === 1, 'directly under Goals');
420
423
  assert.match(read('goals.html'), /href="\/builders\/goal-map"/, 'Goals still links to it');
421
424
  });
422
425
 
@@ -290,9 +290,16 @@ const BASE = (d) => ({
290
290
  '/api/bongos/government/constitution': { board: { membership: 'rank:metic+', pass_rule: 'unanimous', window_minutes: 1440, close_early_on_full_turnout: true } },
291
291
  });
292
292
 
293
- test('a pin opens the full card: the ballot is read, then the seats, counsel and acts are drawn as text', async () => {
293
+ // A pin goes into its building now (owner, 2026-11-04: no pop-ups); the card
294
+ // still opens for a pin with no building, so these tests open it directly.
295
+ const openCard = (p) => {
296
+ const pin = p.byId.get('streetPins').children[0];
297
+ p.window.GovStreetPage.openCard(pin.attrs['data-matter'], pin, pin.attrs['data-matters'].split(' '));
298
+ };
299
+
300
+ test('the full card: the ballot is read, then the seats, counsel and acts are drawn as text', async () => {
294
301
  const p = await bootPage({ feeds: BASE(docket([matter({ title: '<b>x</b>' })])) });
295
- p.byId.get('streetPins').children[0].fire('click');
302
+ openCard(p);
296
303
  await settle(); await settle();
297
304
  assert.ok(p.calls.some((c) => c.url === '/api/bongos/government/board/items'), 'the room is read when a sitting opens');
298
305
  const body = p.byId.get('streetCardBody');
@@ -308,7 +315,7 @@ test('vote for: one POST to the vote route, a toast, the docket re-read, and the
308
315
  const feeds = BASE((() => { const d0 = docket([matter()]); return () => (reads++ === 0 ? d0 : after); })());
309
316
  feeds['/api/bongos/government/docket'] = (() => { const d0 = docket([matter()]); return () => (reads++ === 0 ? d0 : after); })();
310
317
  const p = await bootPage({ feeds, posts: { 'POST /api/bongos/government/board/items/41/votes': { ok: true, vote: {}, already_voted: false, karma_delta: 1 } } });
311
- p.byId.get('streetPins').children[0].fire('click');
318
+ openCard(p);
312
319
  await settle(); await settle();
313
320
  byAct(p.byId.get('streetCardBody'), 'for').fire('click');
314
321
  await settle(); await settle(); await settle();
@@ -324,7 +331,7 @@ test('vote for: one POST to the vote route, a toast, the docket re-read, and the
324
331
 
325
332
  test('object: "no" opens the desk row\'s one-tap reasons, and a tap puts the reason and the part on the record', async () => {
326
333
  const p = await bootPage({ feeds: BASE(docket([matter()])), posts: { 'POST /api/bongos/government/board/items/41/votes': { ok: true, vote: {}, item_closed: true, outcome: 'returned' } } });
327
- p.byId.get('streetPins').children[0].fire('click');
334
+ openCard(p);
328
335
  await settle(); await settle();
329
336
  const body = () => p.byId.get('streetCardBody');
330
337
  assert.equal(byAct(body(), 'object').textContent, D.ACT_WORDS.object, 'the desk row\'s word');
@@ -341,7 +348,7 @@ test('object: "no" opens the desk row\'s one-tap reasons, and a tap puts the rea
341
348
 
342
349
  test('a refused act says why and records nothing; abstain sends nothing at all', async () => {
343
350
  const p = await bootPage({ feeds: BASE(docket([matter()])), posts: { 'POST /api/bongos/government/board/items/41/votes': { __status: 403, error: { code: 'not_board_member' } } } });
344
- p.byId.get('streetPins').children[0].fire('click');
351
+ openCard(p);
345
352
  await settle(); await settle();
346
353
  byAct(p.byId.get('streetCardBody'), 'for').fire('click');
347
354
  await settle(); await settle();
@@ -361,7 +368,7 @@ test('release: the override is approved, the card closes, Release rises, and wit
361
368
  feeds['/api/bongos/government/docket'] = () => (reads++ === 0 ? docket([ov]) : docket([]));
362
369
  const p = await bootPage({ feeds, posts: { 'POST /api/bongos/override-requests/9/decide': { ok: true } } });
363
370
  assert.equal(p.byId.get('streetEvening').hidden, true);
364
- p.byId.get('streetPins').children[0].fire('click');
371
+ openCard(p);
365
372
  byAct(p.byId.get('streetCardBody'), 'release').fire('click');
366
373
  await settle(); await settle(); await settle();
367
374
  assert.deepEqual(p.calls.filter((c) => c.method !== 'GET').map((c) => [c.method, c.url, c.body]), [['POST', '/api/bongos/override-requests/9/decide', { decision: 'approve' }]]);
@@ -476,7 +483,7 @@ test('more expands the same card in place: the full text, who and when, and the
476
483
  const { d } = SOURCE_FIXTURES();
477
484
  d.matters = d.matters.filter((m) => m.source === 'blockers');
478
485
  const p = await bootPage({ feeds: { ...BASE(d) } });
479
- p.byId.get('streetPins').children[0].fire('click');
486
+ openCard(p);
480
487
  await settle(); await settle();
481
488
  const body = () => p.byId.get('streetCardBody');
482
489
  const toggle = () => byAct(body(), 'more');
@@ -243,7 +243,10 @@ test('sidebar groups read by audience, in order', () => {
243
243
  // 'Archon' → 'Governance' in ADR 0157 (the group's pages opened to Metic, so a header
244
244
  // naming the top rank was wrong), then → 'Government' by ADR 0174: a header
245
245
  // reading "Archon" over items a Metic can open would misdescribe the ladder.
246
- assert.deepEqual(nav.map((g) => g.label), ['', 'You', 'Community', 'Docs', 'Government']);
246
+ // task 1005071 (owner decision 2026-11-04, from the sitemap audit): every group is
247
+ // labelled, and the old unlabeled project group split into Work, Make and Govern.
248
+ // Docs became Help; Government became Run the project.
249
+ assert.deepEqual(nav.map((g) => g.label), ['Work', 'Make', 'Community', 'You', 'Govern', 'Run the project', 'Help']);
247
250
  // task 1003113 (R02 of goal 1000074): 'copy-desk' joins the unlabeled group,
248
251
  // after Tasks. It belongs with the project's own work and not in Docs — it is a
249
252
  // QUEUE somebody acts on, not reference material — and not in You or Community,
@@ -285,12 +288,15 @@ test('sidebar groups read by audience, in order', () => {
285
288
  // task 1004939 (goal 1000137): 'market-watch' joins last — the project's scouts on its
286
289
  // market, so its subject is the project. Module-gated: it is dropped wherever the
287
290
  // default-off market-watch module is not enabled (this boot reports no module state).
288
- assert.deepEqual(nav[0].items.map((i) => i.id), ['home', 'roadmap', 'goals', 'goal-map', 'work', 'blockers', 'board-room', 'city', 'thinking', 'studio', 'market-watch'],
289
- 'the unlabeled group is the project itself (task 1002425 order)');
291
+ // task 1005071: the project group split three ways. Work is the project's state;
292
+ // Make is where things get made; Govern is the board's rooms.
293
+ assert.deepEqual(nav[0].items.map((i) => i.id), ['home', 'work', 'goals', 'goal-map', 'roadmap', 'blockers', 'thinking'],
294
+ 'Work = the project itself (task 1005071 order)');
295
+ assert.deepEqual(nav[1].items.map((i) => i.id), ['studio', 'market-watch'], 'Make = where things get made');
290
296
  // task 1004685 (the living-city prototype, round-3 board 6): 'my-agents' joins You —
291
297
  // how your own Claude works for you, the personal half of "the city steers
292
298
  // everyone, My agents steers yours". Its subject is the viewer's own agents.
293
- assert.deepEqual(nav[1].items.map((i) => i.id), ['standing', 'profile', 'my-agents'], 'You = your own numbers and your own agents');
299
+ assert.deepEqual(nav[3].items.map((i) => i.id), ['standing', 'profile', 'my-agents'], 'You = your own numbers and your own agents');
294
300
  // task 1003444: 'people' LEFT the group — the owner deleted the hall's
295
301
  // cross-project directory (ADR 0337 D5); its home is the hub, and /people
296
302
  // redirects to /roster. Builders is the hall's directory now.
@@ -305,19 +311,20 @@ test('sidebar groups read by audience, in order', () => {
305
311
  // 'leaderboard') and is now a real page at /roster labelled Builders. That is
306
312
  // what made this group read as one real page plus a link into the home page.
307
313
  assert.deepEqual(nav[2].items.map((i) => i.id), ['collab', 'roster'], 'Community = everyone else, in this project');
314
+ assert.deepEqual(nav[4].items.map((i) => i.id), ['city', 'board-room'], 'Govern = the board\'s rooms, the city first');
308
315
  assert.ok(!nav.flatMap((g) => g.items).some((i) => /^\/people\b/.test(i.href)), 'no nav item points at the deleted /people');
309
316
  // task 1004473: 'commands' — every skill and CLI verb, read live — is reference
310
317
  // material that reads the same for every builder, so it joins Docs.
311
318
  // task 1005046 (owner decision 2026-11-03, idea 1001311): ONE reading room. Ranks,
312
319
  // Economy and Diagrams explained what the Primer explains; they are its chapters
313
320
  // now, so Docs is the Primer and the four references that are not a document.
314
- assert.deepEqual(nav[3].items.map((i) => i.id),
321
+ assert.deepEqual(nav[6].items.map((i) => i.id),
315
322
  ['primer', 'commands', 'atlas', 'modules', 'apidocs'], 'Docs = reference material');
316
323
  // 'government' (the Permissions tab, BV1.R113) is the group's one archon-gated item.
317
324
  // 'project-settings' joins the Government group at the metic floor (task 1003280,
318
325
  // goal 1000072) — it sits before 'government', which is the one archon-only item.
319
326
  // 'board-room' LED this group from the ADR 0266 carve until task 1003744 moved
320
- // it to the project group — see the note on nav[0]. What is left is rank surface
327
+ // it to the project group — see the note on Govern (nav[4]). What is left is rank surface
321
328
  // throughout, which is what lets the group keep its metic head honestly.
322
329
  // 'deploy' (task 1003159, ADR 0293) is the SECOND archon item, beside 'government':
323
330
  // its gate is the `core.pin.move` atom, archon-floor and system:true. It sits with
@@ -328,7 +335,7 @@ test('sidebar groups read by audience, in order', () => {
328
335
  // 'market-watch-settings' (task 1004938) LEFT the group in task 1005045: the owner saw
329
336
  // "Market Watch" twice and had to guess which was which (owner decision 2026-11-03,
330
337
  // idea 1001310). The settings are an owner-only Settings tab on /market-watch now.
331
- assert.deepEqual(nav[4].items.map((i) => i.id), ['watch', 'gate', 'sessions', 'project-settings', 'money', 'deploy', 'government']);
338
+ assert.deepEqual(nav[5].items.map((i) => i.id), ['watch', 'gate', 'sessions', 'project-settings', 'money', 'deploy', 'government']);
332
339
  // task 1005045: and no label is left twice anywhere in the sidebar, so the next pair
333
340
  // fails here instead of in front of the owner.
334
341
  const labels = [...boot().sidebarHtml.matchAll(/<a class="nav-item" [^>]*>[\s\S]*?<span class="nav-item__label">([^<]*)<\/span>/g)].map((m) => m[1]);
@@ -355,7 +362,7 @@ test('Ranks, Economy and Diagrams are Primer chapters, not pages', () => {
355
362
  for (const f of ['ranks.html', 'drachmae.html', 'diagrams.html']) {
356
363
  assert.ok(!fs.existsSync(path.join(HALL, f)), `${f} left the hall`);
357
364
  }
358
- const primer = nav.find((g) => g.label === 'Docs').items.find((i) => i.id === 'primer');
365
+ const primer = nav.find((g) => g.label === 'Help').items.find((i) => i.id === 'primer');
359
366
  assert.equal(primer.href, '/primer', 'the Primer is the reading room the three became');
360
367
  });
361
368
 
@@ -564,11 +571,11 @@ test('collapsible groups carry a stable storage key (collapse persistence)', ()
564
571
  // hall-nav-collapsed storage key, so any builder who had collapsed the old "archon"
565
572
  // group gets the new one expanded once — cosmetic, and preferable to keeping a stale
566
573
  // slug that no longer matches the visible label.
567
- for (const g of ['you', 'community', 'docs', 'government']) {
574
+ // task 1005071: a label with spaces hyphenates, so the key stays a valid id.
575
+ for (const g of ['work', 'make', 'community', 'you', 'govern', 'run-the-project', 'help']) {
568
576
  assert.match(html, new RegExp(`data-group="${g}"`), `${g} is collapsible (hall-nav-collapsed key)`);
569
577
  }
570
- assert.ok(!/nav-group--collapsible[^>]*>(?!<button)/.test(html.split('<div class="nav-group')[1] || ''),
571
- 'the unlabeled project group stays always-expanded');
578
+ assert.ok(!/aria-controls="[^"]* /.test(html), 'no aria-controls id carries a space');
572
579
  });
573
580
 
574
581
  // ── a nav item can carry a COUNT badge (task 1003737) ───────────────────────
@@ -780,7 +787,7 @@ test('no glyph path data reads as an IP address to the release leak scan', () =>
780
787
  // block (task 1004713). These drive the real shell and the real gov-positions.js.
781
788
 
782
789
  const tick = () => new Promise((resolve) => setImmediate(resolve));
783
- const GOVERNING = ['board-room', 'city', 'watch', 'gate', 'sessions', 'project-settings', 'money', 'deploy', 'government'];
790
+ const GOVERNING = ['city', 'board-room', 'watch', 'gate', 'sessions', 'project-settings', 'money', 'deploy', 'government'];
784
791
  const lead = (building, title) => ({ kind: 'lead', seat: 'lead:' + building, building, title });
785
792
  const block = (held) => ({ held, title: held.length ? held[0].title : null, has_position: held.length > 0 });
786
793
 
@@ -799,7 +806,7 @@ test('POSITION: exactly the governing surfaces carry the flag — the City, the
799
806
  const html = boot().sidebarHtml;
800
807
  const flagged = [...html.matchAll(/id="nav-([^"]+)"[^>]*data-position="1"/g)].map((m) => m[1]);
801
808
  assert.deepEqual(flagged, GOVERNING);
802
- assert.match(html, /class="nav-group__head" data-gate="government" data-position="1" data-group="government"/,
809
+ assert.match(html, /class="nav-group__head" data-gate="government" data-position="1" data-group="run-the-project"/,
803
810
  'the Government head carries it too, so no label is left over an emptied group');
804
811
  const nav = parseNav(html);
805
812
  assert.ok(!nav.find((g) => g.label === 'You').items.some((i) => flagged.includes(i.id)), 'your own pages never need a seat');
@@ -72,7 +72,9 @@ function docket(matters = [], over = {}) {
72
72
  }
73
73
  const goal = (id, status = 'open', title = `Goal ${id}`) => ({ id: String(id), title, status });
74
74
  const rollup = (id, c) => ({ goal_id: String(id), done: 0, active: 0, ready: 0, blocked: 0, backlog: 0, cut: 0, ...c });
75
- const model = (over = {}) => S.streetModel({ docket: docket(), goals: [], rollups: [], mode: 'dark', now: NOW, ...over });
75
+ // The kit is held to the prototype's row, districts and all (`districts: true`); the
76
+ // page itself draws the civic row alone (owner, 2026-11-04), pinned further down.
77
+ const model = (over = {}) => S.streetModel({ docket: docket(), goals: [], rollups: [], mode: 'dark', now: NOW, districts: true, ...over });
76
78
 
77
79
  // ── the drawing is the prototype's ───────────────────────────────────────────
78
80
 
@@ -299,7 +301,7 @@ test('the collision pass places in tier order: when only one of two can show, th
299
301
  });
300
302
 
301
303
  test('no docket: no pins, the readings say so, and nothing is invented', () => {
302
- const m = S.streetModel({ docket: null, goals: [goal(1)], rollups: [], mode: 'dark', now: NOW });
304
+ const m = S.streetModel({ docket: null, goals: [goal(1)], rollups: [], mode: 'dark', now: NOW, districts: true });
303
305
  assert.equal(m.pins.length, 0);
304
306
  assert.equal(m.crates.length, 0);
305
307
  assert.ok(m.signs.every((s) => s.figure === '—' && !s.hot && s.line === S.FALLBACK_LINE));
@@ -617,32 +619,30 @@ test('the page reads the four feeds and draws the docket\'s pins, the readings a
617
619
  assert.equal(p.byId.get('hudMoving').textContent, '2');
618
620
  assert.equal(p.byId.get('hudShare').textContent, '$24');
619
621
  assert.equal(p.byId.get('hudDot').className, 'street-hud__dot street-hud__dot--hot');
620
- assert.equal(p.byId.get('streetDoors').children.length, 10, 'nine buildings and one district, each a door');
622
+ assert.equal(p.byId.get('streetDoors').children.length, 9, 'the nine civic buildings, each a door; no district stands on the street (owner, 2026-11-04)');
623
+ assert.ok(p.byId.get('streetDoors').children.every((a) => !/district=/.test(a.attrs.href)), 'no door to a working area');
621
624
  assert.equal(p.byId.get('streetHome').attrs.href, '/builders/city');
622
625
  });
623
626
 
624
- test('a pin opens its short card, Escape closes it and focus goes back to the pin', async () => {
625
- const p = await bootPage({ feeds: FEEDS([matter({ id: 'board:1', building: 'charter', title: '<img src=x onerror=alert(1)>', href: '/builders/board-room' })]) });
627
+ // Owner, 2026-11-04: no pop-ups, go to the place. A pin takes you into its
628
+ // building, where each matter is a desk row you act on; it opens no card.
629
+ test('a pin takes you into its building and opens no card', async () => {
630
+ const p = await bootPage({ feeds: FEEDS([matter({ id: 'board:1', building: 'charter', title: 'A vote', href: '/builders/board-room' })]) });
626
631
  const pin = p.byId.get('streetPins').children[0];
632
+ assert.equal(pin.attrs['data-building'], 'charter');
627
633
  pin.fire('click');
628
- const card = p.byId.get('streetCard');
629
- assert.equal(card.hidden, false);
630
- assert.equal(p.byId.get('streetCardTitle').textContent, '<img src=x onerror=alert(1)>', 'docket text lands as text');
631
- assert.equal(p.byId.get('streetCardOpen').attrs.href, undefined, 'the card links nowhere: it acts in place (task 1004566)');
632
- assert.equal(p.focused.el, p.byId.get('streetCardClose'));
633
- assert.equal(p.byId.get('streetPins').children[0].attrs['aria-expanded'], 'true');
634
- const e = { key: 'Escape', prevented: false, preventDefault() { this.prevented = true; } };
635
- p.key('keydown', e);
636
- assert.equal(card.hidden, true);
637
- assert.equal(e.prevented, true, 'the press is used, so nothing else answers it');
638
- assert.equal(p.focused.el.attrs['data-matter'], 'board:1');
639
- });
640
-
641
- test('the card pager: a pin with several matters opens the most important with "1 of N", and previous/next reach every matter there', async () => {
634
+ assert.equal(p.window.location.hash, 'building=charter', 'the address names the building, so its inside opens');
635
+ assert.equal(p.byId.get('streetCardTitle').textContent, '', 'no card is filled');
636
+ assert.notEqual(p.byId.get('streetPins').children[0].attrs['aria-expanded'], 'true', 'and the pin does not say one is open');
637
+ });
638
+
639
+ // The card itself still works for the code paths that open it (a pin with no
640
+ // building); its pager is held here by opening it directly.
641
+ test('the card pager: a card over several matters opens the most important with "1 of N", and previous/next reach every matter there', async () => {
642
642
  const ms = [matter({ id: 'blockers:1', state: 'moving', title: 'Third' }), matter({ id: 'blockers:2', state: 'needs', title: 'First' }), matter({ id: 'blockers:3', state: 'needs', title: 'Second' }), matter({ id: 'blockers:4', state: 'settled', title: 'Fourth' })];
643
643
  const p = await bootPage({ feeds: FEEDS(ms) });
644
644
  const pin = p.byId.get('streetPins').children.find((s) => s.attrs['data-building'] === 'planning');
645
- pin.fire('click');
645
+ p.window.GovStreetPage.openCard(pin.attrs['data-matter'], pin, pin.attrs['data-matters'].split(' '));
646
646
  const body = p.byId.get('streetCardBody');
647
647
  const pager = () => body.children[0];
648
648
  const btn = (dir) => pager().children.find((c) => c.attrs['data-act'] === 'page-' + dir);
@@ -664,7 +664,8 @@ test('the card pager: a pin with several matters opens the most important with "
664
664
  assert.equal(p.focused.el.attrs['data-building'], 'planning', 'Escape returns focus to the pin');
665
665
  // one matter: no pager
666
666
  const q = await bootPage({ feeds: FEEDS([matter({ id: 'board:1', building: 'charter' })]) });
667
- q.byId.get('streetPins').children[0].fire('click');
667
+ const one = q.byId.get('streetPins').children[0];
668
+ q.window.GovStreetPage.openCard(one.attrs['data-matter'], one, one.attrs['data-matters'].split(' '));
668
669
  assert.ok(!q.byId.get('streetCardBody').children.some((c) => c.className === 'mcard-pager'), 'one matter, no pager');
669
670
  });
670
671
 
@@ -737,17 +738,22 @@ test('arriving through a door marks that building, and a failed docket says so',
737
738
  assert.equal(q.byId.get('hudShare').textContent, '—');
738
739
  });
739
740
 
740
- // task 1004718: a wide window scales the street to its width, and the height
741
- // it does not need goes under the readings, never as empty sky over the street.
742
- test('a stage taller than the street gives its spare height to the foot, not the sky', async () => {
741
+ // Owner, 2026-11-04 (supersedes task 1004718's foot): the readings left the
742
+ // page, so a wide window scales the street to its civic row, not the whole
743
+ // artboard, crops the haze either side, and centres the street in its stage.
744
+ test('a wide window scales the street to its civic row and centres it; a phone scrolls it from the first building', async () => {
743
745
  const p = await bootPage({ feeds: FEEDS([]), width: 1440 });
744
- const win = p.byId.get('streetWindow'), sizer = p.byId.get('streetSizer');
745
- win.clientHeight = 900;
746
- p.key('resize', {});
747
- const h = parseFloat(sizer.style.height);
748
- assert.ok(h < 900, 'the street is width-bound here');
749
- assert.equal(win.style.flex, '0 0 ' + h + 'px', 'the window shrinks to the street, so the readings sit right under it');
750
- win.clientHeight = 300;
746
+ const win = p.byId.get('streetWindow'), sizer = p.byId.get('streetSizer'), board = p.byId.get('streetBoard');
747
+ win.clientWidth = 1376; win.clientHeight = 844;
751
748
  p.key('resize', {});
752
- assert.equal(win.style.flex, '', 'a height-bound street fills its window, nothing to give back');
749
+ const s = Number(board.style['--street-scale']);
750
+ assert.ok(s > 1376 / 1440, 'larger than the whole artboard would allow');
751
+ assert.equal(win.style.flex, '', 'the window keeps the stage, so the street sits centred in it');
752
+ assert.equal(win.style.overflowX, 'hidden', 'no sideways scroll on a wide window');
753
+ assert.equal(parseFloat(sizer.style.width), 1376, 'the sizer is the window');
754
+ assert.ok(parseFloat(board.style.left) < 0, 'the artboard is centred, its haze cropped either side');
755
+ const q = await bootPage({ feeds: FEEDS([]), width: 390 });
756
+ const qwin = q.byId.get('streetWindow');
757
+ assert.equal(qwin.style.overflowX, '', 'a phone scrolls the street sideways');
758
+ assert.ok(qwin.scrollLeft > 0, 'starting at the first building, not the empty haze before it');
753
759
  });
@@ -70,7 +70,7 @@ function feeds(over = {}) {
70
70
  function living(f) { return L.livingModel({ ...f, now: NOW }); }
71
71
  function street(over = {}, mode = 'dark') {
72
72
  const f = feeds(over);
73
- return S.streetModel({ docket: f.docket, goals: f.goals.goals, rollups: f.rollups.rollups, mode, now: NOW, depth: true, living: f.living === null ? null : living(f), roster: f.roster });
73
+ return S.streetModel({ docket: f.docket, goals: f.goals.goals, rollups: f.rollups.rollups, mode, now: NOW, depth: true, living: f.living === null ? null : living(f), roster: f.roster, districts: true });
74
74
  }
75
75
  const openGoals = () => fx('goals').goals.filter((g) => g.status === 'open');
76
76
 
@@ -338,7 +338,7 @@ test('an overlay handed in changes nothing; the Leads not read, no Lead, capsule
338
338
  const c = street({ leads: null }).depth;
339
339
  assert.deepEqual([c.plates.length, c.capsules.length, c.sites.length], [0, 0, 0], 'no Leads read: no plate, no capsule, no build-out going up');
340
340
  // without the living model at all the deep street still draws its real skyline and windows
341
- const none = S.streetModel({ docket: fx('government__docket'), goals: fx('goals').goals, rollups: fx('goals__rollups').rollups, mode: 'dark', now: NOW, depth: true, living: null, roster: null }).depth;
341
+ const none = S.streetModel({ docket: fx('government__docket'), goals: fx('goals').goals, rollups: fx('goals__rollups').rollups, mode: 'dark', now: NOW, depth: true, living: null, roster: null, districts: true }).depth;
342
342
  assert.deepEqual(none.clusters, a.clusters);
343
343
  assert.deepEqual(none.windows, street({ leads: null, docket: fx('government__docket') }).depth.windows, 'no living model and the hub as it is light the same, the docket\'s own');
344
344
  assert.equal(none.agents, 0);
@@ -361,7 +361,7 @@ test('street-depth.js: its model half is pure, and the two drawing files require
361
361
  const before = globalThis.GovStreetDepth;
362
362
  delete globalThis.GovStreetDepth;
363
363
  try {
364
- const flat = S.streetModel({ docket: fx('government__docket'), goals: openGoals(), rollups: fx('goals__rollups').rollups, mode: 'dark', now: NOW, depth: true });
364
+ const flat = S.streetModel({ docket: fx('government__docket'), goals: openGoals(), rollups: fx('goals__rollups').rollups, mode: 'dark', now: NOW, depth: true, districts: true });
365
365
  assert.equal(flat.depth, undefined);
366
366
  } finally { globalThis.GovStreetDepth = before; }
367
367
  });
@@ -500,9 +500,11 @@ test('the page draws the living street: the skyline, a pod per claim, the plates
500
500
  assert.equal(cls('streetDepthFar', 'street-depth-sil__face').length, 1);
501
501
  const claims = fx('tasks__claimable').active_claims.length;
502
502
  assert.equal(cls('streetDepthSky', 'street-depth-pod').length + cls('streetDepthFront', 'street-depth-pod').length, claims, 'one pod per agent');
503
- const pod = cls('streetDepthSky', 'street-depth-pod')[0];
504
- assert.match(pod.style.offsetPath, /^path\('M/);
505
- assert.match(pod.style.animationDelay, /^-[\d.]+s$/);
503
+ // The page stands no district (owner, 2026-11-04), so no claim flies a skylane
504
+ // from its goal's plot: every agent rides the road (the kit's skylanes are held
505
+ // with districts on, above).
506
+ assert.equal(cls('streetDepthSky', 'street-depth-pod').length, 0, 'no plot to fly from');
507
+ assert.equal(cls('streetDepthFront', 'street-depth-pod--road').length, claims, 'every agent on the road');
506
508
  assert.equal(cls('streetDepthFront', 'street-depth-plate').length, 9);
507
509
  assert.equal(all('streetDepthFront').concat(all('streetDepthSky')).filter((e) => 'data-proto' in e.attrs).length, 0, 'nothing is marked prototype');
508
510
  assert.equal(cls('streetDepthFront', 'street-depth-jib').length, 1, 'the live site\'s crane');
@@ -105,11 +105,14 @@ test('fit() is the board\'s fit(), with a word cap on top', () => {
105
105
  for (const n of [5, 12, 20]) for (const w of [1, 2, 3]) assert.ok(D.fit(LONG, n, w).length <= n, 'never longer than its cap');
106
106
  });
107
107
 
108
- test('the limits are the board\'s numbers: name 20, fact 26, facts 24 (three), status 44, record 30 (four), three tiles, ten pips', () => {
108
+ // A row's name and fact were the board's 20 / 26 / 24 while the desk was a
109
+ // corner of the interior; the desk takes the window's width now (owner,
110
+ // 2026-11-04), so a row is one full line: name 90 (14 words), fact 60, facts 48.
111
+ test('the limits: a row is one full line (name 90, fact 60, facts 48, three), status 44, record 30 (four), three tiles, ten pips', () => {
109
112
  const L = D.LIMITS;
110
- assert.deepEqual([L.name, L.nameWords, L.fact, L.factLen, L.facts, L.actions, L.status, L.recordLen, L.record, L.tiles, L.tileWords, L.todayTarget], [20, 3, 26, 24, 3, 2, 44, 30, 4, 3, 2, 10]);
113
+ assert.deepEqual([L.name, L.nameWords, L.fact, L.factLen, L.facts, L.actions, L.status, L.recordLen, L.record, L.tiles, L.tileWords, L.todayTarget], [90, 14, 60, 48, 3, 2, 44, 30, 4, 3, 2, 10]);
111
114
  const src = fs.readFileSync(path.join(ROOT, 'docs/design/mocks/governor-city/Desks.dc.html'), 'utf8');
112
- for (const pin of ['this.fit(r.t, 20)', 'this.fit(r.f, 26)', 'this.fit(t, 24)', '.slice(0, 3)', "this.fit('nothing needs you · ' + F.quiet, 44)", "this.fit(t + ' · now', 30)", '.slice(0, 4)', 'const target = 10']) assert.ok(src.includes(pin), `the board says ${pin}`);
115
+ for (const pin of ['.slice(0, 3)', "this.fit('nothing needs you · ' + F.quiet, 44)", "this.fit(t + ' · now', 30)", '.slice(0, 4)', 'const target = 10']) assert.ok(src.includes(pin), `the board says ${pin}`);
113
116
  });
114
117
 
115
118
  test('THE LINE RULE: no row, tile or status any docket produces breaks it, on every floor of every building', () => {
@@ -140,8 +143,8 @@ test('breaks() names every way a desk can break the rule (so the rule test can f
140
143
  const good = D.deskModel({ key: 'people', floor: 4, docket: dk, now: NOW });
141
144
  assert.deepEqual(D.breaks(good), []);
142
145
  const bad = j(good);
143
- bad.rows[0].name = 'Four words in here';
144
- bad.rows[0].fact = 'x'.repeat(27);
146
+ bad.rows[0].name = Array(16).fill('word').join(' ');
147
+ bad.rows[0].fact = 'x'.repeat(61);
145
148
  bad.rows[0].actions = [{ label: 'a' }, { label: 'b' }, { label: 'c' }];
146
149
  bad.rows[0].facts = ['a', 'b', 'c', 'd'];
147
150
  bad.tiles = [{ v: '0', l: 'need you' }, { v: '3', l: 'three whole words' }, { v: '4', l: 'ok' }, { v: '5', l: 'ok' }];
@@ -555,6 +555,24 @@ test('the markup carries the interior: one persistent lift, two flip blocks, the
555
555
  assert.match(html, /<button type="button" class="inside-textbtn" id="insideAmend"/);
556
556
  });
557
557
 
558
+ // Owner, 2026-11-04: a building is a place to work. On a wide window the
559
+ // drawing is a floor picker cropped to the building, at half size or more (a
560
+ // floor stays a 24 px target), and the desk has the rest of the window; the
561
+ // whole interior is no longer one scaled picture. A district keeps its board.
562
+ test('inside a building the drawing is a cropped floor picker, never under half size, and the board is not scaled', async () => {
563
+ const p = await bootPage({ hash: '#building=planning', feeds: FEEDS([]) });
564
+ const box = p.byId.get('insideArtBox'), art = p.byId.get('insideArt'), board = p.byId.get('insideBoard');
565
+ const k = Number(box.style['--inside-art-scale']);
566
+ assert.ok(k >= 0.5 && k <= 0.72, `picker scale ${k}`);
567
+ assert.equal(board.style['--inside-scale'], '1', 'the interior is laid out, not shrunk as one picture');
568
+ assert.equal(art.style['--inside-art-w'], Math.round(590 * k) + 'px', 'cropped to the building, not the 800 px section');
569
+ assert.equal(box.style['--inside-art-x'], Math.round(-50 * k) + 'px');
570
+ assert.ok(47 * k >= 24, 'a floor stays a 24 px target');
571
+ const d = await bootPage({ hash: '#district=1', feeds: FEEDS([]) });
572
+ assert.equal(d.byId.get('insideArtBox').style['--inside-art-scale'], '1', 'a district keeps its whole board');
573
+ assert.equal(d.byId.get('insideArtBox').style['--inside-art-x'], undefined, 'and is not cropped');
574
+ });
575
+
558
576
  test('a building address draws the interior in place of the street, from the docket', async () => {
559
577
  const ms = [matter({ id: 'access_requests:1', source: 'access_requests', building: 'people', state: 'needs', title: 'Admit Kai' })];
560
578
  const p = await bootPage({ hash: '#building=people', feeds: FEEDS(ms) });
@@ -96,7 +96,7 @@ test('the hall-preview fixture carries every atom /me resolves', () => {
96
96
  // Market Watch settings nav item and its fuel glyph; task 1005041: the Update pill replaced the
97
97
  // core-update banner; task 1005045: the Market Watch settings item left (one sidebar item);
98
98
  // task 1005046: Ranks, Economy and Diagrams left Docs (one reading room, the Primer).
99
- const SHELL_SHA = '662e036a340ad33f';
99
+ const SHELL_SHA = 'baf8a15f025eba68';
100
100
 
101
101
  test('shell.js has not changed without its cache-bust being bumped', () => {
102
102
  const actual = crypto.createHash('sha256').update(SHELL).digest('hex').slice(0, 16);