@bongos/core 1.21.113 → 1.21.114

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 (260) hide show
  1. package/.bongos-core.json +603 -183
  2. package/.claude/skills/ask-for-help/SKILL.md +1 -1
  3. package/.claude/skills/bongos-feedback/SKILL.md +1 -1
  4. package/.claude/skills/builder-sequence/SKILL.md +1 -1
  5. package/.claude/skills/builder-ship/SKILL.md +1 -0
  6. package/GOVERNMENT.md +128 -11
  7. package/clients/bongos-client/README.md +1 -1
  8. package/clients/bongos-client/bongos-client.global.js +28 -0
  9. package/clients/bongos-client/index.cjs +28 -0
  10. package/clients/bongos-client/index.d.ts +45 -2
  11. package/clients/bongos-client/index.mjs +28 -0
  12. package/docs/adr/0372-an-ask-is-stored-per-docket-matter-and-the-server-refuses-slop.md +33 -0
  13. package/docs/adr/0373-an-ai-session-files-a-blocker-with-an-ask.md +31 -0
  14. package/docs/adr/0374-a-building-lead-is-a-held-position-appointed-by-a-people-matter.md +30 -0
  15. package/docs/adr/0375-who-may-build-without-a-vote-comes-from-the-form-of-government.md +38 -0
  16. package/docs/adr/0376-every-ask-is-routed-to-a-real-seat-on-the-server.md +30 -0
  17. package/docs/adr/0377-governance-is-a-held-position-read-on-me.md +24 -0
  18. package/docs/adr/0378-a-leads-build-outs-are-rows-built-at-once-or-by-a-passed-vote.md +33 -0
  19. package/docs/adr/0379-the-treasury-is-pots-per-goal-a-reserve-and-borrowing-over-real-spend.md +32 -0
  20. package/docs/adr/0380-a-strictness-preset-is-a-bundle-of-laws-switched-by-one-laws-vote.md +26 -0
  21. package/docs/adr/README.md +10 -1
  22. package/docs/api/openapi.json +989 -87
  23. package/docs/api-reference.md +18 -4
  24. package/docs/copy-inventory.md +343 -262
  25. package/docs/copy-registry.json +1223 -426
  26. package/docs/file-map.md +2 -1
  27. package/docs/module-api-changelog.md +2 -0
  28. package/docs/packs/engineer.md +2 -1
  29. package/docs/page-inventory.json +91 -6
  30. package/docs/page-readings.json +5017 -4458
  31. package/modules/builder-settings/law-locks.js +202 -0
  32. package/modules/builder-settings/routes/settings.js +66 -6
  33. package/modules/builder-settings/settings.js +7 -0
  34. package/modules/discord/ship-broadcast.js +22 -13
  35. package/modules/government/asks-db.js +54 -0
  36. package/modules/government/asks.js +269 -0
  37. package/modules/government/board-idea-reads.js +68 -0
  38. package/modules/government/board.js +21 -57
  39. package/modules/government/build-rights.js +148 -0
  40. package/modules/government/buildouts.js +160 -0
  41. package/modules/government/counts.js +81 -0
  42. package/modules/government/db.js +242 -0
  43. package/modules/government/decision-board.js +234 -8
  44. package/modules/government/decision-rules.js +2 -0
  45. package/modules/government/docket.js +114 -6
  46. package/modules/government/law-board.js +131 -0
  47. package/modules/government/laws.js +241 -0
  48. package/modules/government/leads.js +170 -0
  49. package/modules/government/migrations/government_027_laws.sql +143 -0
  50. package/modules/government/migrations/government_028_asks.sql +57 -0
  51. package/modules/government/migrations/government_029_leads.sql +135 -0
  52. package/modules/government/migrations/government_030_buildouts.sql +111 -0
  53. package/modules/government/migrations/government_031_treasury.sql +107 -0
  54. package/modules/government/module.json +2 -1
  55. package/modules/government/pollers/treasury-borrow.js +62 -0
  56. package/modules/government/positions.js +107 -0
  57. package/modules/government/presets.js +149 -0
  58. package/modules/government/routes/government.js +425 -2
  59. package/modules/government/routing.js +154 -0
  60. package/modules/government/treasury-db.js +166 -0
  61. package/modules/government/treasury.js +241 -0
  62. package/modules/hall-ui/public/atlas.html +2 -2
  63. package/modules/hall-ui/public/blockers.html +2 -2
  64. package/modules/hall-ui/public/board-room.html +2 -2
  65. package/modules/hall-ui/public/board-room.js +2 -0
  66. package/modules/hall-ui/public/city-draw.js +604 -11
  67. package/modules/hall-ui/public/city.css +157 -1
  68. package/modules/hall-ui/public/city.html +37 -7
  69. package/modules/hall-ui/public/city.js +441 -21
  70. package/modules/hall-ui/public/city.states.json +110 -5
  71. package/modules/hall-ui/public/collab.html +2 -2
  72. package/modules/hall-ui/public/commands.html +2 -2
  73. package/modules/hall-ui/public/copy-desk.html +2 -2
  74. package/modules/hall-ui/public/craft-mode.js +112 -29
  75. package/modules/hall-ui/public/deploy.html +2 -2
  76. package/modules/hall-ui/public/diagram-viewer.css +12 -0
  77. package/modules/hall-ui/public/diagram-viewer.js +4 -0
  78. package/modules/hall-ui/public/diagrams.html +2 -2
  79. package/modules/hall-ui/public/drachmae.html +2 -2
  80. package/modules/hall-ui/public/fleet.html +2 -2
  81. package/modules/hall-ui/public/gate.html +2 -2
  82. package/modules/hall-ui/public/goal-map.html +2 -2
  83. package/modules/hall-ui/public/goals.html +2 -2
  84. package/modules/hall-ui/public/gov-feeds.js +141 -0
  85. package/modules/hall-ui/public/gov-living.js +1002 -0
  86. package/modules/hall-ui/public/gov-positions.js +97 -0
  87. package/modules/hall-ui/public/government.html +2 -2
  88. package/modules/hall-ui/public/hall-render.js +3 -0
  89. package/modules/hall-ui/public/idea.html +2 -2
  90. package/modules/hall-ui/public/ideas.html +2 -2
  91. package/modules/hall-ui/public/index.html +3 -3
  92. package/modules/hall-ui/public/modules.html +2 -2
  93. package/modules/hall-ui/public/my-agents.css +334 -0
  94. package/modules/hall-ui/public/my-agents.html +72 -0
  95. package/modules/hall-ui/public/my-agents.js +603 -0
  96. package/modules/hall-ui/public/my-agents.states.json +37 -0
  97. package/modules/hall-ui/public/primer.html +2 -2
  98. package/modules/hall-ui/public/profile-connections.js +40 -28
  99. package/modules/hall-ui/public/profile.html +2 -2
  100. package/modules/hall-ui/public/project-settings.html +2 -2
  101. package/modules/hall-ui/public/ranks.html +2 -2
  102. package/modules/hall-ui/public/roadmap.html +2 -2
  103. package/modules/hall-ui/public/room.css +8 -0
  104. package/modules/hall-ui/public/roster.html +2 -2
  105. package/modules/hall-ui/public/sessions.html +2 -2
  106. package/modules/hall-ui/public/settings.html +2 -2
  107. package/modules/hall-ui/public/shell.js +116 -16
  108. package/modules/hall-ui/public/street-card.js +18 -1
  109. package/modules/hall-ui/public/street-deck.css +299 -0
  110. package/modules/hall-ui/public/street-deck.js +1145 -0
  111. package/modules/hall-ui/public/street-depth.js +712 -0
  112. package/modules/hall-ui/public/street-desk.js +5 -2
  113. package/modules/hall-ui/public/street-draw.js +109 -16
  114. package/modules/hall-ui/public/street-inside.js +137 -22
  115. package/modules/hall-ui/public/street-office.js +705 -0
  116. package/modules/hall-ui/public/street.css +161 -11
  117. package/modules/hall-ui/public/street.html +41 -11
  118. package/modules/hall-ui/public/street.js +78 -66
  119. package/modules/hall-ui/public/street.states.json +129 -21
  120. package/modules/hall-ui/public/studio.html +2 -2
  121. package/modules/hall-ui/public/style.css +5 -1
  122. package/modules/hall-ui/public/task.html +2 -2
  123. package/modules/hall-ui/public/thinking.html +2 -2
  124. package/modules/hall-ui/public/tweak-editor.html +2 -2
  125. package/modules/hall-ui/public/watch.html +2 -2
  126. package/modules/hall-ui/public/watch.js +14 -1
  127. package/modules/hall-ui/public/work.html +2 -2
  128. package/modules/hall-ui/records/living-city.md +57 -0
  129. package/modules/hall-ui/records/my-agents.md +21 -0
  130. package/modules/hall-ui/records/oversight-family.md +1 -0
  131. package/modules/hall-ui/records/profile-connections.md +10 -0
  132. package/modules/hall-ui/records/reading-rooms.md +2 -0
  133. package/modules/ideas/routes/blockers.js +92 -3
  134. package/modules/lifecycle/module.json +2 -1
  135. package/modules/lifecycle/moving.js +17 -1
  136. package/modules/lifecycle/routes/moving.js +6 -0
  137. package/modules/lifecycle/routes/project-settings.js +23 -0
  138. package/modules/onboarding/approval-broadcast.js +8 -0
  139. package/modules/onboarding/routes/access-requests.js +16 -6
  140. package/modules/platform-identity/connections.js +13 -2
  141. package/modules/platform-identity/routes/connections.js +7 -1
  142. package/modules/public-landing/public/account-private.states.json +2 -2
  143. package/modules/public-landing/public/account.html +65 -5
  144. package/modules/public-landing/public/account.states.json +3 -3
  145. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.jpg.json +1 -1
  146. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp +0 -0
  147. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp.json +13 -0
  148. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.jpg.json +1 -1
  149. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp +0 -0
  150. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp.json +13 -0
  151. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.jpg.json +1 -1
  152. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp +0 -0
  153. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp.json +13 -0
  154. package/modules/public-landing/public/assets/world.css +4 -4
  155. package/modules/public-landing/public/connections-empty.states.json +14 -0
  156. package/modules/public-landing/public/connections-inactive.states.json +14 -0
  157. package/modules/public-landing/public/connections.html +450 -0
  158. package/modules/public-landing/public/connections.probes.json +414 -0
  159. package/modules/public-landing/public/connections.states.json +17 -0
  160. package/modules/public-landing/public/contact.html +2 -2
  161. package/modules/public-landing/public/guild.html +2 -2
  162. package/modules/public-landing/public/index.html +2 -2
  163. package/modules/public-landing/public/invitations.html +2 -2
  164. package/modules/public-landing/public/privacy.html +2 -2
  165. package/modules/public-landing/public/projects.html +2 -2
  166. package/modules/public-landing/public/projects.probes.json +6 -2
  167. package/modules/public-landing/public/terms.html +2 -2
  168. package/modules/ui-design/kit/fixtures/connections-empty.json +12 -0
  169. package/modules/ui-design/kit/fixtures/connections-inactive.json +7 -0
  170. package/modules/ui-design/kit/fixtures/connections.json +22 -0
  171. package/modules/ui-design/kit/serve.js +95 -0
  172. package/package-lock.json +2 -2
  173. package/package.json +1 -1
  174. package/release-notes.json +122 -0
  175. package/scripts/discord/README.md +8 -5
  176. package/scripts/discord/post.js +12 -1
  177. package/scripts/gds/artifact-audit.js +20 -3
  178. package/scripts/gds/ask.js +164 -0
  179. package/scripts/gds/autobongos-verify.js +21 -2
  180. package/scripts/gds/government-walk.js +638 -4
  181. package/scripts/gds/pulse.js +9 -6
  182. package/scripts/gds/ship-escalation.js +22 -3
  183. package/scripts/gds/ship-finish.js +5 -4
  184. package/scripts/gds/ship-merge-gate.js +18 -6
  185. package/scripts/hall-preview/README.md +41 -1
  186. package/scripts/hall-preview/pref-writes.js +140 -0
  187. package/scripts/hall-preview/refresh-fixtures.js +101 -7
  188. package/scripts/hall-preview/scrub-identity.js +366 -7
  189. package/scripts/hall-preview/server.js +27 -1
  190. package/scripts/render-diagrams.sh +4 -2
  191. package/src/bongos/auth-idp.js +6 -1
  192. package/src/bongos/project-settings.js +41 -0
  193. package/src/bongos/route-rank-check.js +27 -0
  194. package/src/bongos/routes/me.js +68 -7
  195. package/src/bongos/serve-internal.js +5 -1
  196. package/src/module-api.js +7 -1
  197. package/src/platform-server.js +10 -0
  198. package/tests/application_lifecycle.mjs +47 -5
  199. package/tests/conductor_main_e2e.mjs +21 -0
  200. package/tests/connections_api.mjs +18 -0
  201. package/tests/craft_mode_nav.mjs +5 -3
  202. package/tests/craft_mode_tallies.mjs +52 -16
  203. package/tests/discord_approvals.mjs +11 -0
  204. package/tests/discord_channels.mjs +20 -0
  205. package/tests/fixtures/gov-seated.mjs +64 -0
  206. package/tests/fixtures/government-ask-cards.json +1284 -0
  207. package/tests/government_act_alone_buildings.mjs +1 -0
  208. package/tests/government_agent_asks.mjs +322 -0
  209. package/tests/government_asks.mjs +408 -0
  210. package/tests/government_build_rights.mjs +253 -0
  211. package/tests/government_buildouts.mjs +382 -0
  212. package/tests/government_counts.mjs +143 -0
  213. package/tests/government_decision_rules.mjs +5 -1
  214. package/tests/government_laws.mjs +488 -0
  215. package/tests/government_laws_session.mjs +321 -0
  216. package/tests/government_leads.mjs +375 -0
  217. package/tests/government_positions.mjs +127 -0
  218. package/tests/government_presets.mjs +215 -0
  219. package/tests/government_routes.mjs +8 -1
  220. package/tests/government_routing.mjs +118 -0
  221. package/tests/government_seed.mjs +5 -0
  222. package/tests/government_treasury.mjs +394 -0
  223. package/tests/governor_docket.mjs +26 -2
  224. package/tests/hall_buildouts.mjs +208 -0
  225. package/tests/hall_city.mjs +14 -6
  226. package/tests/hall_city_living.mjs +617 -0
  227. package/tests/hall_gov_counts.mjs +336 -0
  228. package/tests/hall_gov_living.mjs +636 -0
  229. package/tests/hall_gov_real_site.mjs +655 -0
  230. package/tests/hall_matter_card.mjs +6 -1
  231. package/tests/hall_my_agents.mjs +481 -0
  232. package/tests/hall_nav.mjs +107 -4
  233. package/tests/hall_page_gate_map.mjs +4 -0
  234. package/tests/hall_preview_governor_fixtures.mjs +30 -3
  235. package/tests/hall_preview_scrub.mjs +59 -1
  236. package/tests/hall_preview_security.mjs +169 -0
  237. package/tests/hall_reading_world.mjs +44 -0
  238. package/tests/hall_roster_world.mjs +6 -2
  239. package/tests/hall_street.mjs +18 -2
  240. package/tests/hall_street_amend.mjs +1 -1
  241. package/tests/hall_street_deck.mjs +609 -0
  242. package/tests/hall_street_depth.mjs +559 -0
  243. package/tests/hall_street_desk.mjs +1 -1
  244. package/tests/hall_street_inside.mjs +29 -5
  245. package/tests/hall_tweak_editor.mjs +4 -1
  246. package/tests/hub_account_page.mjs +94 -3
  247. package/tests/hub_connections_page.mjs +507 -0
  248. package/tests/hub_invite_badge.mjs +1 -1
  249. package/tests/idp_device_exchange.mjs +18 -0
  250. package/tests/landing_legal_pages.mjs +8 -5
  251. package/tests/landing_page.mjs +23 -4
  252. package/tests/nav_permission_atoms.mjs +4 -1
  253. package/tests/profile_connections_ui.mjs +88 -25
  254. package/tests/projects_hub.mjs +15 -0
  255. package/tests/render_check.mjs +2 -2
  256. package/tests/ship_broadcast_routing.mjs +57 -16
  257. package/tests/ship_broadcast_server.mjs +40 -7
  258. package/tests/ship_merge_gate.mjs +19 -0
  259. package/tests/ui_design_kit.mjs +2 -2
  260. package/tests/watch_applications_queue.mjs +23 -0
