@bongos/core 1.21.19 → 1.21.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bongos-core.json +84 -74
- package/docs/copy-inventory.md +29 -25
- package/docs/copy-registry.json +87 -30
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +9 -3
- package/docs/page-readings.json +399 -388
- package/modules/autonomy/governor-docket.js +118 -4
- package/modules/economy/cost.js +36 -0
- package/modules/economy/governor-docket.js +81 -0
- package/modules/economy/routes/cost.js +2 -0
- package/modules/government/docket-floors.js +222 -0
- package/modules/government/docket-vocab.js +20 -0
- package/modules/government/docket.js +186 -5
- package/modules/hall-ui/public/atlas.html +1 -1
- package/modules/hall-ui/public/blockers.html +1 -1
- package/modules/hall-ui/public/board-room.html +1 -1
- package/modules/hall-ui/public/city.css +16 -2
- package/modules/hall-ui/public/city.html +29 -9
- package/modules/hall-ui/public/city.js +25 -2
- package/modules/hall-ui/public/city.states.json +7 -1
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/commands.html +1 -1
- package/modules/hall-ui/public/copy-desk.html +1 -1
- package/modules/hall-ui/public/deploy.html +1 -1
- package/modules/hall-ui/public/diagrams.html +1 -1
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/fleet.html +1 -1
- package/modules/hall-ui/public/gate.html +1 -1
- package/modules/hall-ui/public/goal-map.html +1 -1
- package/modules/hall-ui/public/goals.html +1 -1
- package/modules/hall-ui/public/government.html +1 -1
- package/modules/hall-ui/public/idea.html +1 -1
- package/modules/hall-ui/public/ideas.html +1 -1
- package/modules/hall-ui/public/index.html +1 -1
- package/modules/hall-ui/public/modules.html +1 -1
- package/modules/hall-ui/public/primer.html +1 -1
- package/modules/hall-ui/public/profile.html +1 -1
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/ranks.html +1 -1
- package/modules/hall-ui/public/roadmap.html +1 -1
- package/modules/hall-ui/public/roster.html +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings.html +1 -1
- package/modules/hall-ui/public/shell.js +3 -0
- package/modules/hall-ui/public/street-inside.js +99 -43
- package/modules/hall-ui/public/street.css +17 -4
- package/modules/hall-ui/public/street.html +26 -8
- package/modules/hall-ui/public/street.js +16 -2
- package/modules/hall-ui/public/street.states.json +17 -1
- package/modules/hall-ui/public/studio.html +1 -1
- package/modules/hall-ui/public/task.html +1 -1
- package/modules/hall-ui/public/thinking.html +1 -1
- package/modules/hall-ui/public/tweak-editor.html +1 -1
- package/modules/hall-ui/public/watch.html +1 -1
- package/modules/hall-ui/public/work.html +1 -1
- package/modules/ideas/governor-docket.js +42 -1
- package/modules/lifecycle/governor-docket.js +197 -2
- package/modules/security/routes/reports.js +16 -0
- package/modules/security/security.js +5 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/src/module-api.js +1 -1
- package/tests/governor_docket.mjs +186 -2
- package/tests/hall_audit.mjs +7 -12
- package/tests/hall_city.mjs +44 -4
- package/tests/hall_street.mjs +37 -5
- package/tests/hall_street_inside.mjs +97 -3
- package/tests/hall_tweak_editor.mjs +1 -1
- package/tests/nav_permission_atoms.mjs +1 -1
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
|
|
102
102
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
103
103
|
|
|
104
|
-
<script src="/builders/shell.js?v=2026-10-
|
|
104
|
+
<script src="/builders/shell.js?v=2026-10-02-theme"></script>
|
|
105
105
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
106
106
|
<script src="/builders/palette.js" defer></script>
|
|
107
107
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
|
|
116
116
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
117
117
|
|
|
118
|
-
<script src="/builders/shell.js?v=2026-10-
|
|
118
|
+
<script src="/builders/shell.js?v=2026-10-02-theme"></script>
|
|
119
119
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
120
120
|
<script src="/builders/palette.js" defer></script>
|
|
121
121
|
<!-- the shared markdown reader (window.OTBMdReader) — primer.js renders through
|
|
@@ -206,7 +206,7 @@
|
|
|
206
206
|
|
|
207
207
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
208
208
|
chrome exists before the page script runs. -->
|
|
209
|
-
<script src="/builders/shell.js?v=2026-10-
|
|
209
|
+
<script src="/builders/shell.js?v=2026-10-02-theme"></script>
|
|
210
210
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
211
211
|
<script src="/builders/palette.js" defer></script>
|
|
212
212
|
<!-- shared frontend toolkit (window.OTB) — loaded before profile.js, which pulls from it. -->
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
|
|
59
59
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
60
60
|
|
|
61
|
-
<script src="/builders/shell.js?v=2026-10-
|
|
61
|
+
<script src="/builders/shell.js?v=2026-10-02-theme"></script>
|
|
62
62
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
63
63
|
<script src="/builders/palette.js" defer></script>
|
|
64
64
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
|
|
78
78
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
79
79
|
chrome exists before the page script runs. -->
|
|
80
|
-
<script src="/builders/shell.js?v=2026-10-
|
|
80
|
+
<script src="/builders/shell.js?v=2026-10-02-theme"></script>
|
|
81
81
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
82
82
|
<script src="/builders/palette.js" defer></script>
|
|
83
83
|
<!-- shared frontend toolkit (window.OTB) — loaded before ranks.js, which pulls from it. -->
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
43
43
|
|
|
44
|
-
<script src="/builders/shell.js?v=2026-10-
|
|
44
|
+
<script src="/builders/shell.js?v=2026-10-02-theme"></script>
|
|
45
45
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
46
46
|
<script src="/builders/palette.js" defer></script>
|
|
47
47
|
<script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
|
|
54
54
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
55
55
|
chrome exists before the page script runs. -->
|
|
56
|
-
<script src="/builders/shell.js?v=2026-10-
|
|
56
|
+
<script src="/builders/shell.js?v=2026-10-02-theme"></script>
|
|
57
57
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
58
58
|
<script src="/builders/palette.js" defer></script>
|
|
59
59
|
<!-- shared frontend toolkit (window.OTB — rank labels, number formats) + the
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
|
|
100
100
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
101
101
|
chrome exists before the page script runs. -->
|
|
102
|
-
<script src="/builders/shell.js?v=2026-10-
|
|
102
|
+
<script src="/builders/shell.js?v=2026-10-02-theme"></script>
|
|
103
103
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
104
104
|
<script src="/builders/palette.js" defer></script>
|
|
105
105
|
<!-- shared frontend toolkit (window.OTB) — loaded BEFORE sessions.js so it can pull from it. -->
|
|
@@ -435,7 +435,7 @@
|
|
|
435
435
|
|
|
436
436
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
437
437
|
chrome exists before the page scripts run. -->
|
|
438
|
-
<script src="/builders/shell.js?v=2026-10-
|
|
438
|
+
<script src="/builders/shell.js?v=2026-10-02-theme"></script>
|
|
439
439
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
440
440
|
<script src="/builders/palette.js" defer></script>
|
|
441
441
|
<!-- shared frontend toolkit (window.OTB) — loaded before consuming IIFEs. -->
|
|
@@ -1404,5 +1404,8 @@
|
|
|
1404
1404
|
closeDrawer: closeDrawer,
|
|
1405
1405
|
isFullscreen: isFullscreen,
|
|
1406
1406
|
setFullscreen: setFullscreen,
|
|
1407
|
+
// task 1004544: a page with its own light/dark switch (the governor city
|
|
1408
|
+
// and street) repaints the top bar's after writing 'hall-theme'.
|
|
1409
|
+
applyTheme: applyTheme,
|
|
1407
1410
|
};
|
|
1408
1411
|
})();
|
|
@@ -24,20 +24,21 @@
|
|
|
24
24
|
// are about the government rather than the sample project). A floor the
|
|
25
25
|
// governor docket (GET /government/docket) already reads is BOUND to it: a
|
|
26
26
|
// source (its open matters, their count, needs you when one does) or a
|
|
27
|
-
// reading (its figure and line), or
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
27
|
+
// reading (its figure and line), or a FLOOR (task 1004527: docket.floors, a
|
|
28
|
+
// state the owning module reads live — who sits, what the month cost, what
|
|
29
|
+
// went live). A floor whose engine does not exist yet comes back `coming`
|
|
30
|
+
// and stands faint with "not recorded yet" and the docket's reason; nothing
|
|
31
|
+
// here ever writes a number the docket did not send.
|
|
32
|
+
// * Who holds a building and how it decides: the building's `decision` row
|
|
33
|
+
// (task 1004529's table) and the `holders` that row seats now. Without a row
|
|
34
|
+
// the seat is drawn empty and says so, and the rule is the building's own
|
|
35
|
+
// matters' rule, else the design's default, named as the default
|
|
36
|
+
// (holderOf()).
|
|
37
37
|
// * On the table here: the docket's matters at this building. Each row opens
|
|
38
38
|
// the short card (GovStreet.cardFor(); task 1004528 makes it act).
|
|
39
|
-
// * The record:
|
|
40
|
-
//
|
|
39
|
+
// * The record: the building's history (docket.history: rulings, amendments,
|
|
40
|
+
// sign-offs, releases, payouts) and its matters, filed by month, one drawer
|
|
41
|
+
// a month, newest first.
|
|
41
42
|
// * The annex lot stands when the building is full, as the prototype's
|
|
42
43
|
// "growth" lines say it would.
|
|
43
44
|
// * A district's work is the goal's tasks (GET /tasks?goal_id=N): shape from
|
|
@@ -268,7 +269,7 @@
|
|
|
268
269
|
sym: f ? Object.assign(symbolPath(floorSymbol(f.name)), { c: '#fb9569' }) : { d: '', f: '', c: 'none' },
|
|
269
270
|
flipA: ((st.picks || 0) % 2) === 0, flipB: ((st.picks || 0) % 2) === 1,
|
|
270
271
|
openTitle: f ? f.name : '', openLines: f ? f.lines.map((l, i) => ({ key: 'l' + i, text: l[0], meta: l[1] })) : [],
|
|
271
|
-
drawers: record.map((r, i) => ({ key: r.label, index: i, label: r.label, count: r.items.length, aria: 'Open the ' + r.label + ' drawer', selected: i === selDrawer, bg: i === selDrawer ? T.wash : 'transparent', border: i === selDrawer ? T.ink : T.ruleStrong, handle: i === selDrawer ? T.ink : T.ruleStrong, color: T.ink })),
|
|
272
|
+
drawers: record.map((r, i) => ({ key: r.label, index: i, label: r.label, count: r.count == null ? r.items.length : r.count, aria: 'Open the ' + r.label + ' drawer', selected: i === selDrawer, bg: i === selDrawer ? T.wash : 'transparent', border: i === selDrawer ? T.ink : T.ruleStrong, handle: i === selDrawer ? T.ink : T.ruleStrong, color: T.ink })),
|
|
272
273
|
drawerItems: selDrawer >= 0 ? record[selDrawer].items.map((it, i) => ({ key: 'r' + i, text: it[0], meta: it[1] })) : [],
|
|
273
274
|
onFile: b.onFile, shapes: shapes, texts: texts, syms: syms, floors: floors,
|
|
274
275
|
lift: { x: X - 38, y: GY - FH + 6, ty: -(selFloor * FH), h: FH - 16 },
|
|
@@ -314,7 +315,7 @@
|
|
|
314
315
|
// its sample project: what it holds, the rule the design gives it, its
|
|
315
316
|
// capacity, its growth line and its annex.
|
|
316
317
|
const GROUPS = Object.freeze({ charter: 'how we decide', rules: 'how we decide', disputes: 'how we decide', people: 'who and what', planning: 'who and what', automation: 'the work', review: 'checks and going live', release: 'checks and going live', treasury: 'money' });
|
|
317
|
-
const RULE_WORDS = Object.freeze({ decide: 'one person decides', first: 'first yes wins', consent: 'passes unless someone objects', majority: 'more than half must say yes', unanimous: 'everyone has to agree', signoff: 'signed off by someone who did not build it', rule: 'settled by a rule' });
|
|
318
|
+
const RULE_WORDS = Object.freeze({ decide: 'one person decides', first: 'first yes wins', consent: 'passes unless someone objects', majority: 'more than half must say yes', unanimous: 'everyone has to agree', signoff: 'signed off by someone who did not build it', rule: 'settled by a rule', vacant: 'its matters wait until a seat is filled' });
|
|
318
319
|
|
|
319
320
|
// A floor: [name, what it holds, binding]. A binding is { source }, { reading }
|
|
320
321
|
// or { goals: true }; no binding is a floor not recorded yet.
|
|
@@ -322,12 +323,12 @@
|
|
|
322
323
|
charter: {
|
|
323
324
|
holds: 'The form of government. Seven sections say who holds each building and how it decides.', rule: 'unanimous', cap: 9,
|
|
324
325
|
growth: 'Room for more sections before the charter needs a second volume.', annex: 'room for a second volume',
|
|
325
|
-
floors: [['Founding', 'the starting government'], ['§1 Purpose', 'what this project is for'], ['§2 Membership', 'who belongs, and how they join'], ['§3 The council', 'who sits on the council'], ['§4 Rules', 'how a rule passes'], ['§5 Money', 'who may spend, and how much'], ['§6 Review', 'who signs off finished work'], ['§7 Release', 'who puts work live']],
|
|
326
|
+
floors: [['Founding', 'the starting government', { floor: 'charter.founding' }], ['§1 Purpose', 'what this project is for', { floor: 'charter.purpose' }], ['§2 Membership', 'who belongs, and how they join', { floor: 'charter.membership' }], ['§3 The council', 'who sits on the council', { floor: 'charter.council' }], ['§4 Rules', 'how a rule passes', { floor: 'charter.rules' }], ['§5 Money', 'who may spend, and how much', { floor: 'charter.money' }], ['§6 Review', 'who signs off finished work', { floor: 'charter.review' }], ['§7 Release', 'who puts work live', { floor: 'charter.release' }]],
|
|
326
327
|
},
|
|
327
328
|
rules: {
|
|
328
329
|
holds: 'Standing rules. Each one settles a kind of matter so the council never votes on it again.', rule: 'majority', cap: 8,
|
|
329
330
|
growth: 'When the hall fills, the council can open an annex for one area, such as money rules.', annex: 'room for an annex, such as money rules',
|
|
330
|
-
floors: [['Rules in force', 'the rules settling matters now', { reading: 'rules' }], ['New rules', 'a rule put to the council', { source: 'standing_rules' }]],
|
|
331
|
+
floors: [['Rules in force', 'the rules settling matters now', { floor: 'rules.in_force', reading: 'rules' }], ['New rules', 'a rule put to the council', { source: 'standing_rules' }]],
|
|
331
332
|
},
|
|
332
333
|
disputes: {
|
|
333
334
|
holds: 'Grade appeals, removal requests and security reports. Every ruling becomes precedent for the next one.', rule: 'decide', cap: 5,
|
|
@@ -337,32 +338,32 @@
|
|
|
337
338
|
people: {
|
|
338
339
|
holds: 'Who belongs, the rank each person holds, and who sits in which seat.', rule: 'consent', cap: 8,
|
|
339
340
|
growth: 'A new floor opens whenever the charter creates a new seat.', annex: 'room for an annex of seats',
|
|
340
|
-
floors: [['The council', 'who sits, and for how long'], ['Testers', 'who signs off finished work'], ['Builders', 'who builds here', { reading: 'people' }], ['Newcomers', 'builders on newcomer tasks'], ['Waiting to join', 'asked to join the project', { source: 'access_requests' }], ['Ranks', 'the ranks, and who holds each'], ['Goal teams', 'asked to join a goal team', { source: 'goal_join_requests' }]],
|
|
341
|
+
floors: [['The council', 'who sits, and for how long', { floor: 'people.council' }], ['Testers', 'who signs off finished work', { floor: 'people.testers' }], ['Builders', 'who builds here', { reading: 'people' }], ['Newcomers', 'builders on newcomer tasks', { floor: 'people.newcomers' }], ['Waiting to join', 'asked to join the project', { source: 'access_requests' }], ['Ranks', 'the ranks, and who holds each', { floor: 'people.ranks' }], ['Goal teams', 'asked to join a goal team', { source: 'goal_join_requests' }]],
|
|
341
342
|
},
|
|
342
343
|
planning: {
|
|
343
344
|
holds: 'Goals, and the promises that say when each one is done. Kept promises are what the project owes its users.', rule: 'majority', cap: 9,
|
|
344
345
|
growth: 'When it fills, the next new goal opens a second planning office.', annex: 'room for a second planning office',
|
|
345
|
-
floors: [['Goals', 'the open goals', { goals: true }], ['Promises', 'what the goals say they deliver', { reading: 'promises' }], ['Ideas to ratify', 'full ideas waiting for a vote'], ['Priorities', 'the order goals are worked in'], ['Versions', 'what each version holds'], ['Waiting on a decision', 'blockers holding work', { source: 'blockers' }]],
|
|
346
|
+
floors: [['Goals', 'the open goals', { floor: 'planning.goals', goals: true }], ['Promises', 'what the goals say they deliver', { reading: 'promises' }], ['Ideas to ratify', 'full ideas waiting for a vote', { floor: 'planning.ideas' }], ['Priorities', 'the order goals are worked in', { floor: 'planning.priorities' }], ['Versions', 'what each version holds', { floor: 'planning.versions' }], ['Waiting on a decision', 'blockers holding work', { source: 'blockers' }]],
|
|
346
347
|
},
|
|
347
348
|
automation: {
|
|
348
349
|
holds: 'AI builders working on their own. You choose which goals they may build and how much they may spend, and you can stop them at any time.', rule: 'decide', cap: 6,
|
|
349
350
|
growth: 'A new floor opens for each goal you let it build.', annex: 'room for an annex',
|
|
350
|
-
floors: [['The switch', 'unattended building, on or off', { source: 'autobongos_fence' }], ['Allowed goals', 'the goals it may build'], ['Running now', 'builds in progress'], ['Spending limit', 'what it may spend
|
|
351
|
+
floors: [['The switch', 'unattended building, on or off', { source: 'autobongos_fence' }], ['Allowed goals', 'the goals it may build', { floor: 'automation.allowed_goals' }], ['Running now', 'builds in progress', { floor: 'automation.running' }], ['Spending limit', 'what it may spend', { floor: 'automation.spending_limit' }], ['Reports', 'what it did, told without asking', { floor: 'automation.reports' }]],
|
|
351
352
|
},
|
|
352
353
|
review: {
|
|
353
354
|
holds: 'Testing finished work and signing it off. Work does not count until someone who did not build it says it works.', rule: 'signoff', cap: 5,
|
|
354
355
|
growth: 'When one queue of testers is not enough, an annex can take design reviews.', vacantGrowth: 'Nothing is built here until the charter says who holds the seat.', annex: 'room for an annex, such as design review',
|
|
355
|
-
floors: [['Testers', 'who signs off finished work'], ['Waiting outside', 'finished work waiting for a tester', { source: 'criteria_uat' }], ['Signed off', 'work a tester has signed off']],
|
|
356
|
+
floors: [['Testers', 'who signs off finished work', { floor: 'review.testers' }], ['Waiting outside', 'finished work waiting for a tester', { source: 'criteria_uat', held: 'Being tested' }], ['Signed off', 'work a tester has signed off', { floor: 'review.signed_off' }]],
|
|
356
357
|
},
|
|
357
358
|
release: {
|
|
358
359
|
holds: 'Putting finished work live, and taking updates to Bongos itself.', rule: 'decide', cap: 7,
|
|
359
360
|
growth: 'Room for a second gate if releases are ever split by area.', annex: 'room for a second gate',
|
|
360
|
-
floors: [['Overrides', 'past a failed check or grade', { source: 'override_requests' }], ['Modules', 'listed modules, and their re-checks', { source: 'module_rechecks' }], ['Held back', 'work held from going live'], ['Core update', 'a new version of Bongos', { source: 'core_update' }], ['Live this month', 'what went live'], ['Ready to go', 'signed off, waiting to go live', { source: 'gate_approvals' }]],
|
|
361
|
+
floors: [['Overrides', 'past a failed check or grade', { source: 'override_requests' }], ['Modules', 'listed modules, and their re-checks', { source: 'module_rechecks' }], ['Held back', 'work held from going live', { floor: 'release.held_back' }], ['Core update', 'a new version of Bongos', { source: 'core_update' }], ['Live this month', 'what went live', { floor: 'release.live_month' }], ['Ready to go', 'signed off, waiting to go live', { source: 'gate_approvals' }]],
|
|
361
362
|
},
|
|
362
363
|
treasury: {
|
|
363
364
|
holds: 'The budget, and who gets paid for what.', rule: 'majority', cap: 7,
|
|
364
365
|
growth: 'Each new spending line takes a floor.', annex: 'room for an annex of spending lines',
|
|
365
|
-
floors: [['Builder rewards', 'paid for shipped work'], ['AI grading', 'the reviewer that grades every ship'], ['Hosting', 'servers and storage'], ['Reserve', 'what is left this month', { reading: 'money' }], ['Spending matters', 'spending put to a vote', { source: 'treasury_spending' }]],
|
|
366
|
+
floors: [['Builder rewards', 'paid for shipped work', { floor: 'treasury.rewards' }], ['AI grading', 'the reviewer that grades every ship', { floor: 'treasury.grading' }], ['Hosting', 'servers and storage', { floor: 'treasury.hosting' }], ['Reserve', 'what is left this month', { reading: 'money' }], ['Spending matters', 'spending put to a vote', { source: 'treasury_spending' }]],
|
|
366
367
|
},
|
|
367
368
|
});
|
|
368
369
|
const MONTHS = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
|
|
@@ -396,6 +397,18 @@
|
|
|
396
397
|
const docket = ctx.docket, now = ctx.now;
|
|
397
398
|
if (!bind) return F(name, what, '', 'coming', [['What this floor holds is not recorded in Bongos yet.', 'coming']]);
|
|
398
399
|
if (!docket) return F(name, what, '', 'unavailable', [['The docket could not be read just now.', '']]);
|
|
400
|
+
if (bind.floor) {
|
|
401
|
+
const fl = (Array.isArray(docket.floors) ? docket.floors : []).find((x) => x && x.key === bind.floor);
|
|
402
|
+
if (fl && fl.status === 'live') {
|
|
403
|
+
const titles = new Map((ctx.goals || []).map((g) => [String(g.id), String(g.title || '')]));
|
|
404
|
+
// a line that names a goal by id ('goal 12') is lettered with its title
|
|
405
|
+
const word = (t) => { const m = /^goal (\d+)$/.exec(String(t)); return m && titles.get(m[1]) ? titles.get(m[1]) : String(t); };
|
|
406
|
+
const lines = (Array.isArray(fl.lines) ? fl.lines : []).map((l) => [word(l[0]), String(l[1] || '')]);
|
|
407
|
+
return F(name, String(fl.line || what), fl.count == null ? '' : String(fl.count), fl.hot ? 'pending' : 'ok', lines.length ? lines : [[String(fl.line || what), '']]);
|
|
408
|
+
}
|
|
409
|
+
if (fl) return quietFloor(name, what, fl.status, fl.reason);
|
|
410
|
+
if (!bind.goals && !bind.reading) return F(name, what, '', 'unavailable', [['This could not be read just now.', '']]);
|
|
411
|
+
}
|
|
399
412
|
if (bind.goals) {
|
|
400
413
|
const goals = ctx.goals || [];
|
|
401
414
|
const lines = goals.slice(0, MAX_LINES).map((g) => [String(g.title || 'Goal ' + g.id), 'open']);
|
|
@@ -423,36 +436,73 @@
|
|
|
423
436
|
return F(name, what, '', 'unavailable', [[String(reason || 'It could not be read just now.'), '']]);
|
|
424
437
|
}
|
|
425
438
|
|
|
426
|
-
// Who holds the building and how it decides.
|
|
427
|
-
//
|
|
428
|
-
//
|
|
429
|
-
//
|
|
430
|
-
|
|
439
|
+
// Who holds the building and how it decides. The building's decision row
|
|
440
|
+
// (task 1004529's table) is read first, with the people it seats now
|
|
441
|
+
// (`holders`); a vacant row is an empty seat that says so. A building with no
|
|
442
|
+
// row falls back to the legacy `holders` / `who` / `rule`, then its matters'
|
|
443
|
+
// own rule, then the design's default (and the words say 'by default', so a
|
|
444
|
+
// default is never read as a decision).
|
|
445
|
+
function holderOf(key, info, matters, viewerId) {
|
|
431
446
|
const plan = PLAN[key];
|
|
432
447
|
const holders = info && Array.isArray(info.holders) ? info.holders.filter((h) => h && (h.name || h.login)) : [];
|
|
433
|
-
const
|
|
434
|
-
const
|
|
448
|
+
const nameOf = (h) => String(h.name || h.login);
|
|
449
|
+
const seatOf = (h) => (viewerId != null && h.id != null && String(h.id) === String(viewerId) ? 'you' : nameOf(h).charAt(0).toUpperCase());
|
|
450
|
+
const d = info && info.decision && typeof info.decision === 'object' ? info.decision : null;
|
|
451
|
+
if (d) {
|
|
452
|
+
if (d.rule === 'vacant') return { seats: ['—'], who: 'no one holds it yet', rule: 'vacant', how: RULE_WORDS.vacant };
|
|
453
|
+
const rule = RULE_WORDS[d.rule] ? d.rule : plan.rule;
|
|
454
|
+
const n = info.holder_count == null ? holders.length : Number(info.holder_count) || 0;
|
|
455
|
+
const named = holders.slice(0, 3).map(nameOf);
|
|
456
|
+
const people = !n || !named.length ? '' : n <= 3 ? (named.length > 1 ? named.slice(0, -1).join(', ') + ' and ' + named[named.length - 1] : named[0]) : named.join(', ') + ' and ' + (n - 3) + ' more';
|
|
457
|
+
return {
|
|
458
|
+
seats: holders.length ? holders.slice(0, 5).map(seatOf) : ['—'],
|
|
459
|
+
who: people ? people + ' · ' + String(d.who || '') : String(d.who || 'who holds it is not set on this project yet'),
|
|
460
|
+
rule: rule,
|
|
461
|
+
how: RULE_WORDS[rule] + (RULE_WORDS[d.rule] ? '' : ' · by default'),
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
const seats = holders.length ? holders.slice(0, 5).map(seatOf) : ['—'];
|
|
465
|
+
const who = info && info.who ? String(info.who) : holders.length ? holders.map(nameOf).join(', ') : 'who holds it is not set on this project yet';
|
|
435
466
|
const fromMatter = matters.find((m) => m && RULE_WORDS[m.rule]);
|
|
436
467
|
const rule = info && RULE_WORDS[info.rule] ? info.rule : fromMatter ? fromMatter.rule : plan.rule;
|
|
437
468
|
const how = RULE_WORDS[rule] + (info && RULE_WORDS[info.rule] ? '' : fromMatter ? '' : ' · by default');
|
|
438
469
|
return { seats: seats, who: who, rule: rule, how: how };
|
|
439
470
|
}
|
|
440
471
|
|
|
441
|
-
// The record: the building's
|
|
442
|
-
|
|
472
|
+
// The record: the building's history (what closed, dated by when it closed)
|
|
473
|
+
// and its matters (dated by when each was opened), filed by month, newest
|
|
474
|
+
// drawer first. A settled sitting that is also in the history is filed once.
|
|
475
|
+
// A drawer shows MAX_DRAWER items and says how many more; its count is all.
|
|
476
|
+
const MAX_DRAWER = 12;
|
|
477
|
+
function recordOf(matters, now, history) {
|
|
443
478
|
const by = new Map();
|
|
444
|
-
|
|
445
|
-
const t =
|
|
479
|
+
const file = (at, item) => {
|
|
480
|
+
const t = at ? new Date(at) : null;
|
|
446
481
|
const ok = t && !isNaN(t.getTime());
|
|
447
482
|
const k = ok ? t.getUTCFullYear() * 12 + t.getUTCMonth() : -1;
|
|
448
483
|
if (!by.has(k)) by.set(k, []);
|
|
449
|
-
by.get(k).push(
|
|
484
|
+
by.get(k).push(item);
|
|
485
|
+
};
|
|
486
|
+
const filed = new Set();
|
|
487
|
+
(history || []).forEach((h) => {
|
|
488
|
+
const m = /:(?:board|amendment|founding):(.+)$/.exec(String(h.id || ''));
|
|
489
|
+
if (m) filed.add(m[1]);
|
|
490
|
+
});
|
|
491
|
+
(history || []).forEach((h) => file(h.at, [String(h.title || ''), String(h.outcome || '')]));
|
|
492
|
+
matters.forEach((m) => {
|
|
493
|
+
if (m.ref && m.ref.type === 'board_item' && filed.has(String(m.ref.id))) return;
|
|
494
|
+
file(m.opened_at, [String(m.title || m.short || ''), STATE_WORDS[stateOf(m)]]);
|
|
450
495
|
});
|
|
451
496
|
const thisYear = new Date(now || 0).getUTCFullYear();
|
|
452
|
-
return [...by.keys()].sort((a, b) => b - a).map((k) =>
|
|
453
|
-
|
|
454
|
-
items
|
|
455
|
-
|
|
497
|
+
return [...by.keys()].sort((a, b) => b - a).map((k) => {
|
|
498
|
+
const all = by.get(k);
|
|
499
|
+
const items = all.slice(0, MAX_DRAWER);
|
|
500
|
+
if (all.length > MAX_DRAWER) items.push(['and ' + (all.length - MAX_DRAWER) + ' more', '']);
|
|
501
|
+
return {
|
|
502
|
+
label: k < 0 ? 'undated' : MONTHS[k % 12] + (Math.floor(k / 12) === thisYear ? '' : ' ' + String(Math.floor(k / 12)).slice(2)),
|
|
503
|
+
items: items, count: all.length,
|
|
504
|
+
};
|
|
505
|
+
});
|
|
456
506
|
}
|
|
457
507
|
|
|
458
508
|
// The whole building as civicInfo() shaped it, from the real feeds.
|
|
@@ -464,21 +514,27 @@
|
|
|
464
514
|
const c = { key: key, docket: docket, goals: (ctx && ctx.goals) || [], now: (ctx && ctx.now) || 0 };
|
|
465
515
|
const matters = docket ? docket.matters.filter((m) => m && m.building === key) : [];
|
|
466
516
|
const info = docket && Array.isArray(docket.buildings) ? docket.buildings.find((b) => b && b.key === key) : null;
|
|
467
|
-
const h = holderOf(key, info, matters);
|
|
468
|
-
const vacant = key === 'review' && !S.reviewHeldOf(docket);
|
|
469
|
-
|
|
517
|
+
const h = holderOf(key, info, matters, docket && docket.viewer ? docket.viewer.id : null);
|
|
518
|
+
const vacant = key === 'review' && (!S.reviewHeldOf(docket) || !!(info && info.decision && info.decision.rule === 'vacant'));
|
|
519
|
+
// a held Review's waiting floor is the prototype's 'Being tested'
|
|
520
|
+
const floors = plan.floors.map((spec) => {
|
|
521
|
+
const fl = floorFor(spec, c);
|
|
522
|
+
if (!vacant && spec[2] && spec[2].held) fl.name = spec[2].held;
|
|
523
|
+
return fl;
|
|
524
|
+
});
|
|
470
525
|
const order = { needs: 0, moving: 1, settled: 2 };
|
|
471
526
|
const table = matters.slice().sort((a, b) => order[stateOf(a)] - order[stateOf(b)]).map((m) => {
|
|
472
527
|
const s = stateOf(m), when = S.whenWord(m.deadline, c.now);
|
|
473
528
|
return { key: String(m.id), matter: String(m.id), label: String(m.title || m.short || ''), status: s === 'needs' ? 'needs you' + (when ? ' · ' + when : '') : s === 'moving' ? 'moving' + (when ? ' · ' + when : '') : (when || 'settled'), state: s, color: s === 'needs' ? 'accent' : 'ink' };
|
|
474
529
|
});
|
|
475
|
-
const
|
|
530
|
+
const history = docket && Array.isArray(docket.history) ? docket.history.filter((x) => x && x.building === key) : [];
|
|
531
|
+
const record = recordOf(matters, c.now, history);
|
|
476
532
|
return {
|
|
477
533
|
key: key, group: GROUPS[key], name: info && info.name ? String(info.name) : key.charAt(0).toUpperCase() + key.slice(1), holds: plan.holds,
|
|
478
534
|
seats: h.seats, who: h.who, rule: h.rule, how: h.how, cap: plan.cap,
|
|
479
535
|
growth: vacant ? plan.vacantGrowth : plan.growth,
|
|
480
536
|
floors: floors, record: record,
|
|
481
|
-
onFile: !docket ? 'the docket could not be read just now' : matters.length ? matters.length + (matters.length === 1 ? ' matter' : ' matters') + ' on the docket' : 'nothing on file yet',
|
|
537
|
+
onFile: !docket ? 'the docket could not be read just now' : matters.length ? matters.length + (matters.length === 1 ? ' matter' : ' matters') + ' on the docket' + (history.length ? ' · ' + history.length + ' on the record' : '') : history.length ? history.length + ' on the record' : 'nothing on file yet',
|
|
482
538
|
table: table, annex: !vacant && floors.length >= plan.cap ? plan.annex : null, vacant: vacant,
|
|
483
539
|
};
|
|
484
540
|
}
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
#edecee, ink, ink-soft, ink-faint, the rules, the accent #fb9569 and its
|
|
10
10
|
dark ink #2a1305). The drawing's own faces are drawing data in
|
|
11
11
|
street-draw.js (the prototype's front-on palette).
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
In the hall's sidebar and top bar since task 1004544, behind the shell's
|
|
13
|
+
full-screen switch, as the city; the stage is the box the chrome leaves.
|
|
14
14
|
No :root here (tests/hall_reading_world.mjs): page tokens live on the body.
|
|
15
15
|
The two dark blocks below are byte-identical twins. */
|
|
16
16
|
|
|
@@ -51,8 +51,21 @@ html[data-theme="dark"] body[data-page="street"] {
|
|
|
51
51
|
|
|
52
52
|
html:has(body[data-page="street"]) { height: 100%; }
|
|
53
53
|
|
|
54
|
-
/*
|
|
55
|
-
|
|
54
|
+
/* THE STAGE IN THE HALL (task 1004544, the sky's mechanism from task 1004327).
|
|
55
|
+
<main class="street-stage"> is the box under the hall's top bar and beside
|
|
56
|
+
its 64px rail (the drawer's phone width has no rail) and, in full screen
|
|
57
|
+
(html[data-fullscreen="on"], shell.js), the whole window. `contain: layout
|
|
58
|
+
paint` makes it the containing block of the card and every positioned
|
|
59
|
+
layer, so the street measures from the stage instead of the window. */
|
|
60
|
+
.street-stage { position: fixed; top: var(--topbar-h, 56px); left: var(--sidebar-w-rail, 64px); right: 0; bottom: 0; max-width: none; contain: layout paint; margin: 0; padding: 0; display: flex; flex-direction: column; background: var(--bg); color: var(--ink); font-family: var(--font-body); overflow: hidden; }
|
|
61
|
+
html[data-fullscreen="on"] .street-stage { top: 0; left: 0; }
|
|
62
|
+
@media (max-width: 768px) { .street-stage { left: 0; } }
|
|
63
|
+
/* The full-screen switch floats top right over the stage, where the strip's
|
|
64
|
+
light/dark switch ends; the strip steps in by the button's width so nothing
|
|
65
|
+
sits under it. Under 640 the exit is the round button (style.css). */
|
|
66
|
+
html[data-fullscreen="on"] .street-top { padding-right: 184px; }
|
|
67
|
+
@media (max-width: 900px) { html[data-fullscreen="on"] .street-top { padding-right: 168px; } }
|
|
68
|
+
@media (max-width: 640px) { html[data-fullscreen="on"] .street-top { padding-right: 60px; } }
|
|
56
69
|
|
|
57
70
|
.street-label { font-size: 11.5px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-faint); white-space: nowrap; }
|
|
58
71
|
|
|
@@ -20,8 +20,16 @@
|
|
|
20
20
|
docket (GET /api/bongos/government/docket, task 1004523), the open goals
|
|
21
21
|
with their task rollups, and the viewer's earnings this month.
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
IN THE HALL (task 1004544, owner decision 2026-10-02), as the city: the
|
|
24
|
+
street sits in the hall's sidebar and top bar, the way the sky has since
|
|
25
|
+
task 1004327, and the shell's full-screen switch (data-fullscreen-switch)
|
|
26
|
+
hides both so the street fills the window again, remembered for this
|
|
27
|
+
page. The whole street lives inside <main class="street-stage">, the
|
|
28
|
+
stage under the top bar and beside the rail (street.css), so what is
|
|
29
|
+
drawn inside is unchanged. The prototype's header strip is a <div>, not a
|
|
30
|
+
<header>, and the street band is a <div>, not a <main>: the shell's top
|
|
31
|
+
bar is the page's one <header> and the stage its one <main>. It still has
|
|
32
|
+
no menu link of its own (owner: leave it off the sidebar): the way in is
|
|
25
33
|
the city's start governing, and the wordmark is the way back.
|
|
26
34
|
|
|
27
35
|
The markup is the prototype's: its header (the wordmark back to the city,
|
|
@@ -43,11 +51,17 @@
|
|
|
43
51
|
The page is gated on board.vote.cast (src/bongos/serve-internal.js), the
|
|
44
52
|
same atom the docket read checks, so whoever reaches the street can read
|
|
45
53
|
what it draws. -->
|
|
46
|
-
<body data-page="street" class="street">
|
|
54
|
+
<body data-page="street" data-page-title="The street" class="street" data-fullscreen-switch>
|
|
47
55
|
|
|
48
|
-
<div class="
|
|
56
|
+
<div class="app">
|
|
57
|
+
<aside class="app-sidebar" id="app-sidebar" aria-label="Primary"></aside>
|
|
58
|
+
<div class="app-scrim" id="app-scrim" hidden></div>
|
|
59
|
+
<div class="app-body">
|
|
60
|
+
<header class="app-topbar" id="app-topbar"></header>
|
|
49
61
|
|
|
50
|
-
<
|
|
62
|
+
<main id="hall-main" class="street-stage">
|
|
63
|
+
|
|
64
|
+
<div class="street-top">
|
|
51
65
|
<div class="street-top__brand">
|
|
52
66
|
<a class="street-mark" id="streetHome" href="/builders/city" aria-label="Back to the city overview">{{productName}}</a>
|
|
53
67
|
<h1 class="street-kicker">the street</h1>
|
|
@@ -71,9 +85,9 @@
|
|
|
71
85
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" aria-hidden="true"><path id="streetModeIcon" d=""></path></svg>
|
|
72
86
|
<span id="streetModeWord">light</span>
|
|
73
87
|
</button>
|
|
74
|
-
</
|
|
88
|
+
</div>
|
|
75
89
|
|
|
76
|
-
<
|
|
90
|
+
<div class="street-window" id="streetWindow">
|
|
77
91
|
<div class="street-sizer" id="streetSizer">
|
|
78
92
|
<div class="street-board" id="streetBoard" role="group" aria-label="The street">
|
|
79
93
|
<p class="street-hint" id="streetHint"></p>
|
|
@@ -165,7 +179,7 @@
|
|
|
165
179
|
</div>
|
|
166
180
|
</div>
|
|
167
181
|
</section>
|
|
168
|
-
</
|
|
182
|
+
</div>
|
|
169
183
|
|
|
170
184
|
<nav class="street-signs" id="streetSigns" aria-label="The six readings"></nav>
|
|
171
185
|
|
|
@@ -185,8 +199,12 @@
|
|
|
185
199
|
|
|
186
200
|
<div class="street-toast" id="streetToast" role="status" aria-live="polite" hidden></div>
|
|
187
201
|
|
|
202
|
+
</main>
|
|
203
|
+
</div>
|
|
188
204
|
</div>
|
|
189
205
|
|
|
206
|
+
<script src="/builders/shell.js?v=2026-10-02-theme"></script>
|
|
207
|
+
<script src="/builders/palette.js" defer></script>
|
|
190
208
|
<script src="/builders/city-draw.js?v=2026-10-02-street1"></script>
|
|
191
209
|
<script src="/builders/street-draw.js?v=2026-10-02-street1"></script>
|
|
192
210
|
<script src="/builders/street-card.js?v=2026-10-02-amend1"></script>
|
|
@@ -97,8 +97,21 @@
|
|
|
97
97
|
var next = effectiveTheme() === 'dark' ? 'light' : 'dark';
|
|
98
98
|
chosen = next;
|
|
99
99
|
try { window.localStorage.setItem(THEME_KEY, next); } catch (_) { /* private mode: this page only */ }
|
|
100
|
-
applyTheme();
|
|
100
|
+
painted = applyTheme();
|
|
101
101
|
draw();
|
|
102
|
+
var shell = window.OTBShell;
|
|
103
|
+
if (shell && typeof shell.applyTheme === 'function' && storedTheme() === painted) shell.applyTheme();
|
|
104
|
+
}
|
|
105
|
+
// IN THE HALL (task 1004544) the shell's top bar carries the hall's own theme
|
|
106
|
+
// switch beside this one, as on the city. Both write the same 'hall-theme'
|
|
107
|
+
// preference and html[data-theme]; each repaints when the other flips it.
|
|
108
|
+
var painted = null;
|
|
109
|
+
function watchShellTheme() {
|
|
110
|
+
if (typeof MutationObserver !== 'function') return;
|
|
111
|
+
new MutationObserver(function () {
|
|
112
|
+
var t = document.documentElement.getAttribute('data-theme');
|
|
113
|
+
if ((t === 'dark' || t === 'light') && t !== painted) { painted = t; chosen = t; paintSwitch(t); draw(); }
|
|
114
|
+
}).observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
|
|
102
115
|
}
|
|
103
116
|
function currentMode() {
|
|
104
117
|
var t = document.documentElement.getAttribute('data-theme');
|
|
@@ -915,8 +928,9 @@
|
|
|
915
928
|
var win = $('streetWindow');
|
|
916
929
|
if (win) win.addEventListener('scroll', placeCard);
|
|
917
930
|
if (media && media.addEventListener) media.addEventListener('change', function () { if (!storedTheme()) { applyTheme(); draw(); } });
|
|
918
|
-
applyTheme();
|
|
931
|
+
painted = applyTheme();
|
|
919
932
|
draw();
|
|
933
|
+
watchShellTheme();
|
|
920
934
|
return load();
|
|
921
935
|
}
|
|
922
936
|
|
|
@@ -39,7 +39,9 @@
|
|
|
39
39
|
"#streetBoard",
|
|
40
40
|
"#streetSigns",
|
|
41
41
|
"#streetMode",
|
|
42
|
-
"#streetHome"
|
|
42
|
+
"#streetHome",
|
|
43
|
+
"#app-topbar",
|
|
44
|
+
"#fullscreen-toggle"
|
|
43
45
|
],
|
|
44
46
|
"href": [
|
|
45
47
|
[
|
|
@@ -49,6 +51,20 @@
|
|
|
49
51
|
]
|
|
50
52
|
}
|
|
51
53
|
},
|
|
54
|
+
"fullscreen": {
|
|
55
|
+
"auth": true,
|
|
56
|
+
"_": "In the hall since task 1004544: the shell's full-screen switch pressed, the sidebar and the top bar gone, the street filling the window and the one exit in the corner (the sky's mechanism, task 1004327).",
|
|
57
|
+
"actions": [
|
|
58
|
+
["wait", 900],
|
|
59
|
+
["click", "#fullscreen-toggle"],
|
|
60
|
+
["wait", 500]
|
|
61
|
+
],
|
|
62
|
+
"expect": {
|
|
63
|
+
"visible": ["#streetBoard", "#streetSigns", "#streetMode", "#fullscreen-toggle"],
|
|
64
|
+
"hidden": ["#app-sidebar", "#topbar-title"],
|
|
65
|
+
"attr": [["#fullscreen-toggle", "aria-pressed", "true"]]
|
|
66
|
+
}
|
|
67
|
+
},
|
|
52
68
|
"card": {
|
|
53
69
|
"auth": true,
|
|
54
70
|
"_": "A pin pressed: the matter's short card over the street.",
|
|
@@ -176,7 +176,7 @@
|
|
|
176
176
|
|
|
177
177
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
178
178
|
|
|
179
|
-
<script src="/builders/shell.js?v=2026-10-
|
|
179
|
+
<script src="/builders/shell.js?v=2026-10-02-theme"></script>
|
|
180
180
|
<script src="/builders/palette.js" defer></script>
|
|
181
181
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
182
182
|
<!-- The kit, for its ONE renderer: the full-size ship-time visual figure
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
|
|
58
58
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
59
59
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
60
|
-
<script src="/builders/shell.js?v=2026-10-
|
|
60
|
+
<script src="/builders/shell.js?v=2026-10-02-theme"></script>
|
|
61
61
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
62
62
|
<script src="/builders/palette.js" defer></script>
|
|
63
63
|
<!-- shared frontend toolkit (window.OTB) — loaded FIRST so task.js can pull from it. -->
|
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
</div>
|
|
139
139
|
</div>
|
|
140
140
|
|
|
141
|
-
<script src="/builders/shell.js?v=2026-10-
|
|
141
|
+
<script src="/builders/shell.js?v=2026-10-02-theme"></script>
|
|
142
142
|
<script src="/builders/palette.js" defer></script>
|
|
143
143
|
|
|
144
144
|
<script src="/builders/idea-objects.js?v=2026-09-27-split"></script>
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
|
|
141
141
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
142
142
|
|
|
143
|
-
<script src="/builders/shell.js?v=2026-10-
|
|
143
|
+
<script src="/builders/shell.js?v=2026-10-02-theme"></script>
|
|
144
144
|
<script src="/builders/palette.js" defer></script>
|
|
145
145
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
146
146
|
<script src="/builders/brand-holes.js?v=2026-09-29-aqfix"></script>
|
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
|
|
163
163
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
164
164
|
chrome exists before the page script runs. -->
|
|
165
|
-
<script src="/builders/shell.js?v=2026-10-
|
|
165
|
+
<script src="/builders/shell.js?v=2026-10-02-theme"></script>
|
|
166
166
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
167
167
|
<script src="/builders/palette.js" defer></script>
|
|
168
168
|
<!-- shared frontend toolkit (window.OTB) — loaded before watch.js, which pulls from it. -->
|
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
|
|
172
172
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
173
173
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
174
|
-
<script src="/builders/shell.js?v=2026-10-
|
|
174
|
+
<script src="/builders/shell.js?v=2026-10-02-theme"></script>
|
|
175
175
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
176
176
|
<script src="/builders/palette.js" defer></script>
|
|
177
177
|
<!-- shared frontend toolkit (window.OTB) — loaded FIRST so work.js can pull from it. -->
|