@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
@@ -176,13 +176,27 @@
176
176
  // alongside it as sr-only text, because "3" alone in a link tells a screen
177
177
  // reader nothing about what is waiting.
178
178
  // task 2175: grouped by AUDIENCE of the data, so the sidebar reads in coherent
179
- // runs instead of one long list — the project (unlabeled), You, Community,
180
- // Docs, Government. A group's `gate` must match its most permissive item's, or the
179
+ // runs instead of one long list. Since task 1005071: Work, Make, Community, You,
180
+ // Govern, Run the project, Help. A group's `gate` must match its most permissive item's, or the
181
181
  // head renders over nothing.
182
182
  var NAV = [
183
- { group: '', items: [
183
+ // task 1005071 (goal 1000095; owner decision 2026-11-04, from the sitemap audit): the
184
+ // project group was eleven items under no heading, mixing the work, the artist's
185
+ // room, an add-on and two governing surfaces. It is labelled now and split by what
186
+ // you DO there: Work (the project's state, everyone), Make (where you build things),
187
+ // Govern (the board's rooms). 'always' because Home is.
188
+ { group: 'Work', gate: 'always', items: [
184
189
  { id: 'home', label: 'Home', icon: 'home', href: '/', view: 'home', gate: 'always' },
185
- { id: 'roadmap', label: 'Roadmap', icon: 'roadmap', href: '/roadmap', gate: 'member' },
190
+ // task 1966: Tasks (the board formerly labelled "Work") is visible to ANY
191
+ // signed-in builder (incl. newcomer Xenos). Claimability is controlled
192
+ // per-task, not by hiding the page — every row carries a rank tag and the
193
+ // server enforces requires_rank on the claim. This reverses task #833's
194
+ // newcomer hide (the 'work' gate below is now unused).
195
+ // task 1002425: relabelled Work → Tasks and moved below Roadmap/Goals. The
196
+ // id ('work') and href ('/work') are deliberately UNCHANGED so deep links,
197
+ // active-nav detection (#nav-work), the 'work' gate case, and the server
198
+ // page-gate all keep working — only the visible label and order change.
199
+ { id: 'work', label: 'Tasks', icon: 'work', href: '/work', gate: 'signed' },
186
200
  { id: 'goals', label: 'Goals', icon: 'goals', href: '/goals', gate: 'member' },
187
201
  // task 1004232 (sky part 4 of 5, goal 1000095; ADR 0227 §11, ADR 0331):
188
202
  // THE PROJECT SKY — the whole project drawn from the ledger, at the URL
@@ -195,16 +209,7 @@
195
209
  // because the href does: Goals' own "see which goals wait on each other"
196
210
  // link and every bookmark land here unchanged.
197
211
  { id: 'goal-map', label: 'Sky', icon: 'sky', href: '/goal-map', gate: 'member' },
198
- // task 1966: Tasks (the board formerly labelled "Work") is visible to ANY
199
- // signed-in builder (incl. newcomer Xenos). Claimability is controlled
200
- // per-task, not by hiding the page — every row carries a rank tag and the
201
- // server enforces requires_rank on the claim. This reverses task #833's
202
- // newcomer hide (the 'work' gate below is now unused).
203
- // task 1002425: relabelled Work → Tasks and moved below Roadmap/Goals. The
204
- // id ('work') and href ('/work') are deliberately UNCHANGED so deep links,
205
- // active-nav detection (#nav-work), the 'work' gate case, and the server
206
- // page-gate all keep working — only the visible label and order change.
207
- { id: 'work', label: 'Tasks', icon: 'work', href: '/work', gate: 'signed' },
212
+ { id: 'roadmap', label: 'Roadmap', icon: 'roadmap', href: '/roadmap', gate: 'member' },
208
213
  // task 1003455 (goal 1000089): the open-blocker queue — what's stuck on the
209
214
  // owner. Lives with the project's own state (Roadmap/Goals/Tasks), not You
210
215
  // or Community, because its subject is the project, not a person. Its one
@@ -214,40 +219,6 @@
214
219
  // fronts, showing the link to signed-in builders while a stranger who
215
220
  // already has the URL can still open it.
216
221
  { id: 'blockers', label: 'Blockers', icon: 'blockers', href: '/blockers', gate: 'signed' },
217
- // task 1003744 (goal 1000111): the Board Room lives HERE, in the project's
218
- // own group, and gates on the atom its page gates on.
219
- //
220
- // WHY IT LEFT GOVERNMENT. ADR 0266 carved the room out of /government, so
221
- // the item first landed in the Government group and read that group's metic
222
- // floor — the closest rank the shell could then express. Its real wall is
223
- // `board.vote.cast` (auth.js requireBoardVotePage), which floors at XENOS on
224
- // purpose so that WIDENING the board works (modules/government/board.js). The
225
- // moment an instance seats a sub-Metic member — the config change ADR 0175 §6
226
- // exists to make — that member could reach the room by URL and by the
227
- // board_votes need's deep link, with no sidebar entry.
228
- //
229
- // Fixing the gate alone would have put a wider item under a metic group head,
230
- // so a newcomer board member would see a group labelled "Government" holding
231
- // one link — more authority than they hold. The owner's call (2026-09-09) was
232
- // to move it instead: a sitting is work the project does, beside Goals and
233
- // Tasks, not a monitoring surface. The Government group's audience and floor
234
- // are unchanged.
235
- //
236
- // Cosmetic only, as every gate here is — the page gate is the wall (ADR 0016).
237
- // task 1003737: the badge's first consumer. The owner's ask was to see that
238
- // a sitting waits WITHOUT opening the room, and the count is already on
239
- // /me (boardVotesNeed), pre-filtered to what the vote route would accept —
240
- // so the badge counts ballots this viewer can actually cast, never "items
241
- // open somewhere".
242
- // task 1004685: `position: true` narrows the gate to seat holders (GOVERNING IS A POSITION).
243
- { id: 'board-room', label: 'Board Room', icon: 'boardroom', href: '/board-room', gate: 'perm:board.vote.cast', count: 'board_votes', position: true },
244
- // The governor city (task 1004524): the owner put it on the sidebar on
245
- // 2026-10-08, reversing "leave it off". It is the Governing mode's lead tab
246
- // (craft-mode.js folds that mode's other tabs into a dropdown under it).
247
- // Gated on the atom its page is (requireBoardVotePage), and it sits in this
248
- // head-less group for the reason the Board Room does: the atom floors below
249
- // any rank head. Cosmetic only (ADR 0016).
250
- { id: 'city', label: 'City', icon: 'city', href: '/city', gate: 'perm:board.vote.cast', position: true },
251
222
  // task 1004326 (BV2.TW15 of goal 1000095): the Ideas board (/ideas, task
252
223
  // 1002928) and the Copy desk (/copy-desk, task 1003113) are ARCHIVED — off
253
224
  // the nav on the owner's call of 2026-09-27, NOT deleted. Both pages and
@@ -269,6 +240,9 @@
269
240
  // thought, and the route reads the VIEWER's own trail, so there is
270
241
  // nothing here a rank could protect.
271
242
  { id: 'thinking', label: 'Your thinking', icon: 'thinking', href: '/thinking', gate: 'signed' },
243
+ ] },
244
+ // task 1005071: where things get made. Studio and the module-gated Market Watch.
245
+ { group: 'Make', gate: 'signed', items: [
272
246
  // task 1003117 (R06 of goal 1000074): the Studio — artist mode, "a place an
273
247
  // artist wants to be, not a form". Here and not in Docs (a place you ACT,
274
248
  // not reference) or You (its subject is the product, not a person).
@@ -279,21 +253,6 @@
279
253
  { id: 'studio', label: 'Studio', icon: 'studio', href: '/studio', gate: 'signed' },
280
254
  { id: 'market-watch', label: 'Market Watch', icon: 'marketwatch', href: '/market-watch', gate: 'signed', module: 'market-watch' }, // task 1004939: 'signed' is its page floor; dropped where the module is off
281
255
  ] },
282
- // Your own numbers. task 2174 folded Achievements into Standing and this task
283
- // retires its nav entry; VIEW_ALIASES still routes #achievements here, so old
284
- // bookmarks and the stat tiles keep landing on the section.
285
- { group: 'You', gate: 'signed', items: [
286
- { id: 'standing', label: 'Accomplishments', icon: 'accomplishments', href: '/#accomplishments', view: 'standing', gate: 'signed' },
287
- // task 1467: the viewable, shareable builder profile. The nav link points at
288
- // the viewer's OWN profile (profile.js defaults to self when no ?id=). gate is
289
- // cosmetic only — the /profile PAGE is public (a portfolio others can open via
290
- // /profile?id=N from the leaderboard); 'signed' just means "show this 'my
291
- // profile' shortcut once you're signed in".
292
- { id: 'profile', label: 'Profile', icon: 'profile', href: '/profile', gate: 'signed' },
293
- // task 1004685 (prototype, board 6): MY AGENTS — how your own Claude works for you
294
- // and which settings the project's laws lock. 'signed' is its page's real floor.
295
- { id: 'my-agents', label: 'My agents', icon: 'agents', href: '/my-agents', gate: 'signed' },
296
- ] },
297
256
  // task 1003442 (goal 1000075): Community is Collab + Builders, and NEITHER
298
257
  // carries a rank flag any more. Both items and this head read 'member'
299
258
  // (authed && rank !== 'xenos') until now, which hid them from the newcomer
@@ -329,33 +288,68 @@
329
288
  // call 2026-09-25, ADR 0337 D5). Cross-project discovery is the hub's, and
330
289
  // /people redirects to /roster.
331
290
  ] },
332
- { group: 'Docs', gate: 'signed', items: [
333
- // task 1002710: primer (and the old diagrams page) were gate:'member' (rank
334
- // !== xenos) to mirror the old thetes+ page gate. That gate is gone — the
335
- // reading room is for the first-session newcomer above all — so the link
336
- // follows it down to 'signed'. Leaving them at 'member' would have ungated the pages
337
- // while still hiding them from the only readers the change was for.
338
- { id: 'primer', label: 'Primer', icon: 'primer', href: '/primer', gate: 'signed' },
339
- // task 1005046 (owner decision 2026-11-03, idea 1001311): ONE reading room.
340
- // Ranks, Economy and Diagrams were three more pages explaining what the
341
- // Primer already explained, so the copies drifted. They are Primer
342
- // chapters now, each with its diagram beside it; /ranks, /drachmae and
343
- // /diagrams 302 to the matching chapter (serve-internal.js), and the
344
- // personal "Where you stand" card moved to Accomplishments.
345
- // task 1004473: every skill and every bongos CLI verb, read live. Its page and
346
- // both reads behind it are sign-in gated, so 'signed' is the real floor here.
347
- { id: 'commands', label: 'Commands', icon: 'commands', href: '/commands', gate: 'signed' },
348
- { id: 'atlas', label: 'Repo Atlas', icon: 'atlas', href: '/atlas', gate: 'member' },
349
- { id: 'modules', label: 'Modules', icon: 'modules', href: '/modules', gate: 'member' },
350
- // The rendered OpenAPI reference lives on the APEX (/docs), not this builders
351
- // subdomain — external so the rewrite shim doesn't turn it into /builders/docs.
352
- { id: 'apidocs', label: 'API docs', icon: 'apidocs', href: '/docs', gate: 'signed', external: true },
291
+ // Your own numbers. task 2174 folded Achievements into Standing and this task
292
+ // retires its nav entry; VIEW_ALIASES still routes #achievements here, so old
293
+ // bookmarks and the stat tiles keep landing on the section.
294
+ { group: 'You', gate: 'signed', items: [
295
+ { id: 'standing', label: 'Accomplishments', icon: 'accomplishments', href: '/#accomplishments', view: 'standing', gate: 'signed' },
296
+ // task 1467: the viewable, shareable builder profile. The nav link points at
297
+ // the viewer's OWN profile (profile.js defaults to self when no ?id=). gate is
298
+ // cosmetic only — the /profile PAGE is public (a portfolio others can open via
299
+ // /profile?id=N from the leaderboard); 'signed' just means "show this 'my
300
+ // profile' shortcut once you're signed in".
301
+ { id: 'profile', label: 'Profile', icon: 'profile', href: '/profile', gate: 'signed' },
302
+ // task 1004685 (prototype, board 6): MY AGENTS — how your own Claude works for you
303
+ // and which settings the project's laws lock. 'signed' is its page's real floor.
304
+ { id: 'my-agents', label: 'My agents', icon: 'agents', href: '/my-agents', gate: 'signed' },
305
+ ] },
306
+ // task 1005071: the board's own rooms get a head of their own. Gated on the SAME
307
+ // atom as both items (perm:, which floors below any rank), so the head can never
308
+ // be narrower than an item under it — the reason task 1003744 kept them out of a
309
+ // rank-gated group. The street stays off the sidebar (owner, kept 2026-11-05):
310
+ // the city is the way in, and it is Governing's Home.
311
+ { group: 'Govern', gate: 'perm:board.vote.cast', position: true, items: [
312
+ // The governor city (task 1004524): the owner put it on the sidebar on
313
+ // 2026-10-08, reversing "leave it off". It is the Governing mode's lead tab
314
+ // (craft-mode.js folds that mode's other tabs into a dropdown under it).
315
+ // Gated on the atom its page is (requireBoardVotePage), and it sits in this
316
+ // head-less group for the reason the Board Room does: the atom floors below
317
+ // any rank head. Cosmetic only (ADR 0016).
318
+ { id: 'city', label: 'City', icon: 'city', href: '/city', gate: 'perm:board.vote.cast', position: true },
319
+ // task 1003744 (goal 1000111): the Board Room lives HERE, in the project's
320
+ // own group, and gates on the atom its page gates on.
321
+ //
322
+ // WHY IT LEFT GOVERNMENT. ADR 0266 carved the room out of /government, so
323
+ // the item first landed in the Government group and read that group's metic
324
+ // floor — the closest rank the shell could then express. Its real wall is
325
+ // `board.vote.cast` (auth.js requireBoardVotePage), which floors at XENOS on
326
+ // purpose so that WIDENING the board works (modules/government/board.js). The
327
+ // moment an instance seats a sub-Metic member — the config change ADR 0175 §6
328
+ // exists to make — that member could reach the room by URL and by the
329
+ // board_votes need's deep link, with no sidebar entry.
330
+ //
331
+ // Fixing the gate alone would have put a wider item under a metic group head,
332
+ // so a newcomer board member would see a group labelled "Government" holding
333
+ // one link — more authority than they hold. The owner's call (2026-09-09) was
334
+ // to move it instead: a sitting is work the project does, beside Goals and
335
+ // Tasks, not a monitoring surface. The Government group's audience and floor
336
+ // are unchanged.
337
+ //
338
+ // Cosmetic only, as every gate here is — the page gate is the wall (ADR 0016).
339
+ // task 1003737: the badge's first consumer. The owner's ask was to see that
340
+ // a sitting waits WITHOUT opening the room, and the count is already on
341
+ // /me (boardVotesNeed), pre-filtered to what the vote route would accept —
342
+ // so the badge counts ballots this viewer can actually cast, never "items
343
+ // open somewhere".
344
+ // task 1004685: `position: true` narrows the gate to seat holders (GOVERNING IS A POSITION).
345
+ { id: 'board-room', label: 'Board Room', icon: 'boardroom', href: '/board-room', gate: 'perm:board.vote.cast', count: 'board_votes', position: true },
353
346
  ] },
354
347
  // Relabeled 'Archon' → 'Governance' in ADR 0157, then → 'Government' by ADR 0174: these pages are no longer
355
348
  // Archon-only, so a group header reading "Archon" over items a Metic can open
356
349
  // would misdescribe the ladder to the builders most likely to be learning it.
357
350
  // task 1004685: a governing surface throughout, so head and items carry `position`.
358
- { group: 'Government', gate: 'government', position: true, items: [
351
+ // task 1005071: 'Government' → 'Run the project' — the operator surfaces.
352
+ { group: 'Run the project', gate: 'government', position: true, items: [
359
353
  // The Board Room was FIRST here from the ADR 0266 carve until task 1003744
360
354
  // moved it into the project group — its wall is an ATOM (`board.vote.cast`,
361
355
  // xenos floor) rather than this group's rank, and a group whose audience is
@@ -395,14 +389,36 @@
395
389
  // reading 'Ranks' beside it is the confusion this rename exists to remove. This tab
396
390
  // is where a rank's permissions are granted, so it says what it does.,
397
391
  ] },
392
+ // task 1005071: 'Docs' → 'Help', and every craft shows it (craft-mode.js ALWAYS_TABS).
393
+ { group: 'Help', gate: 'signed', items: [
394
+ // task 1002710: primer (and the old diagrams page) were gate:'member' (rank
395
+ // !== xenos) to mirror the old thetes+ page gate. That gate is gone — the
396
+ // reading room is for the first-session newcomer above all — so the link
397
+ // follows it down to 'signed'. Leaving them at 'member' would have ungated the pages
398
+ // while still hiding them from the only readers the change was for.
399
+ { id: 'primer', label: 'Primer', icon: 'primer', href: '/primer', gate: 'signed' },
400
+ // task 1005046 (owner decision 2026-11-03, idea 1001311): ONE reading room.
401
+ // Ranks, Economy and Diagrams were three more pages explaining what the
402
+ // Primer already explained, so the copies drifted. They are Primer
403
+ // chapters now, each with its diagram beside it; /ranks, /drachmae and
404
+ // /diagrams 302 to the matching chapter (serve-internal.js), and the
405
+ // personal "Where you stand" card moved to Accomplishments.
406
+ // task 1004473: every skill and every bongos CLI verb, read live. Its page and
407
+ // both reads behind it are sign-in gated, so 'signed' is the real floor here.
408
+ { id: 'commands', label: 'Commands', icon: 'commands', href: '/commands', gate: 'signed' },
409
+ { id: 'atlas', label: 'Repo Atlas', icon: 'atlas', href: '/atlas', gate: 'member' },
410
+ { id: 'modules', label: 'Modules', icon: 'modules', href: '/modules', gate: 'member' },
411
+ // The rendered OpenAPI reference lives on the APEX (/docs), not this builders
412
+ // subdomain — external so the rewrite shim doesn't turn it into /builders/docs.
413
+ { id: 'apidocs', label: 'API docs', icon: 'apidocs', href: '/docs', gate: 'signed', external: true },
414
+ ] },
398
415
  ];
399
416
 
400
417
  // ---------- nav group collapse (labeled groups only) ----------
401
418
  // Mirrors the THEME_KEY localStorage pattern: per-group collapsed state,
402
419
  // survives reloads and carries across every hall page (shell.js runs on all
403
- // of them). Only labeled groups get a toggle — the unlabeled main group
404
- // (Home/Roadmap/Goals/Tasks) is core nav and always stays expanded. Keys are
405
- // the lowercased group label, so a regroup renames the key and a group reverts
420
+ // of them). Every group is labeled (task 1005071), so every group gets a toggle.
421
+ // Keys are the group label, lowercased and hyphenated, so a regroup renames the key and a group reverts
406
422
  // to expanded once — acceptable for a cosmetic preference.
407
423
  var NAV_COLLAPSE_KEY = 'hall-nav-collapsed';
408
424
 
@@ -435,7 +451,8 @@
435
451
  // source renderFoot() uses, so the core carries no hardcoded identity.
436
452
  var platformName = (brand.identity && (brand.identity.productName || brand.identity.worldName)) || 'projects';
437
453
  var groups = NAV.map(function (g) {
438
- var groupId = g.group ? g.group.toLowerCase() : '';
454
+ // A label can have spaces ('Run the project'); an id cannot, or aria-controls breaks.
455
+ var groupId = g.group ? g.group.toLowerCase().replace(/\s+/g, '-') : '';
439
456
  var collapsible = !!g.group;
440
457
  var collapsed = collapsible && isGroupCollapsed(groupId);
441
458
  var items = g.items.map(function (it) {
@@ -61,7 +61,7 @@
61
61
 
62
62
  // Every limit of the line rule, in characters unless named otherwise.
63
63
  const LIMITS = Object.freeze({
64
- nameWords: 3, name: 20, fact: 26, facts: 3, factLen: 24, actions: 2,
64
+ nameWords: 14, name: 90, fact: 60, facts: 3, factLen: 48, actions: 2,
65
65
  tiles: 3, tileValue: 8, tileWords: 2, tileLabel: 14,
66
66
  status: 44, clearLine: 30, clearSub: 40, record: 4, recordLen: 30,
67
67
  chips: 5, chip: 24, todayTarget: 10, pips: 12, saveLine: 48,
@@ -20,7 +20,8 @@
20
20
  // /goals/rollups) for the districts, and the viewer's earnings for "your share
21
21
  // this month". Where the real data had to bend the design, it is said once here
22
22
  // and in the ship notes:
23
- // * Districts. One per OPEN goal, in the order the goals come, between
23
+ // * Districts (only with `districts: true`; the page draws none since the
24
+ // owner's 2026-11-04 direction, and centres the civic row). One per OPEN goal, in the order the goals come, between
24
25
  // Automation and Review. Up to four keep the prototype's 90 px plots; past
25
26
  // four each plot narrows (never under 56 px) and past that the street grows
26
27
  // longer than the 1440 px artboard, so the page scrolls it sideways rather
@@ -330,9 +331,13 @@
330
331
  // between districts, 18 otherwise), starting at 43. Past MAX_DISTRICTS open
331
332
  // goals the first seven stand as districts and the eighth plot is the rest,
332
333
  // staked out, a door to the goals page.
333
- function layout(goals) {
334
- const shown = goals.length > MAX_DISTRICTS ? goals.slice(0, MAX_DISTRICTS - 1) : goals;
335
- const more = goals.length - shown.length;
334
+ // The street is the governance square (owner, 2026-11-04): the working areas
335
+ // are the city's own buildings, so by default no district stands on the
336
+ // street and the civic row is centred on the artboard. `districts` keeps the
337
+ // prototype's row (the kit's tests hold it to the prototype).
338
+ function layout(goals, districts) {
339
+ const shown = districts ? (goals.length > MAX_DISTRICTS ? goals.slice(0, MAX_DISTRICTS - 1) : goals) : [];
340
+ const more = districts ? goals.length - shown.length : 0;
336
341
  const dw = districtWidth(shown.length + (more ? 1 : 0));
337
342
  const segs = [];
338
343
  SEGS.forEach((s) => {
@@ -346,6 +351,8 @@
346
351
  if (i > 0) x += segs[i - 1].g !== s.g ? 30 : (s.d ? 10 : 18);
347
352
  s.x = x; x += s.w;
348
353
  });
354
+ const spare = ARTBOARD_W - (x + 43);
355
+ if (!districts && spare > 0) segs.forEach((s) => { s.x += Math.floor(spare / 2); });
349
356
  return { segs: segs, width: Math.max(ARTBOARD_W, x + 43), districtW: dw };
350
357
  }
351
358
 
@@ -510,6 +517,10 @@
510
517
  // living GovLiving.livingModel() over the same feeds, or null (no Leads,
511
518
  // no agents, no construction: the deep street with real data only)
512
519
  // roster GET /builders/roster body, or null (the people reading)
520
+ // districts true: stand a district per open goal between Automation and
521
+ // Review (the prototype's row). The page leaves it off: the
522
+ // street is the governance square and the working areas are the
523
+ // city's (owner, 2026-11-04)
513
524
  function streetModel(input) {
514
525
  const opts = input || {};
515
526
  const docket = opts.docket && typeof opts.docket === 'object' && Array.isArray(opts.docket.matters) ? opts.docket : null;
@@ -529,7 +540,7 @@
529
540
  const goals = (Array.isArray(opts.goals) ? opts.goals : []).filter((g) => g && g.status === 'open' && g.id != null);
530
541
  const byGoal = {};
531
542
  (Array.isArray(opts.rollups) ? opts.rollups : []).forEach((r) => { if (r && r.goal_id != null) byGoal[String(r.goal_id)] = r; });
532
- const L = layout(goals);
543
+ const L = layout(goals, opts.districts === true);
533
544
  const segs = L.segs, W = L.width;
534
545
  const at = {};
535
546
  segs.forEach((s) => { at[s.k || ('d:' + s.d)] = s; });
@@ -725,7 +725,23 @@
725
725
  return dv;
726
726
  }
727
727
 
728
+ // The floor picker (owner, 2026-11-04: a building is a place to work). On a
729
+ // wide window the section is cropped to the building itself (PICKER_CROP: its
730
+ // crown, storeys and ground, without the 800 x 708 section's empty margins)
731
+ // and scaled to the stage height, never under half size (a floor stays a
732
+ // 24 px target) nor over 0.72 (the desk keeps the room). The CSS variables
733
+ // street.js sets on the drawing's box and its column.
734
+ const PICKER_CROP = Object.freeze({ x: 50, y: 120, w: 590, h: 580 });
735
+ function pickerFit(stageH) {
736
+ const c = PICKER_CROP;
737
+ const k = Math.max(0.5, Math.min(0.72, Math.round(((Number(stageH) || 708) - 60) / c.h * 1000) / 1000));
738
+ const px = (n) => Math.round(n * k) + 'px';
739
+ return { k: k, box: { '--inside-art-scale': String(k), '--inside-art-x': px(-c.x), '--inside-art-y': px(-c.y) },
740
+ art: { '--inside-art-w': px(c.w), '--inside-art-h': px(c.h) } };
741
+ }
742
+
728
743
  const api = {
744
+ pickerFit: pickerFit, PICKER_CROP: PICKER_CROP,
729
745
  crown: crown, workItem: workItem, floorSymbol: floorSymbol, symbolPath: symbolPath, buildingView: buildingView, districtView: districtView,
730
746
  civicInfo: civicInfo, floorFor: floorFor, holderOf: holderOf, recordOf: recordOf, buildingModel: buildingModel,
731
747
  districtInfo: districtInfo, districtModel: districtModel,
@@ -530,6 +530,37 @@ html[data-fullscreen="on"] .street-top { padding-right: 184px; }
530
530
  .inside-name:focus { outline: none; }
531
531
  .inside-name:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
532
532
 
533
+ /* THE HALL IS FOR WORK (owner, 2026-11-04; task 1005075). The street's own
534
+ header strip and the six readings along the bottom repeat what the shell's
535
+ top bar and the buildings already say, and inside a building they squeezed
536
+ the floor you came to work on into a corner. Both go. */
537
+ .street-top, .street-signs { display: none; }
538
+
539
+ /* Inside a building: the drawing is the floor picker, at half size in its own
540
+ column (a floor stays a 24 px target); the chosen floor's desk takes the rest
541
+ of the window and scrolls on its own. The building's own facts (who holds it,
542
+ how it decides, its record) follow the desk instead of standing above it. */
543
+ @media (min-width: 900px) {
544
+ .inside--work { display: block; overflow: hidden; }
545
+ .inside--work .inside-sizer { width: auto; height: 100%; margin: 0; overflow: visible; }
546
+ .inside--work .inside-board { position: static; width: auto; height: 100%; transform: none; display: grid;
547
+ grid-template-columns: var(--inside-art-w, 400px) minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr);
548
+ column-gap: 40px; padding: 0 32px 0 16px; box-sizing: border-box; animation: none; }
549
+ .inside--work .inside-head { position: static; grid-column: 1 / -1; padding: 14px 0 6px 16px; }
550
+ .inside--work .inside-art { position: relative; width: var(--inside-art-w, 400px); height: var(--inside-art-h, 354px); grid-row: 2; align-self: start; overflow: hidden; }
551
+ .inside--work .inside-art__box { width: 800px; transform: translate(var(--inside-art-x, 0), var(--inside-art-y, 0)) scale(var(--inside-art-scale, 1)); }
552
+ .inside--work .inside-panel { position: static; grid-row: 2; width: auto; max-width: 880px; height: 100%; padding: 4px 4px 32px 0; box-sizing: border-box; gap: 20px; }
553
+ .inside--work .inside-title { order: 0; flex-direction: row; align-items: baseline; gap: 14px; flex-wrap: wrap; }
554
+ .inside--work .inside-name { font-size: 28px; }
555
+ .inside--work .inside-holds { font-size: 14px; }
556
+ .inside--work .inside-open { order: 1; }
557
+ .inside--work .govx { order: 2; }
558
+ .inside--work .inside-gov { order: 3; }
559
+ .inside--work .inside-record { order: 4; }
560
+ .inside--work .inside-open__head .inside-plate { width: 44px; height: 44px; border-radius: 10px; }
561
+ .inside--work .inside-open__title { font-size: 24px; }
562
+ }
563
+
533
564
  /* narrow: the drawing in its own sideways strip, the panel below it */
534
565
  @media (max-width: 899px) {
535
566
  .inside { display: block; overflow-x: hidden; overflow-y: auto; }
@@ -543,6 +574,11 @@ html[data-fullscreen="on"] .street-top { padding-right: 184px; }
543
574
  .inside-dhead { order: 1; }
544
575
  .inside-legend { order: 3; flex-direction: column; gap: 8px; }
545
576
  .inside-panel { order: 3; overflow: visible; }
577
+ /* the floor's desk straight under the name, the building's facts after it */
578
+ .inside-open { order: 1; }
579
+ .govx { order: 2; }
580
+ .inside-gov { order: 3; }
581
+ .inside-record { order: 4; }
546
582
  .inside-name { font-size: 32px; }
547
583
  .inside-gov__row { flex-wrap: wrap; row-gap: 4px; }
548
584
  .desk-row__main { flex-wrap: wrap; row-gap: 8px; }
@@ -230,8 +230,8 @@
230
230
 
231
231
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
232
232
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
233
- <script src="/builders/craft-mode.js?v=2026-11-04-money"></script>
234
- <script src="/builders/shell.js?v=2026-11-04-money"></script>
233
+ <script src="/builders/craft-mode.js?v=2026-11-05-sections"></script>
234
+ <script src="/builders/shell.js?v=2026-11-05-sections"></script>
235
235
  <script src="/builders/palette.js" defer></script>
236
236
  <script src="/builders/gov-living.js?v=2026-10-09-positions1"></script>
237
237
  <script src="/builders/gov-feeds.js?v=2026-10-09-living3"></script>
@@ -77,7 +77,7 @@
77
77
  constitution: '/api/bongos/government/constitution',
78
78
  uat: function (id) { return '/api/bongos/done-when/' + encodeURIComponent(id) + '/uat'; },
79
79
  };
80
- var NARROW = 900;
80
+ var NARROW = 900, FOCUS_W = 1120; // FOCUS_W: the civic row and its pins, which a wide window fits
81
81
  var I = window.GovInside;
82
82
  // The artboard's street band: the prototype's 900-high board less its 64 px
83
83
  // header and 128 px footer, which this page lays out as real rows, and less
@@ -159,13 +159,10 @@
159
159
  function here() { return S.parseHash(window.location && window.location.hash); }
160
160
 
161
161
  // ---------- fit the artboard's street band to the stage ----------
162
- // 1440 x 708 at scale 1 is the prototype exactly. A wider or taller stage
163
- // grows it, a smaller one shrinks it; a phone keeps it near full size and the
164
- // street scrolls sideways inside its window, so a pin stays a pin.
165
- // A stage taller than the street needs (a wide window scales the street to its
166
- // width) gives the spare height to the foot of the page, under the readings, so
167
- // the street sits on them with no empty sky above it (task 1004718). The window
168
- // gives the height back whenever a building's inside is open.
162
+ // 1440 x 708 at scale 1 is the prototype exactly. A wide window fits the civic
163
+ // row (FOCUS_W), crops the haze either side and centres the street in the stage
164
+ // (owner, 2026-11-04); a phone keeps it near full size and scrolls it sideways
165
+ // from the first building, so a pin stays a pin.
169
166
  function fit() {
170
167
  var win = $('streetWindow'), inside = $('streetInside');
171
168
  var insideOn = !!(inside && !inside.hidden);
@@ -175,23 +172,29 @@
175
172
  if (!win || !sizer || !board) return;
176
173
  var W = view.model ? view.model.width : S.ARTBOARD_W;
177
174
  var cw = win.clientWidth || window.innerWidth || 1440, ch = win.clientHeight || 708;
178
- var s = Math.min(cw / W, ch / BAND_H);
179
- if (cw / W < 0.6) s = Math.min(1, ch / BAND_H);
175
+ var wide = cw >= NARROW;
176
+ var s = wide ? Math.min(cw / FOCUS_W, ch / BAND_H) : Math.min(cw / W, ch / BAND_H);
177
+ if (cw / W < 0.6 && !wide) s = Math.min(1, ch / BAND_H);
180
178
  s = Math.max(0.4, Math.round(s * 1000) / 1000);
181
179
  view.scale = s;
182
- sizer.style.width = Math.round(W * s) + 'px';
180
+ var sizerW = wide ? Math.min(Math.round(W * s), cw) : Math.round(W * s);
181
+ sizer.style.width = sizerW + 'px';
183
182
  sizer.style.height = Math.round(BAND_H * s) + 'px';
184
- if (!insideOn && win.style && Math.round(BAND_H * s) < ch - 1) win.style.flex = '0 0 ' + Math.round(BAND_H * s) + 'px';
183
+ board.style.left = wide ? Math.round((sizerW - W * s) / 2) + 'px' : '';
184
+ if (win.style) win.style.overflowX = wide ? 'hidden' : '';
185
185
  board.style.width = W + 'px';
186
186
  board.style.setProperty('--street-scale', String(s));
187
187
  var svg = $('streetSvg');
188
188
  if (svg) { svg.setAttribute('width', String(W)); svg.setAttribute('viewBox', '0 0 ' + W + ' 900'); }
189
+ var doors = $('streetDoors'), first = doors && (doors.firstElementChild || (doors.children && doors.children[0]));
190
+ if (!wide && !insideOn && !view.scrolled && first) { win.scrollLeft = Math.max(0, Math.round((parseFloat(first.style.left) || 0) * s) - 16); view.scrolled = true; }
189
191
  placeCard();
190
192
  }
191
193
 
192
- // The interior's artboard: 1440 x 708 scaled to the window like the street;
193
- // under NARROW px the board is a column and only the drawing scales, in its
194
- // own sideways strip, never under 0.6 so a floor stays a 24 px target.
194
+ // The interior: a building on a wide window is laid out, its drawing the floor
195
+ // picker (GovInside.pickerFit) beside the desk; a district's 1440 x 708 board
196
+ // scales to the window like the street; under NARROW px the board is a column
197
+ // and only the drawing scales, in its own sideways strip, never under 0.6.
195
198
  function fitInside() {
196
199
  var box = $('streetInside'), sizer = $('insideSizer'), board = $('insideBoard'), art = $('insideArt'), artBox = $('insideArtBox'), span = $('insideArtSpan');
197
200
  if (!box || !sizer || !board || !art || !artBox || box.hidden) return;
@@ -207,6 +210,14 @@
207
210
  if (span) { span.style.width = Math.round(artW * a) + 'px'; span.style.height = Math.round(708 * a) + 'px'; }
208
211
  return;
209
212
  }
213
+ var work = !(view.inside && view.inside.kind === 'district');
214
+ if (box.classList) box.classList.toggle('inside--work', work);
215
+ if (work) {
216
+ var f = I.pickerFit(ch);
217
+ sizer.style.width = ''; sizer.style.height = ''; board.style.setProperty('--inside-scale', '1');
218
+ [[artBox, f.box], [art, f.art]].forEach(function (e) { Object.keys(e[1]).forEach(function (k) { e[0].style.setProperty(k, e[1][k]); }); });
219
+ if (span) { span.style.width = '0px'; span.style.height = '0px'; } return;
220
+ }
210
221
  var s = Math.max(0.4, Math.round(Math.min(cw / 1440, ch / 708) * 1000) / 1000);
211
222
  sizer.style.width = Math.round(1440 * s) + 'px';
212
223
  sizer.style.height = Math.round(708 * s) + 'px';
@@ -292,7 +303,9 @@
292
303
  // GovStreetDepth.page's (street-depth.js); this file hands it the street's
293
304
  // data, its card opener and a redraw, and keeps the street's own wiring.
294
305
  var X = window.GovStreetDepth.page;
295
- function drawPin(p) { return X.pin(p, { open: view.open, openCard: openCard }); }
306
+ // a pin goes into its building, the matter a desk row there (owner, 2026-11-04: no pop-ups)
307
+ function goPin(key, el, keys) { var b = el && el.getAttribute && el.getAttribute('data-building'); if (b) window.location.hash = 'building=' + b; else openCard(key, el, keys); }
308
+ function drawPin(p) { return X.pin(p, { open: view.open, openCard: goPin }); }
296
309
  function drawSign(sg, tile) { return X.sign(sg, tile); }
297
310
  function drawDepth(dm, W) { return X.depth(dm, W); }
298
311
  // the living model arrives after the street's own reads: the street and the deck draw it
@@ -224,8 +224,8 @@
224
224
 
225
225
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
226
226
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
227
- <script src="/builders/craft-mode.js?v=2026-11-04-money"></script>
228
- <script src="/builders/shell.js?v=2026-11-04-money"></script>
227
+ <script src="/builders/craft-mode.js?v=2026-11-05-sections"></script>
228
+ <script src="/builders/shell.js?v=2026-11-05-sections"></script>
229
229
  <script src="/builders/palette.js" defer></script>
230
230
  <!-- The kit, for its ONE renderer: the full-size ship-time visual figure
231
231
  (task 1003829), which the older reviews still show. -->
@@ -58,8 +58,8 @@
58
58
  time so the chrome exists before the deferred page scripts execute. -->
59
59
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
60
60
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
61
- <script src="/builders/craft-mode.js?v=2026-11-04-money"></script>
62
- <script src="/builders/shell.js?v=2026-11-04-money"></script>
61
+ <script src="/builders/craft-mode.js?v=2026-11-05-sections"></script>
62
+ <script src="/builders/shell.js?v=2026-11-05-sections"></script>
63
63
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
64
64
  <script src="/builders/palette.js" defer></script>
65
65
  <!-- task-detail.js still owns the #/idea/N and #/blocker/N deep-link overlay
@@ -140,8 +140,8 @@
140
140
 
141
141
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
142
142
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
143
- <script src="/builders/craft-mode.js?v=2026-11-04-money"></script>
144
- <script src="/builders/shell.js?v=2026-11-04-money"></script>
143
+ <script src="/builders/craft-mode.js?v=2026-11-05-sections"></script>
144
+ <script src="/builders/shell.js?v=2026-11-05-sections"></script>
145
145
  <script src="/builders/palette.js" defer></script>
146
146
 
147
147
  <script src="/builders/idea-objects.js?v=2026-09-27-split"></script>
@@ -144,8 +144,8 @@
144
144
 
145
145
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
146
146
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
147
- <script src="/builders/craft-mode.js?v=2026-11-04-money"></script>
148
- <script src="/builders/shell.js?v=2026-11-04-money"></script>
147
+ <script src="/builders/craft-mode.js?v=2026-11-05-sections"></script>
148
+ <script src="/builders/shell.js?v=2026-11-05-sections"></script>
149
149
  <script src="/builders/palette.js" defer></script>
150
150
  <script src="/builders/brand-holes.js?v=2026-09-29-aqfix"></script>
151
151
  <script src="/builders/tweak-editor-lib.js?v=2026-09-29-tw27"></script>
@@ -167,8 +167,8 @@
167
167
  chrome exists before the page script runs. -->
168
168
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
169
169
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
170
- <script src="/builders/craft-mode.js?v=2026-11-04-money"></script>
171
- <script src="/builders/shell.js?v=2026-11-04-money"></script>
170
+ <script src="/builders/craft-mode.js?v=2026-11-05-sections"></script>
171
+ <script src="/builders/shell.js?v=2026-11-05-sections"></script>
172
172
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
173
173
  <script src="/builders/palette.js" defer></script>
174
174
  <!-- shared component kit (window.OTBKit) — tabs, ledger, pager (task 2166). -->
@@ -173,8 +173,8 @@
173
173
  time so the chrome exists before the deferred page scripts execute. -->
174
174
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
175
175
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
176
- <script src="/builders/craft-mode.js?v=2026-11-04-money"></script>
177
- <script src="/builders/shell.js?v=2026-11-04-money"></script>
176
+ <script src="/builders/craft-mode.js?v=2026-11-05-sections"></script>
177
+ <script src="/builders/shell.js?v=2026-11-05-sections"></script>
178
178
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
179
179
  <script src="/builders/palette.js" defer></script>
180
180
  <!-- task 1004727: the ONE karma vote control every task/idea list renders -->