@@ -0,0 +1,705 @@
1
+ // modules/hall-ui/public/street-office.js — THE GOVERNMENT INSIDE EACH BUILDING:
2
+ // the Lead's office, build-outs, the law book, the treasury and how strict the
3
+ // agents run (task 1004685, the living-city prototype; V3 of four builders).
4
+ //
5
+ // WHAT THIS IS. The interiors (street-inside.js, street.js) already draw each
6
+ // building in section with a desk on every floor. This file adds the layer the
7
+ // thesis is about — government is how a project steers its AI — over the living
8
+ // model (gov-living.js). ALL OF IT IS REAL (task 1004717): the prototype overlay
9
+ // and this file's own sample asks are gone; what no table holds is not drawn.
10
+ //
11
+ // * THE LEAD'S OFFICE, the top storey of every building (GET /government/leads,
12
+ // tasks 1004709-1004711): who leads it (or that the owner holds it), whether
13
+ // it builds without a vote (the SERVER's answer from the form of government,
14
+ // build_rights; never the charter, which is words), its committee rooms, and
15
+ // BUILD OUT for the building's holder: a committee room, a new floor, an
16
+ // annex, or growing into a district. A tap is POST /government/buildouts
17
+ // (built at once, or filed as a vote, ADR 0378); a build-out's vote card
18
+ // carries the picture drawn from the real building as it stands (isoPicture
19
+ // with its structure, GovCity's iso kit, read-only), so the board votes on a
20
+ // picture.
21
+ // * THE LAW BOOK in Rules: each law in force (GET /government/laws), who passed
22
+ // it, when, and what every agent does because of it; and what every agent
23
+ // reads at the start of a session: the interaction-profile contract the
24
+ // session is injected with (/me), and the laws.
25
+ // * THE TREASURY (GET /government/treasury, task 1004712): the spend this month
26
+ // and last, the pots per goal, the cap (off by default: no limit), and the dry
27
+ // pot's loan from the reserve as its real ask card.
28
+ // * STRICTNESS in the Charter (GET /government/presets, task 1004715): the
29
+ // preset the laws in force amount to, and a switch that files ONE real laws
30
+ // vote (POST /government/board/presets); only its passing changes a law.
31
+ //
32
+ // TWO HALVES, as street-deck.js: the model half is pure and node-require-safe
33
+ // (tests/hall_street_deck.mjs), the page half mounts into street.js.
34
+
35
+ (function (root) {
36
+ 'use strict';
37
+ const req = typeof require === 'function' ? require : null;
38
+ const L = root.GovLiving || (req ? req('./gov-living.js') : null);
39
+ const City = root.GovCity || (req ? req('./city-draw.js') : null);
40
+ const D = root.GovDesk || (req ? req('./street-desk.js') : null);
41
+ const str = (v) => (v == null ? '' : String(v));
42
+ const fit = (s, n, w) => (D && D.fit ? D.fit(s, n, w) : str(s).slice(0, n));
43
+ const NAMES = L ? L.BUILDING_NAMES : {};
44
+ const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
45
+ function dayWords(iso) {
46
+ const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(str(iso));
47
+ return m ? Number(m[3]) + ' ' + MONTHS[Number(m[2]) - 1] : '';
48
+ }
49
+ function usd(n) {
50
+ if (n == null || !isFinite(Number(n))) return '';
51
+ return '$' + Math.round(Number(n)).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
52
+ }
53
+ // a goal's name without its working-area prefix: 'Working area 1 — Project creation' → 'Project creation'
54
+ function shortTitle(t) { const m = /^.+?\s+[—–-]\s+(.+)$/.exec(str(t)); return m ? m[1] : str(t); }
55
+ function buildingOf(living, key) { return living && Array.isArray(living.buildings) ? living.buildings.find((b) => b && b.key === key) || null : null; }
56
+ // the page's own state this visit: the presets it filed a vote for
57
+ function blank() { return { presetFiled: {} }; }
58
+
59
+ // ---------- build-outs ----------
60
+ // A capsule plugs into the civic core; four sizes, the last a part of the city.
61
+ const CAPSULES = Object.freeze([
62
+ { key: 'committee', chip: 'committee room', what: 'a committee room', made: 'a committee room' },
63
+ { key: 'floor', chip: 'new floor', what: 'a new floor', made: 'a new floor' },
64
+ { key: 'annex', chip: 'annex', what: 'an annex', made: 'an annex' },
65
+ { key: 'district', chip: 'grow a district', what: 'a district of its own', made: 'a district of its own' },
66
+ ]);
67
+ const capsuleOf = (k) => CAPSULES.find((c) => c.key === k) || null;
68
+
69
+ // The Lead's office, as the drawing and the desk read it; null when the Leads
70
+ // were not read (no office is invented).
71
+ function officeOf(living, key) {
72
+ const b = buildingOf(living, key);
73
+ // GET /government/leads was read: who holds it, what it may build without a
74
+ // vote and what it has grown are the server's (tasks 1004709-1004711)
75
+ if (!b || !living.seats_real) return null;
76
+ const lead = b.lead || null;
77
+ const owner = living.owner ? living.owner.name : 'the owner';
78
+ const rooms = (b.structure ? b.structure.committees : []).map((c) => ({
79
+ name: c.name, line: c.state === 'building' ? 'going up' + (c.line ? ' · ' + c.line : '') : c.line, proposed: false,
80
+ }));
81
+ // a committee room waiting on its vote (its sitting is open)
82
+ (b.construction || []).filter((c) => c.capsule === 'committee' && !c.authorised && c.ask_id).forEach((c) => rooms.push({ name: c.label, line: 'proposed · a vote is open', proposed: true }));
83
+ // the charter is words; it grants nothing
84
+ const charter = b.charter ? { scope: str(b.charter.scope) } : null;
85
+ return {
86
+ key: key, name: b.name, seated: !!lead,
87
+ lead: lead ? { id: str(lead.id), name: str(lead.name), since: dayWords(lead.since) } : null,
88
+ owner: owner,
89
+ line: lead ? lead.name + ' · Lead since ' + dayWords(lead.since) : 'no Lead · ' + owner + ' holds it',
90
+ charter: charter,
91
+ // what may be built here without a vote: the server's answer from the form of
92
+ // government (GET /government/leads build_rights, task 1004710), never the charter
93
+ rights: b.build_rights || null,
94
+ rooms: rooms,
95
+ // who may ask for a build-out here (GET /government/leads answers), else null
96
+ holders: b.answers && Array.isArray(b.answers.ids) ? b.answers.ids.map(str) : null,
97
+ };
98
+ }
99
+ // What stands or goes up at the building: its build-outs going up or waiting on
100
+ // a vote, the open sittings the model drew as scaffolds, and the annexes and
101
+ // districts that stand built. [{ kind, state, label, line, progress, id, ask }]
102
+ function worksOf(living, key) {
103
+ const b = buildingOf(living, key);
104
+ if (!b) return [];
105
+ const out = [];
106
+ (b.construction || []).forEach((c) => {
107
+ // a build-out (task 1004711): its committee rooms are the office's rows;
108
+ // a floor goes up as a site at its real progress, an annex or district on the lot
109
+ if (c.capsule) {
110
+ if (c.capsule === 'committee') return;
111
+ const lot = c.capsule === 'annex' || c.capsule === 'district';
112
+ const pct = c.progress != null ? Math.round(c.progress * 100) + '% built' : 'going up';
113
+ if (c.authorised) out.push({ kind: lot ? c.capsule : 'floor', state: lot ? 'proposed' : 'site', label: c.label, line: lot ? 'going up · ' + pct : pct, progress: c.progress, id: c.id });
114
+ else out.push({ kind: lot ? c.capsule : 'floor', state: 'proposed', label: c.label, line: 'proposed · a vote is open', id: c.id, ask: c.ask_id });
115
+ return;
116
+ }
117
+ // an open sitting at the building (a board vote, or its way of deciding)
118
+ out.push({ kind: 'floor', state: 'proposed', label: c.label, line: 'a sitting is open', id: c.id });
119
+ });
120
+ // an annex or district that stands built (task 1004711)
121
+ ((b.structure && b.structure.built) || []).filter((x) => x.capsule === 'annex' || x.capsule === 'district')
122
+ .forEach((x) => out.push({ kind: x.capsule, state: 'built', label: x.name, line: 'built', id: x.id }));
123
+ return out;
124
+ }
125
+ // The floors a build-out put up and that stand built (task 1004711), appended
126
+ // to the building's own.
127
+ function builtFloors(key, living) {
128
+ const b = living ? buildingOf(living, key) : null;
129
+ return ((b && b.structure && b.structure.built) || []).filter((x) => x.capsule === 'floor').map((x) => ({ name: str(x.name), line: str(x.line) || 'built by a build-out' }));
130
+ }
131
+ // May this be built without a vote? The server answers (build_rights); this
132
+ // only reads it, and an answer it cannot read is a vote.
133
+ function buildPlan(living, key, capsule) {
134
+ const off = officeOf(living, key);
135
+ if (!off || !capsuleOf(capsule)) return { authorised: false, why: 'This cannot be built from here.' };
136
+ const r = off.rights && off.rights.capsules ? off.rights.capsules[capsule] : null;
137
+ if (!r) return { authorised: false, why: 'Who may build here is not known, so the board votes on it.' };
138
+ return { authorised: r.without_vote === true, why: str(r.why) || (r.without_vote ? 'It may be built at once.' : 'It goes to a vote.'), ground: str(r.ground) };
139
+ }
140
+ // The office's one-line answer: does its holder build without a vote?
141
+ function buildsAlone(off) {
142
+ const r = off && off.rights && off.rights.capsules ? off.rights.capsules.floor : null;
143
+ if (!r || !r.without_vote) return { yes: false, fact: 'no · it goes to a vote' };
144
+ if (r.ground === 'form') return { yes: true, fact: 'yes, under ' + (off.rights.form_name || 'its form of government') };
145
+ if (r.ground === 'goal') return { yes: true, fact: 'yes, by a goal' };
146
+ return { yes: true, fact: 'yes, by how this building decides' };
147
+ }
148
+ // ---------- the picture the board votes on ----------
149
+ // The building as the city draws it (GovCity.isoInstitution), with the change
150
+ // as a dashed-glass ghost. Pure: paths and a viewBox fitted to them.
151
+ // FROM THE REAL BUILDING (task 1004711): handed the building's `structure` from
152
+ // the living model (GET /government/leads), the picture first draws what the
153
+ // building really has — each floor that stands built stacked solid on its roof,
154
+ // each committee room a solid capsule above them, a built annex or district
155
+ // solid beside it — and draws the change dashed on top of THAT, so the board
156
+ // votes on the building as it stands. `stands` says what was drawn solid.
157
+ const ROOF = Object.freeze({
158
+ charter: [0.75, 0.75, 1.25, 1.25, 144], rules: [0.22, 0.62, 1.78, 1.38, 36], treasury: [0.62, 0.62, 1.38, 1.38, 66],
159
+ planning: [0.3, 0.3, 1.7, 1.7, 51], review: [0.38, 0.38, 1.62, 1.62, 53], release: [0.3, 0.75, 0.75, 1.25, 66],
160
+ people: [1.1, 0.3, 1.75, 0.9, 31], disputes: [0.75, 0.55, 1.25, 1.45, 60], automation: [1.0, 0.4, 1.65, 0.9, 36],
161
+ });
162
+ const ANNEX_AT = [[2.0, 0.85, 2.35, 1.15, 10, 5], [2.35, 0.3, 3.55, 1.7, 0, 46]];
163
+ const DISTRICT_AT = [[2.4, 0.2, 40], [3.3, 0.6, 62], [2.6, 1.3, 30], [3.5, 1.6, 48]];
164
+ function isoPicture(building, change, mode, structure) {
165
+ if (!City || !City.isoKit) return null;
166
+ const I = City.isoTheme(mode === 'light' ? 'light' : 'dark');
167
+ const k = City.isoKit(56, 28, 0, 0, 1, I, false);
168
+ const out = [];
169
+ const ghost = Object.assign({}, I.glass, { dash: '4 3' });
170
+ City.isoBox(out, k, -0.2, -0.2, 2.2, 2.2, 0, 0.5, I.plinth);
171
+ City.isoInstitution(out, k, building, 0, 0, {});
172
+ const r = ROOF[building] || [0.4, 0.4, 1.6, 1.6, 40];
173
+ const cx = (r[0] + r[2]) / 2, cy = (r[1] + r[3]) / 2;
174
+ // what stands: the real building's own build-outs, solid
175
+ const s = structure || {};
176
+ const built = Array.isArray(s.built) ? s.built : [];
177
+ const rooms = (Array.isArray(s.committees) ? s.committees : []).filter((c) => c && c.real);
178
+ const floorsBuilt = built.filter((x) => x && x.capsule === 'floor').length;
179
+ const annexBuilt = built.some((x) => x && x.capsule === 'annex');
180
+ const districtBuilt = built.some((x) => x && x.capsule === 'district');
181
+ let z = r[4];
182
+ for (let i = 0; i < floorsBuilt; i++) { City.isoBox(out, k, r[0], r[1], r[2], r[3], z, 14, I.civic, { rows: true }); z += 14; }
183
+ rooms.forEach(() => { City.isoBox(out, k, cx - 0.22, cy - 0.22, cx + 0.22, cy + 0.22, z, 12, I.civic); z += 12; });
184
+ // a second annex stands further out than the first
185
+ const shift = (dx) => (t) => [t[0] + dx, t[1], t[2] + dx, t[3], t[4], t[5]];
186
+ if (annexBuilt) ANNEX_AT.forEach((t) => City.isoBox(out, k, t[0], t[1], t[2], t[3], t[4], t[5], I.civic, { rows: true }));
187
+ if (districtBuilt) DISTRICT_AT.forEach((t) => City.isoBox(out, k, t[0] + (annexBuilt ? 1.6 : 0), t[1], t[0] + (annexBuilt ? 1.6 : 0) + 0.7, t[1] + 0.7, 0, t[2], I.civic, { rows: true }));
188
+ // the change, dashed, on top of what stands
189
+ if (change === 'floor') City.isoBox(out, k, r[0], r[1], r[2], r[3], z, 14, ghost, { rows: true });
190
+ else if (change === 'committee') City.isoBox(out, k, cx - 0.22, cy - 0.22, cx + 0.22, cy + 0.22, z, 12, ghost);
191
+ else if (change === 'annex') {
192
+ const at = ANNEX_AT.map(shift(annexBuilt ? 1.4 : 0));
193
+ City.isoBox(out, k, at[0][0], at[0][1], at[0][2], at[0][3], at[0][4], at[0][5], ghost);
194
+ City.isoBox(out, k, at[1][0], at[1][1], at[1][2], at[1][3], at[1][4], at[1][5], ghost, { rows: true, door: true });
195
+ } else if (change === 'district') {
196
+ const dx = (annexBuilt ? 1.6 : 0) + (districtBuilt ? 1.8 : 0);
197
+ DISTRICT_AT.forEach((t) => City.isoBox(out, k, t[0] + dx, t[1], t[0] + dx + 0.7, t[1] + 0.7, 0, t[2], ghost, { rows: true }));
198
+ }
199
+ // fit the view to what was drawn
200
+ let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
201
+ out.forEach((p) => {
202
+ const re = /[ML]\s*(-?[\d.]+)\s+(-?[\d.]+)/g;
203
+ let m;
204
+ while ((m = re.exec(p.d))) { const x = Number(m[1]), y = Number(m[2]); if (x < x0) x0 = x; if (x > x1) x1 = x; if (y < y0) y0 = y; if (y > y1) y1 = y; }
205
+ });
206
+ const pad = 10;
207
+ const vb = [Math.floor(x0 - pad), Math.floor(y0 - pad - 8), Math.ceil(x1 - x0 + pad * 2), Math.ceil(y1 - y0 + pad * 2 + 8)];
208
+ return {
209
+ viewBox: vb.join(' '), w: vb[2], h: vb[3], paths: out, change: change, building: building,
210
+ stands: { floors: floorsBuilt, rooms: rooms.length, annex: annexBuilt, district: districtBuilt },
211
+ };
212
+ }
213
+
214
+ // ---------- the law book ----------
215
+ // Every law in force (GET /government/laws, task 1004706), and the one standing
216
+ // rule the docket says the project runs. A law is passed by a vote (My agents'
217
+ // "make it a law" files one); this book only reads them.
218
+ function lawBook(living) {
219
+ const laws = living && living.city ? living.city.laws : [];
220
+ const rows = laws.map((l) => ({
221
+ id: l.id, text: l.text,
222
+ by: l.origin === 'docket' ? 'the docket' : l.passed_by ? str(l.passed_by.name) : 'a vote',
223
+ when: l.origin === 'docket' ? 'standing' : dayWords(l.passed_at),
224
+ ai: str(l.effect_on_ai), locks: (l.locks || []).map((x) => str(x.setting).replace(/_/g, ' ')).join(', '),
225
+ }));
226
+ return { rows: rows, n: rows.length, preset: living && living.strictness ? living.strictness.name : '' };
227
+ }
228
+ // What every agent reads at the start of a session: the interaction contract
229
+ // the session is injected with (/me), and the laws in force (task 1004707).
230
+ function injection(living, me) {
231
+ const book = lawBook(living);
232
+ const contract = me && me.interaction && me.interaction.contract ? str(me.interaction.contract) : '';
233
+ const lines = contract.split('\n').map((x) => x.replace(/^-\s*/, '').trim()).filter(Boolean);
234
+ return {
235
+ laws: book.rows.map((r, i) => ({ n: i + 1, text: r.text, ai: r.ai })),
236
+ head: lines[0] || '', profile: lines.slice(1), real: !!contract,
237
+ };
238
+ }
239
+
240
+ // ---------- the treasury ----------
241
+ // GET /government/treasury (task 1004712: spend.cap is there once read): the
242
+ // pots, the reserve and the cap are the server's, and a loan is answered on the
243
+ // dry pot's real ask. Unread, there is no treasury book.
244
+ function treasury(living) {
245
+ if (!living || !living.city.spend.cap) return null;
246
+ const sp = living.city.spend;
247
+ const tiles = [];
248
+ if (sp.month_usd) tiles.push({ v: usd(sp.month_usd), l: 'this month' });
249
+ if (sp.last_month_usd) tiles.push({ v: usd(sp.last_month_usd), l: 'last month' });
250
+ if (sp.reserve_usd != null) tiles.push({ v: usd(Math.abs(sp.reserve_usd)), l: sp.reserve_usd < 0 ? 'overdrawn' : 'in reserve' });
251
+ const pots = living.districts.filter((d) => d.pot).map((d) => {
252
+ const extra = Number(d.pot.lent_usd) || 0;
253
+ const budget = d.pot.budget_usd + extra;
254
+ return { goal: d.goal_id, title: d.title, spent: d.pot.spent_usd, budget: budget, lent: extra, pct: budget > 0 ? Math.min(100, Math.round((d.pot.spent_usd / budget) * 100)) : 100, dry: d.pot.spent_usd >= budget, ask: d.pot.borrow_ask_id, you: !!(d.pot.borrow_ask_id && (living.city.asks.find((a) => a.id === d.pot.borrow_ask_id) || {}).needs_you) };
255
+ }).sort((a, b) => (b.dry - a.dry) || b.spent - a.spent);
256
+ return {
257
+ tiles: tiles, pots: pots, shown: pots.slice(0, 6), more: Math.max(0, pots.length - 6),
258
+ cap: { on: !!sp.cap.on, line: sp.cap.on ? 'on · ' + usd(sp.cap.usd) + ' a month' : 'off · no spend limit, the default' },
259
+ dryAsk: pots.filter((p) => p.dry && p.ask).map((p) => p.ask)[0] || null, basis: sp.pots_basis,
260
+ };
261
+ }
262
+
263
+ // ---------- how strict ----------
264
+ // GET /government/presets (task 1004715): the preset is the one the laws in
265
+ // force amount to (none: "No preset yet"), each carries the locks a switch to
266
+ // it leaves, and a switch is a real laws vote (switchPreset). Unread: no book.
267
+ function strictness(living, os) {
268
+ const s = os || blank();
269
+ if (!living || !living.strictness || !living.strictness.presets.length) return null;
270
+ const cur = living.strictness.key;
271
+ const p = living.strictness.presets.find((x) => x.key === cur) || living.strictness;
272
+ return {
273
+ current: cur, name: str(p.name), line: str(p.line),
274
+ presets: living.strictness.presets.map((x) => ({ key: x.key, name: x.name, on: x.key === cur, filed: !!s.presetFiled[x.key] })),
275
+ locked: cur ? (p.locks || []).length : 0,
276
+ };
277
+ }
278
+
279
+ const model = {
280
+ CAPSULES: CAPSULES, ROOF: ROOF, blank: blank,
281
+ officeOf: officeOf, worksOf: worksOf, builtFloors: builtFloors, buildPlan: buildPlan, buildsAlone: buildsAlone,
282
+ isoPicture: isoPicture, lawBook: lawBook, injection: injection,
283
+ treasury: treasury, strictness: strictness, usd: usd, dayWords: dayWords,
284
+ };
285
+
286
+ // =====================================================================
287
+ // THE PAGE HALF. street.js hands the same env as the deck's, plus
288
+ // pickFloor(i). input(key) feeds the drawing; inside(bv, on, govt) draws the
289
+ // government layer and returns true when it filled the floor block itself
290
+ // (the office, or a floor the Lead just built).
291
+ // =====================================================================
292
+ const page = (function () {
293
+ let env = null, doc = null;
294
+ const os = blank();
295
+ const deckPage = () => (root.GovDeck ? root.GovDeck.page : null);
296
+ const living = () => { const p = deckPage(); return p ? p.living() : null; };
297
+ const viewerId = () => { const d = env ? env.data() : null; return d && d.docket && d.docket.viewer ? str(d.docket.viewer.id) : null; };
298
+
299
+ function input(key) {
300
+ const lv = living();
301
+ const off = lv ? officeOf(lv, key) : null;
302
+ if (!off) return {};
303
+ const extra = builtFloors(key, lv);
304
+ const b = buildingOf(lv, key);
305
+ return {
306
+ office: { seated: off.seated, line: off.line, ini: '', rooms: off.rooms },
307
+ works: worksOf(lv, key), floors: extra,
308
+ // a floor a build-out put up lights with a tick, as a cleared one does
309
+ cleared: extra.map((x, i) => (b ? (root.GovInside && root.GovInside.PLAN[key] ? root.GovInside.PLAN[key].floors.length : 0) + i : -1)).filter((i) => i >= 0),
310
+ };
311
+ }
312
+
313
+ // ---------- small DOM ----------
314
+ function h(tag, cls, text) { const el = doc.createElement(tag); if (cls) el.className = cls; if (text != null) el.textContent = String(text); return el; }
315
+ function clear(el) { while (el && el.firstChild) el.removeChild(el.firstChild); }
316
+ function chip(cls, text, on) { const b = h('button', 'desk-chip ' + cls, text); b.setAttribute('type', 'button'); b.addEventListener('click', on); return b; }
317
+ function head(label, extra) {
318
+ const hd = h('div', 'govx-head');
319
+ hd.appendChild(h('span', 'street-label', label));
320
+ if (extra) hd.appendChild(extra);
321
+ return hd;
322
+ }
323
+ function svg(tagName) { return doc.createElementNS('http://www.w3.org/2000/svg', tagName); }
324
+ function pictureEl(pic) {
325
+ if (!pic) return null;
326
+ // drawn from the real building as it stands (its structure on the living model)
327
+ const b = buildingOf(living(), pic.building);
328
+ const lv = isoPicture(pic.building, pic.change, env.mode(), b ? b.structure : null);
329
+ if (!lv) return null;
330
+ const fig = h('figure', 'govx-pic');
331
+ const s = svg('svg');
332
+ s.setAttribute('viewBox', lv.viewBox);
333
+ s.setAttribute('width', '100%');
334
+ s.setAttribute('height', '150');
335
+ s.setAttribute('role', 'img');
336
+ s.setAttribute('aria-label', NAMES[pic.building] + ' with the change drawn dashed: ' + (capsuleOf(pic.change) ? capsuleOf(pic.change).what : 'a change'));
337
+ s.setAttribute('preserveAspectRatio', 'xMidYMax meet');
338
+ lv.paths.forEach((p) => {
339
+ const e = svg('path');
340
+ e.setAttribute('d', p.d); e.setAttribute('fill', p.f); e.setAttribute('stroke', p.s);
341
+ if (p.dash && p.dash !== 'none') e.setAttribute('stroke-dasharray', p.dash);
342
+ e.setAttribute('stroke-width', String(p.w || 1));
343
+ e.setAttribute('vector-effect', 'non-scaling-stroke');
344
+ s.appendChild(e);
345
+ });
346
+ fig.appendChild(s);
347
+ fig.appendChild(h('figcaption', 'govx-pic__cap', 'dashed: what the vote would build'));
348
+ return fig;
349
+ }
350
+ // an ask card in place (the deck's own card, the same shape)
351
+ function askHere(id, pic) {
352
+ const p = deckPage();
353
+ if (!p) return null;
354
+ const lv = living();
355
+ const ask = (lv ? lv.city.asks : []).find((a) => a && a.id === id);
356
+ if (!ask || p.isDone(id)) return null;
357
+ const cv = p.cardFor(ask);
358
+ return p.cardEl(cv, { pick: (op) => p.answerHere(id, op) }, { inPlace: true, picture: pic ? pictureEl(pic) : null });
359
+ }
360
+
361
+ // ---------- the lead row and the building's stack, on every building ----------
362
+ function leadRow(key, off) {
363
+ const row = h('div', 'govx-lead');
364
+ const seat = h('span', 'inside-seat' + (off.seated ? '' : ' inside-seat--open'), off.seated ? off.lead.name.charAt(0).toUpperCase() : '');
365
+ row.appendChild(seat);
366
+ const words = h('span', 'govx-lead__words');
367
+ words.appendChild(h('span', 'govx-lead__t', off.seated ? off.lead.name + ' leads ' + off.name : 'No Lead yet'));
368
+ words.appendChild(h('span', 'govx-lead__f', off.seated ? 'since ' + off.lead.since + (buildsAlone(off).yes ? ' · builds without a vote' : ' · builds by vote') : off.owner + ' holds ' + off.name));
369
+ row.appendChild(words);
370
+ const go = h('button', 'inside-textbtn govx-lead__go', off.seated ? 'the Lead\'s office' : 'the office');
371
+ go.setAttribute('type', 'button');
372
+ go.addEventListener('click', () => { if (env.pickFloor) env.pickFloor('office'); });
373
+ row.appendChild(go);
374
+ return row;
375
+ }
376
+ function stackRow(key) {
377
+ const p = deckPage();
378
+ if (!p || !root.GovDeck) return null;
379
+ const q = root.GovDeck.queueOf(living(), p.session, key);
380
+ if (!q.length) return null;
381
+ const you = q.filter((a) => a.needs_you).length;
382
+ const row = h('div', 'govx-stack' + (you ? ' govx-stack--you' : ''));
383
+ const words = h('span', 'govx-stack__words');
384
+ // the deck's own words for its counts (gov-living.js COUNT_WORDS)
385
+ words.appendChild(h('span', 'govx-stack__n', q.length));
386
+ words.appendChild(h('span', 'govx-stack__t', ' to decide here'));
387
+ words.appendChild(h('span', 'govx-stack__f', you ? you + ' ' + L.countWords('on_you', you) : 'nothing to do'));
388
+ row.appendChild(words);
389
+ const a = h('a', 'desk-chip desk-chip--primary govx-stack__go', 'decide in a row');
390
+ a.setAttribute('href', '/builders/street#deck=' + key);
391
+ row.appendChild(a);
392
+ return row;
393
+ }
394
+
395
+ // ---------- the books: Rules, Treasury, Charter ----------
396
+ function lawEl() {
397
+ const book = lawBook(living());
398
+ const box = h('section', 'govx-book govx-laws');
399
+ box.setAttribute('aria-label', 'the law book');
400
+ box.appendChild(head('the law book', h('span', 'govx-head__n', book.n + ' in force')));
401
+ const list = h('ol', 'govx-laws__rows');
402
+ book.rows.forEach((r) => {
403
+ const li = h('li', 'govx-law');
404
+ const top = h('div', 'govx-law__top');
405
+ top.appendChild(h('span', 'govx-law__t', r.text));
406
+ top.appendChild(h('span', 'govx-law__by', r.by + ' · ' + r.when));
407
+ li.appendChild(top);
408
+ const ai = h('span', 'govx-law__ai');
409
+ ai.appendChild(h('span', 'govx-law__k', 'the AI'));
410
+ ai.appendChild(h('span', '', r.ai));
411
+ li.appendChild(ai);
412
+ if (r.locks) li.appendChild(h('span', 'govx-law__lock', 'locks ' + r.locks));
413
+ list.appendChild(li);
414
+ });
415
+ if (book.rows.length) box.appendChild(list);
416
+ else box.appendChild(h('p', 'inside-note', 'No law is in force yet. A law is passed by a vote.'));
417
+ box.appendChild(injectionEl());
418
+ return box;
419
+ }
420
+ function injectionEl() {
421
+ const d = env.data();
422
+ const me = deckPage() ? deckPage().feeds().me : null;
423
+ const inj = injection(living(), me || (d && d.me));
424
+ const box = h('div', 'govx-inject');
425
+ box.appendChild(head('what every agent reads at the start'));
426
+ const pre = h('div', 'govx-inject__body');
427
+ pre.setAttribute('role', 'region');
428
+ pre.setAttribute('tabindex', '0');
429
+ pre.setAttribute('aria-label', 'what every session starts with');
430
+ const prof = h('div', 'govx-inject__part');
431
+ prof.appendChild(h('span', 'govx-inject__h', 'how it talks to you'));
432
+ if (inj.real) { prof.appendChild(h('span', 'govx-inject__line', inj.head)); inj.profile.forEach((x) => prof.appendChild(h('span', 'govx-inject__line', '· ' + x))); }
433
+ else prof.appendChild(h('span', 'govx-inject__line', 'Your interaction profile could not be read just now.'));
434
+ pre.appendChild(prof);
435
+ if (inj.laws.length) {
436
+ const laws = h('div', 'govx-inject__part');
437
+ laws.appendChild(h('span', 'govx-inject__h', 'the project\'s laws'));
438
+ inj.laws.forEach((l) => laws.appendChild(h('span', 'govx-inject__line', l.n + '. ' + l.text + (l.ai ? ' — ' + l.ai : ''))));
439
+ pre.appendChild(laws);
440
+ }
441
+ box.appendChild(pre);
442
+ return box;
443
+ }
444
+ function treasuryEl() {
445
+ const t = treasury(living());
446
+ if (!t) return null;
447
+ const box = h('section', 'govx-book govx-money');
448
+ box.setAttribute('aria-label', 'the treasury');
449
+ box.appendChild(head('the money', h('span', 'govx-head__n', 'spend, pots and the reserve')));
450
+ const tiles = h('div', 'desk-tiles govx-tiles');
451
+ t.tiles.forEach((x) => {
452
+ const tl = h('div', 'desk-tile');
453
+ tl.appendChild(h('span', 'desk-tile__v', x.v));
454
+ tl.appendChild(h('span', 'desk-tile__l', x.l));
455
+ tiles.appendChild(tl);
456
+ });
457
+ box.appendChild(tiles);
458
+ const cap = h('div', 'desk-row govx-cap');
459
+ const cm = h('div', 'desk-row__main');
460
+ cm.appendChild(h('span', 'desk-ini', '$'));
461
+ const cn = h('span', 'desk-name govx-static');
462
+ cn.appendChild(h('span', 'desk-name__t', 'Spend cap'));
463
+ cn.appendChild(h('span', 'desk-name__f', t.cap.line));
464
+ cm.appendChild(cn);
465
+ const sw = h('span', 'govx-switch');
466
+ sw.setAttribute('role', 'img');
467
+ sw.setAttribute('aria-label', 'spend cap: ' + (t.cap.on ? 'on' : 'off'));
468
+ if (t.cap.on) sw.classList.add('govx-switch--on');
469
+ sw.appendChild(h('span', 'govx-switch__knob'));
470
+ cm.appendChild(sw);
471
+ cap.appendChild(cm);
472
+ box.appendChild(cap);
473
+ const pots = h('div', 'govx-pots');
474
+ pots.appendChild(head('pots per goal', h('span', 'govx-head__n', t.pots.length + (t.pots.length === 1 ? ' goal' : ' goals'))));
475
+ t.shown.forEach((p) => {
476
+ // a dry pot is lit orange only when its loan waits on you
477
+ const r = h('div', 'govx-pot' + (p.dry ? (p.you ? ' govx-pot--dry' : ' govx-pot--empty') : '') + (p.lent ? ' govx-pot--lent' : ''));
478
+ const top = h('div', 'govx-pot__top');
479
+ const nm = h('span', 'govx-pot__t', fit(shortTitle(p.title), 26, 4));
480
+ nm.setAttribute('title', p.title);
481
+ top.appendChild(nm);
482
+ top.appendChild(h('span', 'govx-pot__f', usd(p.spent) + ' of ' + usd(p.budget) + (p.dry ? ' · dry' : p.lent ? ' · topped up' : '')));
483
+ r.appendChild(top);
484
+ const bar = h('span', 'govx-pot__bar');
485
+ bar.setAttribute('aria-hidden', 'true');
486
+ const fill = h('span', 'govx-pot__fill');
487
+ fill.style.setProperty('width', p.pct + '%');
488
+ bar.appendChild(fill);
489
+ r.appendChild(bar);
490
+ pots.appendChild(r);
491
+ });
492
+ if (t.more) pots.appendChild(h('p', 'inside-note', 'and ' + t.more + ' smaller pots'));
493
+ if (!t.pots.length) pots.appendChild(h('p', 'inside-note', 'No goal has a pot yet.'));
494
+ box.appendChild(pots);
495
+ if (t.dryAsk) { const c = askHere(t.dryAsk); if (c) box.appendChild(c); }
496
+ return box;
497
+ }
498
+ function strictEl() {
499
+ const st = strictness(living(), os);
500
+ if (!st) return null;
501
+ const box = h('section', 'govx-book govx-strict');
502
+ box.setAttribute('aria-label', 'how strict the agents run');
503
+ box.appendChild(head('how strict the agents run', h('span', 'govx-head__n', st.current ? st.locked + (st.locked === 1 ? ' lock' : ' locks') + ' in force' : 'no preset yet')));
504
+ const g = h('div', 'govx-presets');
505
+ g.setAttribute('role', 'radiogroup');
506
+ g.setAttribute('aria-label', 'the preset');
507
+ st.presets.forEach((p) => {
508
+ const b = h('button', 'desk-chip desk-chip--choice govx-preset' + (p.on ? ' govx-preset--on' : ''), p.name);
509
+ b.setAttribute('type', 'button');
510
+ b.setAttribute('role', 'radio');
511
+ b.setAttribute('aria-checked', p.on ? 'true' : 'false');
512
+ if (p.filed) b.setAttribute('aria-disabled', 'true');
513
+ b.addEventListener('click', () => { if (!p.on && !p.filed) switchPreset(p.key); });
514
+ g.appendChild(b);
515
+ });
516
+ box.appendChild(g);
517
+ box.appendChild(h('p', 'govx-strict__line', st.line));
518
+ return box;
519
+ }
520
+
521
+ // ---------- the office desk ----------
522
+ function officeEl(el, key, bv) {
523
+ const lv = living();
524
+ const off = officeOf(lv, key);
525
+ clear(el);
526
+ el.className = 'inside-open desk office';
527
+ el.setAttribute('data-desk', key + ':office');
528
+ const hd = h('div', 'inside-open__head');
529
+ const plate = h('span', 'inside-plate');
530
+ const ic = svg('svg');
531
+ ic.setAttribute('width', '34'); ic.setAttribute('height', '34'); ic.setAttribute('viewBox', '0 0 24 24'); ic.setAttribute('aria-hidden', 'true');
532
+ const sp = root.GovInside ? root.GovInside.symbolPath(bv.builtSel ? 'layers' : 'hall') : { d: '', f: '' };
533
+ const a = svg('path'); a.setAttribute('d', sp.d); ic.appendChild(a);
534
+ plate.appendChild(ic);
535
+ hd.appendChild(plate);
536
+ const words = h('div', 'inside-open__words');
537
+ words.appendChild(h('span', 'inside-open__kicker', bv.builtSel ? 'built by a build-out' : 'the top floor'));
538
+ words.appendChild(h('h3', 'inside-open__title', bv.builtSel ? 'A new floor' : 'Lead\'s office'));
539
+ words.appendChild(h('span', 'desk-status', bv.builtSel ? 'built · empty until work comes' : off.line));
540
+ hd.appendChild(words);
541
+ el.appendChild(hd);
542
+ if (bv.builtSel) {
543
+ const q = h('div', 'desk-quiet desk-quiet--cleared');
544
+ q.appendChild(h('span', 'desk-quiet__line', 'Built, and lit'));
545
+ q.appendChild(h('span', 'desk-quiet__sub', 'its first desk opens when work comes here'));
546
+ el.appendChild(q);
547
+ return;
548
+ }
549
+ // tiles: one number and two words, never a zero
550
+ const tiles = h('div', 'desk-tiles');
551
+ const standing = off.rooms.filter((r) => !r.proposed).length;
552
+ if (standing) {
553
+ const t = h('div', 'desk-tile');
554
+ t.appendChild(h('span', 'desk-tile__v', standing));
555
+ t.appendChild(h('span', 'desk-tile__l', standing === 1 ? 'committee' : 'committees'));
556
+ tiles.appendChild(t);
557
+ el.appendChild(tiles);
558
+ }
559
+ // rows: a name, one fact
560
+ const rows = h('div', 'desk-rows');
561
+ const initials = (t) => { const w = str(t).replace(/[^A-Za-z0-9 ]+/g, ' ').trim().split(/\s+/).filter(Boolean); return (w.length > 1 ? w[0].charAt(0) + w[1].charAt(0) : str(w[0]).slice(0, 2)).toUpperCase(); };
562
+ const row = (name, fact) => {
563
+ const r = h('div', 'desk-row');
564
+ const m = h('div', 'desk-row__main');
565
+ m.appendChild(h('span', 'desk-ini', initials(name)));
566
+ const nm = h('span', 'desk-name govx-static');
567
+ nm.appendChild(h('span', 'desk-name__t', fit(name, 20, 3)));
568
+ nm.appendChild(h('span', 'desk-name__f', fit(fact, 40)));
569
+ m.appendChild(nm);
570
+ r.appendChild(m);
571
+ rows.appendChild(r);
572
+ };
573
+ if (off.seated) row('Builds alone', buildsAlone(off).fact);
574
+ if (off.seated && off.charter && off.charter.scope) row('Its remit', off.charter.scope);
575
+ off.rooms.forEach((r) => row(r.name, r.proposed ? 'proposed · a vote is open' : r.line));
576
+ worksOf(lv, key).forEach((w) => row(w.label, w.line));
577
+ if (rows.children && rows.children.length) el.appendChild(rows);
578
+ // build out: a capsule each, one tap. Only the building's holder sees them
579
+ // (GET /government/leads answers; the server holds the same wall).
580
+ if (off.holders && off.holders.includes(str(viewerId()))) {
581
+ const bo = h('div', 'govx-build');
582
+ bo.setAttribute('role', 'group');
583
+ bo.setAttribute('aria-label', 'build out');
584
+ bo.appendChild(h('span', 'desk-choices__ask', 'build out'));
585
+ CAPSULES.forEach((c) => {
586
+ const pl = buildPlan(lv, key, c.key);
587
+ const b = chip('desk-chip--choice govx-capsule', c.chip, () => buildOut(key, c.key));
588
+ b.setAttribute('data-capsule', c.key);
589
+ b.setAttribute('title', pl.why);
590
+ b.appendChild(h('span', 'govx-capsule__how', pl.authorised ? 'builds now' : 'a vote'));
591
+ bo.appendChild(b);
592
+ });
593
+ el.appendChild(bo);
594
+ }
595
+ // the votes waiting on this office: a build-out's docket card, with the picture drawn from the real building
596
+ (buildingOf(lv, key).construction || []).forEach((c) => {
597
+ if (c.capsule && c.ask_id) { const card = askHere(c.ask_id, { building: key, change: c.capsule }); if (card) el.appendChild(card); }
598
+ });
599
+ }
600
+ // the live site over the drawing: a tower crane over a floor going up
601
+ function crane(bv) {
602
+ const layer = doc.getElementById('insideFloors');
603
+ if (!layer) return;
604
+ // a tower crane stands beside the building, its jib over the site
605
+ (bv.sites || []).slice(0, 1).forEach((s) => {
606
+ if (s.state !== 'site') return;
607
+ const c = h('div', 'office-crane');
608
+ c.setAttribute('aria-hidden', 'true');
609
+ // over the roof (the office is the top storey), clear of the crown
610
+ const roof = bv.officeIdx >= 0 ? 676 - (bv.officeIdx + 1) * 52 : s.y;
611
+ const top = Math.max(16, roof - 70);
612
+ c.style.left = (s.x + s.w - 90) + 'px';
613
+ c.style.top = top + 'px';
614
+ c.style.width = '180px';
615
+ c.style.height = (676 - top) + 'px';
616
+ c.style.setProperty('--office-hook', Math.max(30, s.y + 12 - top) + 'px');
617
+ c.appendChild(h('span', 'office-crane__mast'));
618
+ const jib = h('span', 'office-crane__jib');
619
+ jib.appendChild(h('span', 'office-crane__hook'));
620
+ c.appendChild(jib);
621
+ layer.appendChild(c);
622
+ });
623
+ }
624
+
625
+ // ---------- acting ----------
626
+ // A BUILD-OUT (task 1004711): a tap is POST /government/buildouts — the server
627
+ // checks the holder and asks build_rights, then builds it at once or files the
628
+ // vote — and the page reads the Leads and the docket again, so the site or the
629
+ // ghost it draws is the server's. The hall preview stores nothing, and says so.
630
+ function preview(out) { return !!(out && out.status === 501 && out.body && out.body.error && out.body.error.code === 'preview_readonly'); }
631
+ function refusal(out) {
632
+ const e = (out && out.body && out.body.error) || {};
633
+ return str(e.what_this_means || (e.details && e.details.what_this_means) || e.message) || 'It could not be built from here.';
634
+ }
635
+ function buildOut(key, capsule) {
636
+ const off = officeOf(living(), key);
637
+ if (!off || !env.send) return Promise.resolve({ built: false });
638
+ const what = capsuleOf(capsule) ? capsuleOf(capsule).what : 'it';
639
+ return env.send({ method: 'POST', url: '/api/bongos/government/buildouts', body: { building: key, capsule: capsule } }).then((out) => {
640
+ if (preview(out)) { env.say('This preview stores nothing; nothing was built.', true); return { built: false, preview: true }; }
641
+ if (!out.ok) { env.say(refusal(out), true); return { built: false, refused: true }; }
642
+ const atOnce = out.body && out.body.path === 'at_once';
643
+ const why = out.body && out.body.rights && out.body.rights.why ? ' ' + str(out.body.rights.why) : '';
644
+ env.say(atOnce ? 'Building ' + what + ' now.' + why : 'Filed for a vote.' + why + ' The card has its picture.', true);
645
+ if (atOnce) env.decided();
646
+ if (root.GovFeeds && typeof root.GovFeeds.refresh === 'function') root.GovFeeds.refresh(['leads', 'docket']);
647
+ if (env.load) env.load();
648
+ return { built: atOnce, path: out.body && out.body.path };
649
+ });
650
+ }
651
+ // a switch of preset (task 1004715): ONE laws vote, filed through the board
652
+ function switchPreset(to) {
653
+ const st = strictness(living(), os);
654
+ if (!st || !env.send) return Promise.resolve();
655
+ return env.send({ method: 'POST', url: '/api/bongos/government/board/presets', body: { preset: to } }).then((out) => {
656
+ if (preview(out)) { env.say('This preview stores nothing; no vote was filed.', true); return; }
657
+ if (!out.ok) { env.say(refusal(out), true); return; }
658
+ os.presetFiled[to] = true;
659
+ env.say('Put to a vote. Nothing changes until the board passes it.', true);
660
+ if (root.GovFeeds && typeof root.GovFeeds.refresh === 'function') root.GovFeeds.refresh(['docket', 'presets']);
661
+ if (env.load) env.load();
662
+ env.draw();
663
+ });
664
+ }
665
+
666
+ function inside(bv, on, govt) {
667
+ if (!env || !doc || !bv) return false;
668
+ const lv = living();
669
+ const key = bv.key;
670
+ // the office stands from the real Leads and build-outs; each book from its
671
+ // own read (laws, the treasury, the presets); with none, no government layer
672
+ const off = lv ? officeOf(lv, key) : null;
673
+ const book = !lv || bv.officeSel || bv.builtSel ? null : key === 'rules' ? lawEl() : key === 'treasury' ? treasuryEl() : key === 'charter' ? strictEl() : null;
674
+ if (!lv || !(off || book)) { if (govt) { govt.hidden = true; clear(govt); } return false; }
675
+ crane(bv);
676
+ if (govt) {
677
+ clear(govt);
678
+ govt.hidden = false;
679
+ if (off) govt.appendChild(leadRow(key, off));
680
+ const st = stackRow(key);
681
+ if (st) govt.appendChild(st);
682
+ // the building's book stands over its floors; the office (or a built
683
+ // floor) is a desk of its own, so the book steps aside for it
684
+ if (book) govt.appendChild(book);
685
+ }
686
+ if (off && (bv.officeSel || bv.builtSel)) { officeEl(on, key, bv); return true; }
687
+ return false;
688
+ }
689
+
690
+ function mount(e) {
691
+ env = e;
692
+ doc = root.document || (typeof document !== 'undefined' ? document : null);
693
+ const p = deckPage();
694
+ if (!p) return;
695
+ // an answer on the deck redraws the interior it stands in
696
+ p.onChange(() => { if (env && env.inside && env.inside()) env.draw(); });
697
+ }
698
+
699
+ return { mount: mount, input: input, inside: inside, buildOut: buildOut, switchPreset: switchPreset, state: os };
700
+ }());
701
+
702
+ const api = Object.assign({ page: page }, model);
703
+ root.GovOffice = api;
704
+ if (typeof module !== 'undefined' && module.exports) module.exports = api;
705
+ }(typeof window !== 'undefined' ? window : globalThis));