@bongos/core 1.21.112 → 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.
- package/.bongos-core.json +616 -186
- package/.claude/skills/ask-for-help/SKILL.md +1 -1
- package/.claude/skills/bongos-feedback/SKILL.md +1 -1
- package/.claude/skills/builder-sequence/SKILL.md +1 -1
- package/.claude/skills/builder-ship/SKILL.md +1 -0
- package/GOVERNMENT.md +128 -11
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +28 -0
- package/clients/bongos-client/index.cjs +28 -0
- package/clients/bongos-client/index.d.ts +45 -2
- package/clients/bongos-client/index.mjs +28 -0
- package/docs/adr/0372-an-ask-is-stored-per-docket-matter-and-the-server-refuses-slop.md +33 -0
- package/docs/adr/0373-an-ai-session-files-a-blocker-with-an-ask.md +31 -0
- package/docs/adr/0374-a-building-lead-is-a-held-position-appointed-by-a-people-matter.md +30 -0
- package/docs/adr/0375-who-may-build-without-a-vote-comes-from-the-form-of-government.md +38 -0
- package/docs/adr/0376-every-ask-is-routed-to-a-real-seat-on-the-server.md +30 -0
- package/docs/adr/0377-governance-is-a-held-position-read-on-me.md +24 -0
- package/docs/adr/0378-a-leads-build-outs-are-rows-built-at-once-or-by-a-passed-vote.md +33 -0
- package/docs/adr/0379-the-treasury-is-pots-per-goal-a-reserve-and-borrowing-over-real-spend.md +32 -0
- package/docs/adr/0380-a-strictness-preset-is-a-bundle-of-laws-switched-by-one-laws-vote.md +26 -0
- package/docs/adr/README.md +10 -1
- package/docs/api/openapi.json +989 -87
- package/docs/api-reference.md +18 -4
- package/docs/copy-inventory.md +343 -262
- package/docs/copy-registry.json +1223 -426
- package/docs/file-map.md +2 -1
- package/docs/module-api-changelog.md +4 -0
- package/docs/packs/engineer.md +2 -1
- package/docs/page-inventory.json +91 -6
- package/docs/page-readings.json +5017 -4458
- package/modules/builder-settings/law-locks.js +202 -0
- package/modules/builder-settings/routes/settings.js +66 -6
- package/modules/builder-settings/settings.js +7 -0
- package/modules/discord/ship-broadcast.js +22 -13
- package/modules/government/asks-db.js +54 -0
- package/modules/government/asks.js +269 -0
- package/modules/government/board-idea-reads.js +68 -0
- package/modules/government/board.js +21 -57
- package/modules/government/build-rights.js +148 -0
- package/modules/government/buildouts.js +160 -0
- package/modules/government/counts.js +81 -0
- package/modules/government/db.js +242 -0
- package/modules/government/decision-board.js +234 -8
- package/modules/government/decision-rules.js +2 -0
- package/modules/government/docket.js +114 -6
- package/modules/government/law-board.js +131 -0
- package/modules/government/laws.js +241 -0
- package/modules/government/leads.js +170 -0
- package/modules/government/migrations/government_027_laws.sql +143 -0
- package/modules/government/migrations/government_028_asks.sql +57 -0
- package/modules/government/migrations/government_029_leads.sql +135 -0
- package/modules/government/migrations/government_030_buildouts.sql +111 -0
- package/modules/government/migrations/government_031_treasury.sql +107 -0
- package/modules/government/module.json +2 -1
- package/modules/government/pollers/treasury-borrow.js +62 -0
- package/modules/government/positions.js +107 -0
- package/modules/government/presets.js +149 -0
- package/modules/government/routes/government.js +425 -2
- package/modules/government/routing.js +154 -0
- package/modules/government/treasury-db.js +166 -0
- package/modules/government/treasury.js +241 -0
- package/modules/hall-ui/public/atlas.html +2 -2
- package/modules/hall-ui/public/blockers.html +2 -2
- package/modules/hall-ui/public/board-room.html +2 -2
- package/modules/hall-ui/public/board-room.js +2 -0
- package/modules/hall-ui/public/city-draw.js +604 -11
- package/modules/hall-ui/public/city.css +157 -1
- package/modules/hall-ui/public/city.html +37 -7
- package/modules/hall-ui/public/city.js +441 -21
- package/modules/hall-ui/public/city.states.json +110 -5
- package/modules/hall-ui/public/collab.html +2 -2
- package/modules/hall-ui/public/commands.html +2 -2
- package/modules/hall-ui/public/copy-desk.html +2 -2
- package/modules/hall-ui/public/craft-mode.js +112 -29
- package/modules/hall-ui/public/deploy.html +2 -2
- package/modules/hall-ui/public/diagram-viewer.css +12 -0
- package/modules/hall-ui/public/diagram-viewer.js +4 -0
- package/modules/hall-ui/public/diagrams.html +2 -2
- package/modules/hall-ui/public/drachmae.html +2 -2
- package/modules/hall-ui/public/fleet.html +2 -2
- package/modules/hall-ui/public/gate.html +2 -2
- package/modules/hall-ui/public/goal-map.html +2 -2
- package/modules/hall-ui/public/goals.html +2 -2
- package/modules/hall-ui/public/gov-feeds.js +141 -0
- package/modules/hall-ui/public/gov-living.js +1002 -0
- package/modules/hall-ui/public/gov-positions.js +97 -0
- package/modules/hall-ui/public/government.html +2 -2
- package/modules/hall-ui/public/hall-render.js +3 -0
- package/modules/hall-ui/public/idea.html +2 -2
- package/modules/hall-ui/public/ideas.html +2 -2
- package/modules/hall-ui/public/index.html +3 -3
- package/modules/hall-ui/public/modules.html +2 -2
- package/modules/hall-ui/public/my-agents.css +334 -0
- package/modules/hall-ui/public/my-agents.html +72 -0
- package/modules/hall-ui/public/my-agents.js +603 -0
- package/modules/hall-ui/public/my-agents.states.json +37 -0
- package/modules/hall-ui/public/primer.html +2 -2
- package/modules/hall-ui/public/profile-connections.js +40 -28
- package/modules/hall-ui/public/profile.html +2 -2
- package/modules/hall-ui/public/project-settings.html +2 -2
- package/modules/hall-ui/public/ranks.html +2 -2
- package/modules/hall-ui/public/roadmap.html +2 -2
- package/modules/hall-ui/public/room.css +8 -0
- package/modules/hall-ui/public/roster.html +2 -2
- package/modules/hall-ui/public/sessions.html +2 -2
- package/modules/hall-ui/public/settings.html +2 -2
- package/modules/hall-ui/public/shell.js +116 -16
- package/modules/hall-ui/public/street-card.js +18 -1
- package/modules/hall-ui/public/street-deck.css +299 -0
- package/modules/hall-ui/public/street-deck.js +1145 -0
- package/modules/hall-ui/public/street-depth.js +712 -0
- package/modules/hall-ui/public/street-desk.js +5 -2
- package/modules/hall-ui/public/street-draw.js +109 -16
- package/modules/hall-ui/public/street-inside.js +137 -22
- package/modules/hall-ui/public/street-office.js +705 -0
- package/modules/hall-ui/public/street.css +161 -11
- package/modules/hall-ui/public/street.html +41 -11
- package/modules/hall-ui/public/street.js +78 -66
- package/modules/hall-ui/public/street.states.json +129 -21
- package/modules/hall-ui/public/studio.html +2 -2
- package/modules/hall-ui/public/style.css +5 -1
- package/modules/hall-ui/public/task.html +2 -2
- package/modules/hall-ui/public/thinking.html +2 -2
- package/modules/hall-ui/public/tweak-editor.html +2 -2
- package/modules/hall-ui/public/watch.html +2 -2
- package/modules/hall-ui/public/watch.js +14 -1
- package/modules/hall-ui/public/work.html +2 -2
- package/modules/hall-ui/records/living-city.md +57 -0
- package/modules/hall-ui/records/my-agents.md +21 -0
- package/modules/hall-ui/records/oversight-family.md +1 -0
- package/modules/hall-ui/records/profile-connections.md +10 -0
- package/modules/hall-ui/records/reading-rooms.md +2 -0
- package/modules/ideas/routes/blockers.js +92 -3
- package/modules/lifecycle/module.json +2 -1
- package/modules/lifecycle/moving.js +17 -1
- package/modules/lifecycle/routes/moving.js +6 -0
- package/modules/lifecycle/routes/project-settings.js +23 -0
- package/modules/onboarding/approval-broadcast.js +8 -0
- package/modules/onboarding/routes/access-requests.js +16 -6
- package/modules/platform-identity/connections.js +13 -2
- package/modules/platform-identity/routes/connections.js +7 -1
- package/modules/public-landing/public/account-private.states.json +2 -2
- package/modules/public-landing/public/account.html +65 -5
- package/modules/public-landing/public/account.states.json +3 -3
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp.json +13 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp.json +13 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp.json +13 -0
- package/modules/public-landing/public/assets/world.css +4 -4
- package/modules/public-landing/public/connections-empty.states.json +14 -0
- package/modules/public-landing/public/connections-inactive.states.json +14 -0
- package/modules/public-landing/public/connections.html +450 -0
- package/modules/public-landing/public/connections.probes.json +414 -0
- package/modules/public-landing/public/connections.states.json +17 -0
- package/modules/public-landing/public/contact.html +2 -2
- package/modules/public-landing/public/guild.html +2 -2
- package/modules/public-landing/public/index.html +2 -2
- package/modules/public-landing/public/invitations.html +2 -2
- package/modules/public-landing/public/privacy.html +2 -2
- package/modules/public-landing/public/projects.html +2 -2
- package/modules/public-landing/public/projects.probes.json +6 -2
- package/modules/public-landing/public/terms.html +2 -2
- package/modules/ui-design/kit/fixtures/connections-empty.json +12 -0
- package/modules/ui-design/kit/fixtures/connections-inactive.json +7 -0
- package/modules/ui-design/kit/fixtures/connections.json +22 -0
- package/modules/ui-design/kit/serve.js +95 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +128 -0
- package/scripts/discord/README.md +8 -5
- package/scripts/discord/post.js +12 -1
- package/scripts/gds/artifact-audit.js +20 -3
- package/scripts/gds/ask.js +164 -0
- package/scripts/gds/autobongos-run.js +20 -5
- package/scripts/gds/autobongos-tweak-lane.js +262 -0
- package/scripts/gds/autobongos-verify.js +21 -2
- package/scripts/gds/claim.js +2 -0
- package/scripts/gds/government-walk.js +638 -4
- package/scripts/gds/pulse.js +9 -6
- package/scripts/gds/ship-escalation.js +22 -3
- package/scripts/gds/ship-finish.js +5 -4
- package/scripts/gds/ship-merge-gate.js +18 -6
- package/scripts/gds/tweak-halt.js +1 -0
- package/scripts/hall-preview/README.md +41 -1
- package/scripts/hall-preview/pref-writes.js +140 -0
- package/scripts/hall-preview/refresh-fixtures.js +101 -7
- package/scripts/hall-preview/scrub-identity.js +366 -7
- package/scripts/hall-preview/server.js +27 -1
- package/scripts/render-diagrams.sh +4 -2
- package/src/bongos/auth-idp.js +6 -1
- package/src/bongos/project-settings.js +41 -0
- package/src/bongos/route-rank-check.js +27 -0
- package/src/bongos/routes/me.js +68 -7
- package/src/bongos/serve-internal.js +5 -1
- package/src/module-api.js +7 -1
- package/src/platform-server.js +10 -0
- package/tests/application_lifecycle.mjs +47 -5
- package/tests/autobongos_tweak_lane.mjs +208 -0
- package/tests/conductor_main_e2e.mjs +21 -0
- package/tests/connections_api.mjs +18 -0
- package/tests/craft_mode_nav.mjs +5 -3
- package/tests/craft_mode_tallies.mjs +52 -16
- package/tests/discord_approvals.mjs +11 -0
- package/tests/discord_channels.mjs +20 -0
- package/tests/fixtures/gov-seated.mjs +64 -0
- package/tests/fixtures/government-ask-cards.json +1284 -0
- package/tests/government_act_alone_buildings.mjs +1 -0
- package/tests/government_agent_asks.mjs +322 -0
- package/tests/government_asks.mjs +408 -0
- package/tests/government_build_rights.mjs +253 -0
- package/tests/government_buildouts.mjs +382 -0
- package/tests/government_counts.mjs +143 -0
- package/tests/government_decision_rules.mjs +5 -1
- package/tests/government_laws.mjs +488 -0
- package/tests/government_laws_session.mjs +321 -0
- package/tests/government_leads.mjs +375 -0
- package/tests/government_positions.mjs +127 -0
- package/tests/government_presets.mjs +215 -0
- package/tests/government_routes.mjs +8 -1
- package/tests/government_routing.mjs +118 -0
- package/tests/government_seed.mjs +5 -0
- package/tests/government_treasury.mjs +394 -0
- package/tests/governor_docket.mjs +26 -2
- package/tests/hall_buildouts.mjs +208 -0
- package/tests/hall_city.mjs +14 -6
- package/tests/hall_city_living.mjs +617 -0
- package/tests/hall_gov_counts.mjs +336 -0
- package/tests/hall_gov_living.mjs +636 -0
- package/tests/hall_gov_real_site.mjs +655 -0
- package/tests/hall_matter_card.mjs +6 -1
- package/tests/hall_my_agents.mjs +481 -0
- package/tests/hall_nav.mjs +107 -4
- package/tests/hall_page_gate_map.mjs +4 -0
- package/tests/hall_preview_governor_fixtures.mjs +30 -3
- package/tests/hall_preview_scrub.mjs +59 -1
- package/tests/hall_preview_security.mjs +169 -0
- package/tests/hall_reading_world.mjs +44 -0
- package/tests/hall_roster_world.mjs +6 -2
- package/tests/hall_street.mjs +18 -2
- package/tests/hall_street_amend.mjs +1 -1
- package/tests/hall_street_deck.mjs +609 -0
- package/tests/hall_street_depth.mjs +559 -0
- package/tests/hall_street_desk.mjs +1 -1
- package/tests/hall_street_inside.mjs +29 -5
- package/tests/hall_tweak_editor.mjs +4 -1
- package/tests/hub_account_page.mjs +94 -3
- package/tests/hub_connections_page.mjs +507 -0
- package/tests/hub_invite_badge.mjs +1 -1
- package/tests/idp_device_exchange.mjs +18 -0
- package/tests/landing_legal_pages.mjs +8 -5
- package/tests/landing_page.mjs +23 -4
- package/tests/nav_permission_atoms.mjs +4 -1
- package/tests/profile_connections_ui.mjs +88 -25
- package/tests/projects_hub.mjs +15 -0
- package/tests/render_check.mjs +2 -2
- package/tests/ship_broadcast_routing.mjs +57 -16
- package/tests/ship_broadcast_server.mjs +40 -7
- package/tests/ship_merge_gate.mjs +19 -0
- package/tests/ui_design_kit.mjs +2 -2
- package/tests/watch_applications_queue.mjs +23 -0
|
@@ -173,9 +173,11 @@
|
|
|
173
173
|
recheck: 'check again',
|
|
174
174
|
// task 1004580: a work category renamed, retired, brought back, or a new one added
|
|
175
175
|
rename: 'rename', retire: 'retire', restore: 'bring back', add: 'add',
|
|
176
|
+
// task 1004712: a dry pot's borrow from the reserve: lend it, lend half, or say no
|
|
177
|
+
answer: 'answer',
|
|
176
178
|
});
|
|
177
179
|
// What a row's chips ask, per act (a reason or an option is one tap, never text).
|
|
178
|
-
const ASKS = Object.freeze({ recheck: 'why', budget: 'monthly limit', unsign: 'why', decide: 'pick one', ratify_goal: 'which version', check: 'check', take_core: 'move to', rot: 'stale after', module_off: 'sure', invite: 'their GitHub name', object: 'why', force: 'why', switch_off: 'why', pause: 'why', deactivate: 'why', signoff: 'confirm', record: 'the recording', allow: 'which goal', tier: 'which tier', give: 'to whom', take: 'from whom', rename: 'new name', add: 'its name' });
|
|
180
|
+
const ASKS = Object.freeze({ recheck: 'why', budget: 'monthly limit', unsign: 'why', decide: 'pick one', ratify_goal: 'which version', check: 'check', take_core: 'move to', rot: 'stale after', module_off: 'sure', invite: 'their GitHub name', object: 'why', force: 'why', switch_off: 'why', pause: 'why', deactivate: 'why', signoff: 'confirm', record: 'the recording', allow: 'which goal', tier: 'which tier', give: 'to whom', take: 'from whom', rename: 'new name', add: 'its name', answer: 'lend from the reserve?' });
|
|
179
181
|
// How a floor counts rows nobody is waiting on (desk rows), by its desk.
|
|
180
182
|
const DESK_NOUNS = Object.freeze({
|
|
181
183
|
'people.builders': 'builders', 'people.ranks': 'ranks', 'disputes.confirmed': 'to fix',
|
|
@@ -326,7 +328,8 @@
|
|
|
326
328
|
});
|
|
327
329
|
const first = actions.find((a) => a.ask);
|
|
328
330
|
// deciding a blocker is picking one of its options: its chips stand open
|
|
329
|
-
|
|
331
|
+
// (so is answering a borrow: lend, half or no, task 1004712)
|
|
332
|
+
const show = first && (first.key === 'decide' || first.key === 'answer') && choices[first.key] ? first.key : null;
|
|
330
333
|
return { actions: actions, chips: first ? choices[first.key] : null, choices: choices, show: show, wait: actions.length ? '' : 'not from here', card: card };
|
|
331
334
|
}
|
|
332
335
|
|
|
@@ -107,9 +107,11 @@
|
|
|
107
107
|
function rect(x, y, w, h) { return 'M' + x + ' ' + y + 'h' + w + 'v' + h + 'h' + (-w) + 'Z'; }
|
|
108
108
|
function ellipse(cx, cy, rx, ry) { return 'M' + (cx - rx) + ' ' + cy + 'a' + rx + ' ' + ry + ' 0 1 0 ' + (2 * rx) + ' 0a' + rx + ' ' + ry + ' 0 1 0 ' + (-2 * rx) + ' 0Z'; }
|
|
109
109
|
|
|
110
|
+
// T.depth (the living street's deeper side face, task 1004685) is the side's
|
|
111
|
+
// depth when a box names none; the prototype's theme has none, so 7.
|
|
110
112
|
function box(out, T, x, G, w, h, tone, o) {
|
|
111
113
|
o = o || {};
|
|
112
|
-
const d = o.d === undefined ? 7 : o.d, z = o.z || 0, base = G - z, y = base - h, dash = tone.dash || 'none';
|
|
114
|
+
const d = o.d === undefined ? (T && T.depth ? T.depth : 7) : o.d, z = o.z || 0, base = G - z, y = base - h, dash = tone.dash || 'none';
|
|
113
115
|
push(out, 'M' + (x + w) + ' ' + y + 'L' + (x + w + d) + ' ' + (y - d) + 'L' + (x + w + d) + ' ' + (base - d) + 'L' + (x + w) + ' ' + base + 'Z', tone.r, tone.s, dash);
|
|
114
116
|
push(out, 'M' + x + ' ' + y + 'L' + (x + d) + ' ' + (y - d) + 'L' + (x + w + d) + ' ' + (y - d) + 'L' + (x + w) + ' ' + y + 'Z', tone.t, tone.s, dash);
|
|
115
117
|
push(out, rect(x, y, w, h), tone.f, tone.s, dash);
|
|
@@ -136,8 +138,8 @@
|
|
|
136
138
|
if (o.brace) push(out, 'M' + x + ' ' + y + 'L' + (x + w) + ' ' + base + 'M' + (x + w) + ' ' + y + 'L' + x + ' ' + base, 'none', tone.s, '3 3');
|
|
137
139
|
}
|
|
138
140
|
|
|
139
|
-
function gable(out, x, yTop, w, hr, tone) {
|
|
140
|
-
const d = 7;
|
|
141
|
+
function gable(out, x, yTop, w, hr, tone, depth) {
|
|
142
|
+
const d = depth || 7;
|
|
141
143
|
push(out, 'M' + (x + w / 2) + ' ' + (yTop - hr) + 'L' + (x + w / 2 + d) + ' ' + (yTop - hr - d) + 'L' + (x + w + d) + ' ' + (yTop - d) + 'L' + (x + w) + ' ' + yTop + 'Z', tone.r, tone.s);
|
|
142
144
|
push(out, 'M' + x + ' ' + yTop + 'L' + (x + w / 2) + ' ' + (yTop - hr) + 'L' + (x + w) + ' ' + yTop + 'Z', tone.f, tone.s);
|
|
143
145
|
}
|
|
@@ -183,15 +185,15 @@
|
|
|
183
185
|
if (kind === 'disputes') {
|
|
184
186
|
box(out, T, x, G, 24, 40, C, { win: true, lit: lit });
|
|
185
187
|
box(out, T, x + 24, G, 32, 84, C, { fins: 3, door: true });
|
|
186
|
-
gable(out, x + 24, G - 84, 32, 16, R);
|
|
188
|
+
gable(out, x + 24, G - 84, 32, 16, R, T.depth);
|
|
187
189
|
box(out, T, x + 56, G, 24, 40, C, { win: true, lit: lit });
|
|
188
190
|
return 102;
|
|
189
191
|
}
|
|
190
192
|
if (kind === 'people') {
|
|
191
193
|
box(out, T, x, G, 34, 28, C, { door: true });
|
|
192
|
-
gable(out, x, G - 28, 34, 15, R);
|
|
194
|
+
gable(out, x, G - 28, 34, 15, R, T.depth);
|
|
193
195
|
box(out, T, x + 40, G, 40, 36, C, { door: true, win: true, lit: lit });
|
|
194
|
-
gable(out, x + 40, G - 36, 40, 18, R);
|
|
196
|
+
gable(out, x + 40, G - 36, 40, 18, R, T.depth);
|
|
195
197
|
return 58;
|
|
196
198
|
}
|
|
197
199
|
if (kind === 'planning') {
|
|
@@ -398,6 +400,14 @@
|
|
|
398
400
|
return 'nothing open';
|
|
399
401
|
}
|
|
400
402
|
|
|
403
|
+
// A district's name under its plot: a working area by its number (the title is
|
|
404
|
+
// the door's label and the hover), anything else its first words.
|
|
405
|
+
function districtName(title) {
|
|
406
|
+
const t = String(title || '');
|
|
407
|
+
const m = /^working area\s+(\d+)/i.exec(t);
|
|
408
|
+
return m ? 'area ' + m[1] : t.split(/\s+[—–:-]\s+/)[0];
|
|
409
|
+
}
|
|
410
|
+
|
|
401
411
|
function pinState(state) {
|
|
402
412
|
return state === 'needs' || state === 'moving' || state === 'settled' ? state : 'moving';
|
|
403
413
|
}
|
|
@@ -479,6 +489,14 @@
|
|
|
479
489
|
|
|
480
490
|
const FALLBACK_LINE = 'the docket could not be read just now';
|
|
481
491
|
|
|
492
|
+
// ---------- THE DEPTH lives in street-depth.js (task 1004685) ----------
|
|
493
|
+
// The living street's three planes (the goals' skyline, the civic row's
|
|
494
|
+
// structure and construction, the skylanes, the road, the readings as tiles)
|
|
495
|
+
// are GovStreetDepth's, found on its global when a street is drawn deep: it
|
|
496
|
+
// requires this file, so this file never requires it (no require cycle), and
|
|
497
|
+
// without it a deep street is drawn flat.
|
|
498
|
+
const depthKit = () => root.GovStreetDepth || null;
|
|
499
|
+
|
|
482
500
|
// The whole street view as data.
|
|
483
501
|
// docket GET /government/docket body, or null when it failed
|
|
484
502
|
// goals GET /goals goals[] (only 'open' ones are drawn)
|
|
@@ -487,6 +505,11 @@
|
|
|
487
505
|
// mode 'dark' | 'light'
|
|
488
506
|
// now ms since the epoch (for deadlines and the month)
|
|
489
507
|
// here parseHash() of the address, or null
|
|
508
|
+
// depth true: the living street (task 1004685), in three planes; the
|
|
509
|
+
// model gains `depth` and the civic row its deeper side face
|
|
510
|
+
// living GovLiving.livingModel() over the same feeds, or null (no Leads,
|
|
511
|
+
// no agents, no construction: the deep street with real data only)
|
|
512
|
+
// roster GET /builders/roster body, or null (the people reading)
|
|
490
513
|
function streetModel(input) {
|
|
491
514
|
const opts = input || {};
|
|
492
515
|
const docket = opts.docket && typeof opts.docket === 'object' && Array.isArray(opts.docket.matters) ? opts.docket : null;
|
|
@@ -495,8 +518,13 @@
|
|
|
495
518
|
const now = Number(opts.now) || 0;
|
|
496
519
|
const here = opts.here || null;
|
|
497
520
|
const counts = docket && docket.counts ? docket.counts : { needs: 0, moving: 0, settled: 0 };
|
|
498
|
-
|
|
499
|
-
|
|
521
|
+
// THE COUNTS (task 1004685): `counts` is gov-living.js countsOf, the numbers the
|
|
522
|
+
// city, the deck and the shell print; the header reads it when it is given
|
|
523
|
+
// (null: still reading, so "—"), and the docket's own counts only without it.
|
|
524
|
+
const shared = opts.counts !== undefined;
|
|
525
|
+
const sc = opts.counts || null;
|
|
526
|
+
const needs = shared ? Math.max(0, Number(sc && sc.on_you) || 0) : Math.max(0, Number(counts.needs) || 0);
|
|
527
|
+
const evening = !!docket && (shared ? !!sc && sc.on_you === 0 : needs === 0);
|
|
500
528
|
|
|
501
529
|
const goals = (Array.isArray(opts.goals) ? opts.goals : []).filter((g) => g && g.status === 'open' && g.id != null);
|
|
502
530
|
const byGoal = {};
|
|
@@ -512,6 +540,32 @@
|
|
|
512
540
|
const reviewHeld = reviewHeldOf(docket);
|
|
513
541
|
const queue = matters.filter((m) => m.building === 'review' && pinState(m.state) !== 'settled');
|
|
514
542
|
|
|
543
|
+
// the living street (task 1004685): the deeper civic row and its structure
|
|
544
|
+
const Dp = opts.depth ? depthKit() : null;
|
|
545
|
+
const deep = !!Dp;
|
|
546
|
+
const TD = deep ? Object.assign({}, T, { depth: Dp.DEPTH.side }) : T;
|
|
547
|
+
const living = deep && opts.living && Array.isArray(opts.living.buildings) ? opts.living : null;
|
|
548
|
+
const lb = {};
|
|
549
|
+
if (living) living.buildings.forEach((b) => { if (b && b.key) lb[b.key] = b; });
|
|
550
|
+
const struct = {};
|
|
551
|
+
const askById = {};
|
|
552
|
+
if (living && living.city && Array.isArray(living.city.asks)) living.city.asks.forEach((a) => { if (a && a.id) askById[a.id] = a; });
|
|
553
|
+
// SALMON ONLY FOR WHAT NEEDS YOU: on the living street a matter the government
|
|
554
|
+
// routes to another seat waits on someone else, so its pin and its windows stand
|
|
555
|
+
// in ink (the docket's own 'needs' is the viewer's before anyone was seated)
|
|
556
|
+
const seen = living ? matters.map((m) => (m.state === 'needs' && askById[m.id] && !askById[m.id].needs_you ? Object.assign({}, m, { state: 'moving' }) : m)) : matters;
|
|
557
|
+
const boardSeats = Math.max(1, ((bInfo.charter && Array.isArray(bInfo.charter.holders)) ? bInfo.charter.holders.length : 0) || 5);
|
|
558
|
+
const depthCtx = {
|
|
559
|
+
// a vote's tally: its matter's (real) or its ask's, over the seats that decide it
|
|
560
|
+
tallyOf: (c) => {
|
|
561
|
+
const m = c.sitting_id != null ? matters.find((x) => x.source === 'board' && x.ref && String(x.ref.id) === String(c.sitting_id)) : null;
|
|
562
|
+
const t = (m && m.tally) || (c.ask_id && askById[c.ask_id] && askById[c.ask_id].tally) || null;
|
|
563
|
+
const f = t ? Math.max(0, Number(t.for) || 0) : 0, ag = t ? Math.max(0, Number(t.against) || 0) : 0, op = t ? Math.max(0, Number(t.open) || 0) : 0;
|
|
564
|
+
const seats = Math.max(boardSeats, f + ag + op, 1);
|
|
565
|
+
return { for: f, against: ag, seats: Math.min(seats, 12) };
|
|
566
|
+
},
|
|
567
|
+
};
|
|
568
|
+
|
|
515
569
|
// the road
|
|
516
570
|
const flat = [];
|
|
517
571
|
push(flat, rect(32, G, W - 64, 30), T.road, 'none');
|
|
@@ -525,7 +579,16 @@
|
|
|
525
579
|
if (T.shadow !== 'none') push(flat, ellipse(s.x + s.w / 2 + 4, G + 2, s.w * 0.56, 5), T.shadow, 'none');
|
|
526
580
|
if (s.k) {
|
|
527
581
|
const polys = [];
|
|
528
|
-
|
|
582
|
+
const st = { evening: evening, reviewHeld: reviewHeld, fixes: City.rulesAtWork(docket) };
|
|
583
|
+
if (deep) {
|
|
584
|
+
// the structure stands ON the roof, in front of its deeper top face,
|
|
585
|
+
// so it is drawn after the recipe (its core's foot plugs into the roof)
|
|
586
|
+
const r = civic(polys, TD, s.k, s.x, G, s.w, st) + (Dp.DEPTH.side - 7);
|
|
587
|
+
const sp = Dp.structureOf(TD, s.k, s.x, lb[s.k] || null, depthCtx);
|
|
588
|
+
sp.polys.forEach((p) => polys.push(p));
|
|
589
|
+
roofs[s.k] = Math.max(r, sp.top);
|
|
590
|
+
struct[s.k] = sp;
|
|
591
|
+
} else roofs[s.k] = civic(polys, T, s.k, s.x, G, s.w, st);
|
|
529
592
|
blds.push({ key: s.k, polys: polys });
|
|
530
593
|
} else {
|
|
531
594
|
let wx = s.x + 2;
|
|
@@ -557,12 +620,21 @@
|
|
|
557
620
|
if (k === 'review') return !reviewHeld ? 'empty seat' : queue.length ? queue.length + ' waiting' : 'nothing waiting';
|
|
558
621
|
if (k === 'planning') return goals.length + (goals.length === 1 ? ' goal open' : ' goals open');
|
|
559
622
|
if (readingFor[k]) return readingFor[k].figure;
|
|
623
|
+
// on the living street a building's line counts as the header does (needs you
|
|
624
|
+
// is what waits on your seats; what the government routed elsewhere waits on others)
|
|
625
|
+
const b = living ? lb[k] : null;
|
|
626
|
+
if (b && Array.isArray(b.asks)) {
|
|
627
|
+
const you = b.asks.filter((a) => a && a.state === 'needs' && a.needs_you).length;
|
|
628
|
+
const others = b.asks.filter((a) => a && a.state === 'needs' && !a.needs_you).length;
|
|
629
|
+
if (you) return you + ' to do';
|
|
630
|
+
if (others) return others + (others === 1 ? ' waits on others' : ' wait on others');
|
|
631
|
+
}
|
|
560
632
|
return countsLine(bInfo[k] ? bInfo[k].counts : null);
|
|
561
633
|
}
|
|
562
634
|
const cols = segs.map((s) => s.k
|
|
563
635
|
? { key: s.k, x: s.x - 10, w: s.w + 20, name: s.k, title: s.k, state: civicState(s.k), strong: s.k === 'review' && !reviewHeld }
|
|
564
636
|
// districts stand 10 apart, so their names keep to their own plot (and ellipsis there)
|
|
565
|
-
: { key: 'd:' + s.d, x: s.x - 4, w: s.w + 8, name: s.title, title: s.title, state: s.more ? 'on the goals page' : districtLine(byGoal[s.d]), strong: false });
|
|
637
|
+
: { key: 'd:' + s.d, x: s.x - 4, w: s.w + 8, name: districtName(s.title), title: s.title, state: s.more ? 'on the goals page' : districtLine(byGoal[s.d]), strong: false });
|
|
566
638
|
const groups = GROUP_NAMES.map((n, gi) => {
|
|
567
639
|
const mem = segs.filter((s) => s.g === gi);
|
|
568
640
|
const x0 = mem[0].x, x1 = mem[mem.length - 1].x + mem[mem.length - 1].w;
|
|
@@ -572,7 +644,7 @@
|
|
|
572
644
|
// one pin per building, in the tier of its most important matter (task 1004569)
|
|
573
645
|
const pins = [];
|
|
574
646
|
const byB = {};
|
|
575
|
-
|
|
647
|
+
seen.forEach((m) => { if (m.building !== 'review') (byB[m.building] = byB[m.building] || []).push(m); });
|
|
576
648
|
CIVIC_KEYS.forEach((k) => {
|
|
577
649
|
const bp = City.buildingPin(byB[k], now);
|
|
578
650
|
if (!bp) return;
|
|
@@ -616,20 +688,40 @@
|
|
|
616
688
|
return { key: k, label: label, figure: figure, line: line, building: b, href: buildingHref(b), hot: !!docket && (r.hot === true || bNeeds), here: !!here && here.kind === 'building' && here.key === b, aria: label + ': ' + figure + '. Go to ' + b };
|
|
617
689
|
});
|
|
618
690
|
|
|
619
|
-
const moving = Math.max(0, Number(
|
|
691
|
+
const moving = shared ? (sc && sc.moving != null ? Math.max(0, Number(sc.moving) || 0) : null) : Math.max(0, Number(counts.moving) || 0);
|
|
692
|
+
const settled = Math.max(0, Number(counts.settled) || 0);
|
|
620
693
|
const share = monthShare(opts.earnings, now);
|
|
621
694
|
const pinCount = { needs: 0, moving: 0, settled: 0 };
|
|
622
695
|
pins.forEach((p) => { const t = p.state === 'urgent' ? 'needs' : p.state; if (pinCount[t] != null) pinCount[t] += 1; });
|
|
623
|
-
|
|
696
|
+
const hudNeeds = !docket || (shared && (!sc || sc.on_you == null)) ? '—' : String(needs);
|
|
697
|
+
const out = {
|
|
624
698
|
mode: mode, width: W, evening: evening, reviewHeld: reviewHeld,
|
|
625
699
|
flat: flat, blds: blds, crates: crates, cols: cols, groups: groups, pins: pins, doors: doors, signs: signs,
|
|
626
|
-
hud: { needs: docket
|
|
700
|
+
hud: { needs: hudNeeds, moving: !docket || moving == null ? '—' : String(moving), share: money(share), hot: hudNeeds !== '—' && needs > 0, shared: shared },
|
|
627
701
|
line: docket ? String(docket.line || '') : FALLBACK_LINE,
|
|
628
|
-
eveningLine: 'this month · ' + moving + ' moving · ' + settled + ' settled · ' + money(share) + ' to your share',
|
|
702
|
+
eveningLine: 'this month · ' + (moving == null ? '—' : moving) + ' moving · ' + settled + ' settled · ' + money(share) + ' to your share',
|
|
629
703
|
label: 'The street: the nine civic buildings in the order work moves, and '
|
|
630
704
|
+ (goals.length === 1 ? 'one district' : goals.length + ' districts') + ', one for each open goal'
|
|
631
705
|
+ (docket ? '. Pins: ' + pinCount.needs + ' need you, ' + pinCount.moving + ' moving, ' + pinCount.settled + ' settled.' : '.'),
|
|
632
706
|
};
|
|
707
|
+
if (!deep) return out;
|
|
708
|
+
// the living street: the depth layers, the deck behind "need you", and a
|
|
709
|
+
// door that says who holds its building
|
|
710
|
+
out.depth = Dp.depthOf({ segs: segs, at: at, roofs: roofs, struct: struct, living: living, lb: lb, docket: docket, goals: goals, byGoal: byGoal, roster: opts.roster || null, W: W, now: now, matters: living ? seen : null });
|
|
711
|
+
out.hud.deck = { href: Dp.DECK_HREF, aria: hudNeeds !== '—' ? needs + ' to do. Decide them in a row.' : 'Decide what is yours to do, in a row.' };
|
|
712
|
+
out.label += ' ' + out.depth.label;
|
|
713
|
+
// who holds it: a Lead, or the owner's seat — the real read (GET /government/leads,
|
|
714
|
+
// task 1004709); without it, nobody is named
|
|
715
|
+
if (living && living.seats_real) {
|
|
716
|
+
const viewerId = living.viewer ? String(living.viewer.id) : null;
|
|
717
|
+
out.doors.forEach((d) => {
|
|
718
|
+
const b = lb[d.key];
|
|
719
|
+
if (!b) return;
|
|
720
|
+
const who = b.lead ? (viewerId && String(b.lead.id) === viewerId ? 'you' : String(b.lead.name || 'its Lead')) : 'the owner';
|
|
721
|
+
d.aria += b.lead ? ', led by ' + who : ', held by ' + who;
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
return out;
|
|
633
725
|
}
|
|
634
726
|
|
|
635
727
|
// The short card a pin opens (task 1004528 makes it act). null for an unknown key.
|
|
@@ -657,7 +749,8 @@
|
|
|
657
749
|
theme: theme, push: push, rect: rect, ellipse: ellipse, box: box, gable: gable, sawtooth: sawtooth, tree: tree, light: light, civic: civic, work: work,
|
|
658
750
|
layout: layout, districtWidth: districtWidth, districtItems: districtItems, districtLine: districtLine, countsLine: countsLine,
|
|
659
751
|
whenWord: whenWord, reviewHeldOf: reviewHeldOf, monthShare: monthShare, money: money, parseHash: parseHash, buildingHref: buildingHref, districtHref: districtHref,
|
|
660
|
-
streetModel: streetModel, cardFor: cardFor, placeLabels: placeLabels, labelBox: labelBox, shownLabels: shownLabels, pinLabel: pinLabel,
|
|
752
|
+
streetModel: streetModel, cardFor: cardFor, districtName: districtName, placeLabels: placeLabels, labelBox: labelBox, shownLabels: shownLabels, pinLabel: pinLabel,
|
|
753
|
+
count: count, MORE_KEY: MORE_KEY,
|
|
661
754
|
G: G, SEGS: SEGS, GROUP_NAMES: GROUP_NAMES, CIVIC_KEYS: CIVIC_KEYS, READING_KEYS: READING_KEYS, ARTBOARD_W: ARTBOARD_W,
|
|
662
755
|
PIN_W: PIN_W, PIN_LABEL: PIN_LABEL, LABEL_MAX_W: LABEL_MAX_W, PIN_STACK: PIN_STACK, MAX_CRATES: MAX_CRATES, MAX_DISTRICTS: MAX_DISTRICTS, GOALS_HREF: GOALS_HREF, FALLBACK_LINE: FALLBACK_LINE, HOME_HREF: '/builders/city',
|
|
663
756
|
};
|
|
@@ -183,13 +183,25 @@
|
|
|
183
183
|
const vacant = !!st.vacant;
|
|
184
184
|
const dark = T.bg === '#000000';
|
|
185
185
|
const X = 130, W = 460, FH = 52, GY = 676;
|
|
186
|
-
|
|
186
|
+
// THE LEAD'S OFFICE AND THE BUILD-OUTS (task 1004685, the living-city
|
|
187
|
+
// prototype). Opt-in: with no st.office and no st.works the drawing is the
|
|
188
|
+
// prototype's to the pixel (tests/hall_street_inside.mjs). The office is one
|
|
189
|
+
// storey on top of the building's floors; a floor being built (a live site)
|
|
190
|
+
// or proposed (a vote is open) takes the next empty storey; an annex or a
|
|
191
|
+
// district stands on the annex lot. officeDraw() and worksDraw() below.
|
|
192
|
+
const office = st.office || null;
|
|
193
|
+
const works = Array.isArray(st.works) ? st.works : [];
|
|
194
|
+
const sites = works.filter((w) => w && w.kind === 'floor');
|
|
195
|
+
const capFloors = Math.max(b.cap, b.floors.length + sites.length);
|
|
196
|
+
const occ = b.floors.length, cap = office || works.length ? capFloors + (office ? 1 : 0) : Math.max(b.cap, occ), top = GY - cap * FH;
|
|
197
|
+
const officeIdx = office ? cap - 1 : -1;
|
|
198
|
+
const officeSel = !!office && st.floor === 'office';
|
|
187
199
|
const firstPending = b.floors.findIndex((f) => f.status === 'pending' || f.status === 'proposed');
|
|
188
|
-
const pick = st.floor === null || st.floor === undefined ? null : Number(st.floor);
|
|
200
|
+
const pick = officeSel || st.floor === null || st.floor === undefined ? null : Number(st.floor);
|
|
189
201
|
const selFloor = pick === null || !isFinite(pick) ? (firstPending >= 0 ? firstPending : occ - 1) : Math.max(0, Math.min(pick, occ - 1));
|
|
190
202
|
const M2 = dark
|
|
191
|
-
? { slab: '#4a4a52', wall: '#2c2c32', room: '#121215', roomLit: '#2b2219', lamp: '#55565a', lampLit: '#ffd9a8', glow: 'rgba(255,217,168,0.10)', pane: 'rgba(150,170,255,0.22)', paneLit: 'rgba(255,214,170,0.55)', kit: 'rgba(255,255,255,0.34)', kitFill: 'rgba(255,255,255,0.05)', name: '#ffffff', line: '#c0c0c0', faint: '#8e8e93' }
|
|
192
|
-
: { slab: '#b9b8be', wall: '#e2e1e5', room: '#f8f7f9', roomLit: '#fdf0e2', lamp: '#a9a8ad', lampLit: '#f0a86a', glow: 'rgba(240,168,106,0.12)', pane: 'rgba(91,111,208,0.20)', paneLit: 'rgba(240,168,106,0.45)', kit: 'rgba(23,24,26,0.40)', kitFill: 'rgba(23,24,26,0.05)', name: '#17181a', line: '#55565a', faint: '#65656a' };
|
|
203
|
+
? { slab: '#4a4a52', wall: '#2c2c32', room: '#121215', roomSel: '#202025', lampSel: '#e6e6ea', glowSel: 'rgba(255,255,255,0.07)', paneSel: 'rgba(220,224,240,0.40)', roomLit: '#2b2219', lamp: '#55565a', lampLit: '#ffd9a8', glow: 'rgba(255,217,168,0.10)', pane: 'rgba(150,170,255,0.22)', paneLit: 'rgba(255,214,170,0.55)', kit: 'rgba(255,255,255,0.34)', kitFill: 'rgba(255,255,255,0.05)', name: '#ffffff', line: '#c0c0c0', faint: '#8e8e93' }
|
|
204
|
+
: { slab: '#b9b8be', wall: '#e2e1e5', room: '#f8f7f9', roomSel: '#ebeaee', lampSel: '#55565a', glowSel: 'rgba(23,24,26,0.06)', paneSel: 'rgba(23,24,26,0.22)', roomLit: '#fdf0e2', lamp: '#a9a8ad', lampLit: '#f0a86a', glow: 'rgba(240,168,106,0.12)', pane: 'rgba(91,111,208,0.20)', paneLit: 'rgba(240,168,106,0.45)', kit: 'rgba(23,24,26,0.40)', kitFill: 'rgba(23,24,26,0.05)', name: '#17181a', line: '#55565a', faint: '#65656a' };
|
|
193
205
|
if (key === 'review' && st.reviewHeld) { M2.room = dark ? '#121722' : '#f1f3fb'; M2.pane = dark ? 'rgba(150,170,255,0.35)' : 'rgba(91,111,208,0.30)'; }
|
|
194
206
|
const shapes = [], texts = [], syms = [];
|
|
195
207
|
const sans = "Manrope, 'Segoe UI', system-ui, sans-serif", mono = "'JetBrains Mono', ui-monospace, Consolas, monospace";
|
|
@@ -205,20 +217,26 @@
|
|
|
205
217
|
}
|
|
206
218
|
for (let i = 0; i < cap; i++) {
|
|
207
219
|
const y0 = GY - (i + 1) * FH, y1 = y0 + FH, fl = b.floors[i];
|
|
208
|
-
|
|
220
|
+
if (i === officeIdx) { officeDraw(y0, y1, i); continue; }
|
|
221
|
+
if (!fl && i >= occ && sites[i - occ]) { siteDraw(sites[i - occ], y0, y1, i); continue; }
|
|
222
|
+
const used = !!fl, sel = used && i === selFloor && !officeSel;
|
|
209
223
|
const pend = used && fl.status === 'pending';
|
|
210
224
|
const prop = used && (fl.status === 'proposed' || QUIET[fl.status] !== undefined);
|
|
211
225
|
// a floor you cleared this visit lights its windows and shows a tick (task 1004558)
|
|
212
226
|
const clr = used && Array.isArray(st.cleared) && st.cleared.indexOf(i) >= 0;
|
|
213
|
-
|
|
227
|
+
// the chosen floor lights in ink (a cool, neutral light), never the warm lit wash:
|
|
228
|
+
// salmon and its warmth mean "needs you" only (task 1004718). A floor you cleared,
|
|
229
|
+
// or every floor in the evening, still lights warm.
|
|
230
|
+
const lit = clr || (used && st.evening), on = lit || sel;
|
|
231
|
+
const room = lit ? M2.roomLit : sel ? M2.roomSel : M2.room;
|
|
214
232
|
push(shapes, rect(X - 6, y1 - 5, W + 12, 5), fillOr(M2.slab), line, dash);
|
|
215
|
-
push(shapes, rect(X + 9, y0, W - 18, FH - 5), fillOr(
|
|
233
|
+
push(shapes, rect(X + 9, y0, W - 18, FH - 5), fillOr(room), line, dash);
|
|
216
234
|
[X, X + W - 9].forEach((wx) => {
|
|
217
235
|
push(shapes, rect(wx, y0, 9, FH - 5), fillOr(M2.wall), line, dash);
|
|
218
|
-
if (!ghost) push(shapes, rect(wx + 3, y0 + 9, 3, FH - 23), lit ? M2.paneLit : M2.pane, 'none');
|
|
236
|
+
if (!ghost) push(shapes, rect(wx + 3, y0 + 9, 3, FH - 23), lit ? M2.paneLit : sel ? M2.paneSel : M2.pane, 'none');
|
|
219
237
|
});
|
|
220
|
-
if (i === 0 && !ghost) push(shapes, rect(X, y1 - 33, 9, 28),
|
|
221
|
-
txt(X - 50, y0 + 30, String(i + 1).padStart(2, '0'), sel ?
|
|
238
|
+
if (i === 0 && !ghost) push(shapes, rect(X, y1 - 33, 9, 28), room, 'none');
|
|
239
|
+
txt(X - 50, y0 + 30, String(i + 1).padStart(2, '0'), sel ? M2.name : M2.faint, 11, 400, 'end', mono, '0.08em');
|
|
222
240
|
if (!used) {
|
|
223
241
|
if (!ghost) txt(X + 24, y0 + 30, 'empty floor', M2.faint, 12.5, 400);
|
|
224
242
|
continue;
|
|
@@ -228,14 +246,14 @@
|
|
|
228
246
|
[0.2, 0.45, 0.8].forEach((f) => {
|
|
229
247
|
const lx = Math.round(X + W * f);
|
|
230
248
|
push(shapes, 'M' + lx + ' ' + y0 + 'V' + (y0 + 7), 'none', ghost ? T.ghost.s : M2.kit);
|
|
231
|
-
push(shapes, ellipse(lx, y0 + 9, 2.4, 2.4), lit ? M2.lampLit : M2.lamp, 'none');
|
|
232
|
-
if (
|
|
249
|
+
push(shapes, ellipse(lx, y0 + 9, 2.4, 2.4), lit ? M2.lampLit : sel ? M2.lampSel : M2.lamp, 'none');
|
|
250
|
+
if (on) push(shapes, ellipse(lx, y0 + 12, 12, 7), lit ? M2.glow : M2.glowSel, 'none');
|
|
233
251
|
});
|
|
234
252
|
// the floor's sign: a plate on the wall carrying the one symbol that says what this floor is
|
|
235
253
|
const sx0 = px + 16, sy0 = y0 + 5;
|
|
236
254
|
push(shapes, rect(sx0 - 4, sy0 - 1, 44, 40), ghost ? 'none' : M2.wall, ghost ? T.ghost.s : M2.kit, dash);
|
|
237
255
|
const sp = symbolPath(floorSymbol(fl.name));
|
|
238
|
-
syms.push({ key: 's' + i, x: sx0 + 2, y: sy0 + 2, k: 1.333, d: sp.d, f: sp.f, c: sel ?
|
|
256
|
+
syms.push({ key: 's' + i, x: sx0 + 2, y: sy0 + 2, k: 1.333, d: sp.d, f: sp.f, c: sel ? M2.name : pend ? T.accentText : (ghost || prop ? M2.faint : M2.line), w: sel ? 1.9 : 1.5 });
|
|
239
257
|
if (pend) push(shapes, rect(X + 11, y0 + 2, W - 22, FH - 9), 'none', '#fb9569', '4 4');
|
|
240
258
|
txt(X + 24, y0 + 24, cut(fl.name, 34), ghost || prop ? M2.faint : M2.name, 14.5, 500);
|
|
241
259
|
txt(X + 24, y0 + 40, cut(fl.line, 44), pend ? T.accentText : M2.line, 12, 400);
|
|
@@ -244,13 +262,96 @@
|
|
|
244
262
|
else if (pend) txt(X + W - 22, y0 + 32, 'being decided', T.accentText, 11.5, 500, 'end');
|
|
245
263
|
else if (QUIET[fl.status]) txt(X + W - 22, y0 + 32, QUIET[fl.status], M2.faint, 11.5, 400, 'end');
|
|
246
264
|
}
|
|
247
|
-
// the chosen floor gets
|
|
248
|
-
if (
|
|
265
|
+
// the chosen floor gets an ink frame, and a lift runs in its shaft beside the building to meet it
|
|
266
|
+
if (officeSel) push(shapes, rect(X - 3, GY - (officeIdx + 1) * FH - 2, W + 6, FH - 1), 'none', M2.name, 'none', 1.5);
|
|
267
|
+
else if (b.floors[selFloor]) push(shapes, rect(X - 3, GY - (selFloor + 1) * FH - 2, W + 6, FH - 1), 'none', M2.name, 'none', 1.5);
|
|
249
268
|
push(shapes, rect(X - 40, top, 22, cap * FH), 'none', ghost ? T.ghost.s : M2.kit, ghost ? '4 4' : 'none');
|
|
250
269
|
let rungs = '';
|
|
251
270
|
for (let i = 1; i < cap; i++) rungs += 'M' + (X - 40) + ' ' + (GY - i * FH) + 'h22';
|
|
252
271
|
push(shapes, rungs, 'none', M2.kit, '2 3');
|
|
253
|
-
|
|
272
|
+
works.forEach((w) => { if (w && (w.kind === 'annex' || w.kind === 'district')) lotDraw(w); });
|
|
273
|
+
const floors = b.floors.map((f, i) => ({ key: f.name, index: i, x: X, y: GY - (i + 1) * FH, w: W, h: FH - 5, aria: 'Open ' + f.name, selected: i === selFloor && !officeSel }));
|
|
274
|
+
if (office) floors.push({ key: 'office', index: 'office', x: X, y: GY - (officeIdx + 1) * FH, w: W, h: FH - 5, aria: 'Open the Lead\'s office', selected: officeSel });
|
|
275
|
+
sites.forEach((s, j) => floors.push({ key: 'site:' + j, index: 'office', x: X, y: GY - (occ + j + 1) * FH, w: W, h: FH - 5, aria: 'Open the build: ' + s.label, selected: false }));
|
|
276
|
+
|
|
277
|
+
// ---- the office, a floor going up, and the annex lot (tasks 1004685, 1004711) ----
|
|
278
|
+
function officeDraw(y0, y1, i) {
|
|
279
|
+
// THE LEAD'S OFFICE: its lamps lit while a Lead sits in it, a dashed vacancy
|
|
280
|
+
// while the owner holds the building; its committees are rooms on its back
|
|
281
|
+
// wall (a room the Lead has asked for, dashed); the sign is the hall.
|
|
282
|
+
const seated = !!office.seated, oDash = seated ? 'none' : '4 4';
|
|
283
|
+
push(shapes, rect(X - 6, y1 - 5, W + 12, 5), M2.slab, 'none');
|
|
284
|
+
push(shapes, rect(X + 9, y0, W - 18, FH - 5), officeSel ? M2.roomSel : seated ? M2.room : 'none', seated ? 'none' : T.ghost.s, oDash);
|
|
285
|
+
[X, X + W - 9].forEach((wx) => {
|
|
286
|
+
push(shapes, rect(wx, y0, 9, FH - 5), M2.wall, 'none');
|
|
287
|
+
push(shapes, rect(wx + 3, y0 + 9, 3, FH - 23), seated ? M2.paneLit : M2.pane, 'none');
|
|
288
|
+
});
|
|
289
|
+
txt(X - 50, y0 + 30, String(i + 1).padStart(2, '0'), officeSel ? M2.name : M2.faint, 11, 400, 'end', mono, '0.08em');
|
|
290
|
+
const px = Math.round(X + W * 0.62);
|
|
291
|
+
push(shapes, 'M' + px + ' ' + y0 + 'V' + (y1 - 25), 'none', seated ? M2.kit : T.ghost.s, oDash);
|
|
292
|
+
[0.2, 0.45].forEach((f) => {
|
|
293
|
+
const lx = Math.round(X + W * f);
|
|
294
|
+
push(shapes, 'M' + lx + ' ' + y0 + 'V' + (y0 + 7), 'none', M2.kit);
|
|
295
|
+
push(shapes, ellipse(lx, y0 + 9, 2.4, 2.4), seated ? M2.lampLit : M2.lamp, 'none');
|
|
296
|
+
if (seated) push(shapes, ellipse(lx, y0 + 12, 12, 7), M2.glow, 'none');
|
|
297
|
+
});
|
|
298
|
+
const sx0 = px + 16, sy0 = y0 + 5;
|
|
299
|
+
push(shapes, rect(sx0 - 4, sy0 - 1, 44, 40), seated ? M2.wall : 'none', seated ? M2.kit : T.ghost.s, oDash);
|
|
300
|
+
const sp = symbolPath('hall');
|
|
301
|
+
syms.push({ key: 'so', x: sx0 + 2, y: sy0 + 2, k: 1.333, d: sp.d, f: sp.f, c: officeSel ? M2.name : seated ? M2.line : M2.faint, w: officeSel ? 1.9 : 1.5 });
|
|
302
|
+
txt(X + 24, y0 + 24, 'Lead\'s office', seated ? M2.name : M2.faint, 14.5, 500);
|
|
303
|
+
txt(X + 24, y0 + 40, cut(String(office.line || ''), 44), M2.line, 12, 400);
|
|
304
|
+
const rooms = Array.isArray(office.rooms) ? office.rooms.slice(0, 2) : [];
|
|
305
|
+
rooms.forEach((r, j) => {
|
|
306
|
+
const rx = sx0 + 48 + j * 48;
|
|
307
|
+
push(shapes, rect(rx, y0 + 6, 44, FH - 17), r.proposed ? 'none' : M2.kitFill, r.proposed ? T.ghost.s : M2.kit, r.proposed ? '3 3' : 'none');
|
|
308
|
+
txt(rx + 22, y0 + 29, cut(String(r.name || ''), 7), r.proposed ? M2.faint : M2.line, 9.5, 400, 'middle', mono);
|
|
309
|
+
});
|
|
310
|
+
if (!rooms.length && office.ini) txt(X + W - 22, y0 + 34, office.ini, seated ? M2.name : M2.faint, 20, 300, 'end', sans, '-0.02em');
|
|
311
|
+
}
|
|
312
|
+
function siteDraw(site, y0, y1, i) {
|
|
313
|
+
// A FLOOR GOING UP: a live site where the form of government lets it build (the
|
|
314
|
+
// floor fills from its slab as the work goes), a dashed ghost while a vote
|
|
315
|
+
// is open on it. The crane over a live site is the page's (CSS motion).
|
|
316
|
+
const live = site.state === 'site';
|
|
317
|
+
const p = live && site.progress != null ? Math.max(0, Math.min(1, Number(site.progress) || 0)) : null;
|
|
318
|
+
push(shapes, rect(X - 6, y1 - 5, W + 12, 5), live ? M2.slab : 'none', live ? 'none' : T.ghost.s, live ? 'none' : '4 4');
|
|
319
|
+
if (p) push(shapes, rect(X + 9, y1 - 5 - Math.round((FH - 5) * p), W - 18, Math.round((FH - 5) * p)), M2.kitFill, 'none');
|
|
320
|
+
push(shapes, rect(X + 9, y0, W - 18, FH - 5), 'none', live ? M2.kit : T.ghost.s, '4 3');
|
|
321
|
+
const px = Math.round(X + W * 0.62);
|
|
322
|
+
let br = '';
|
|
323
|
+
for (let bx = px; bx + 30 <= X + W - 70; bx += 30) br += 'M' + bx + ' ' + (y1 - 5) + 'L' + (bx + 30) + ' ' + y0 + 'M' + bx + ' ' + y0 + 'L' + (bx + 30) + ' ' + (y1 - 5);
|
|
324
|
+
push(shapes, br, 'none', live ? M2.kit : T.ghost.s, live ? 'none' : '3 4');
|
|
325
|
+
txt(X - 50, y0 + 30, String(i + 1).padStart(2, '0'), M2.faint, 11, 400, 'end', mono, '0.08em');
|
|
326
|
+
txt(X + 24, y0 + 24, cut(String(site.label || 'A new floor'), 30), live ? M2.name : M2.faint, 14.5, 500);
|
|
327
|
+
txt(X + 24, y0 + 40, cut(String(site.line || (live ? 'being built' : 'a vote is open')), 34), M2.line, 12, 400);
|
|
328
|
+
if (p != null) txt(X + W - 22, y0 + 34, Math.round(p * 100) + '%', M2.name, 20, 300, 'end', sans, '-0.02em');
|
|
329
|
+
else txt(X + W - 22, y0 + 32, live ? 'going up' : 'vote open', M2.faint, 11.5, 400, 'end');
|
|
330
|
+
}
|
|
331
|
+
function lotDraw(w) {
|
|
332
|
+
// THE ANNEX LOT: a proposed annex (or district) is a dashed-glass ghost with
|
|
333
|
+
// a parked crane, joined by a dashed bridge; once built it stands solid.
|
|
334
|
+
const built = w.state === 'built';
|
|
335
|
+
const ax = 628, aw = 160;
|
|
336
|
+
const tone = built ? T.civic : T.glass;
|
|
337
|
+
const dashLot = built ? 'none' : '5 4';
|
|
338
|
+
const blocks = w.kind === 'district' ? [[ax + 6, 44, 96], [ax + 58, 44, 136], [ax + 110, 44, 72]] : [[ax, aw, 156]];
|
|
339
|
+
blocks.forEach((bk) => {
|
|
340
|
+
push(shapes, rect(bk[0], GY - bk[2], bk[1], bk[2]), tone.f, tone.s, dashLot);
|
|
341
|
+
let panes = '';
|
|
342
|
+
for (let yy = GY - bk[2] + 14; yy < GY - 14; yy += 22) for (let xx = bk[0] + 10; xx + 10 <= bk[0] + bk[1] - 8; xx += 18) panes += 'M' + xx + ' ' + yy + 'h8v10h-8Z';
|
|
343
|
+
push(shapes, panes, built ? M2.paneLit : M2.pane, built ? 'none' : tone.s, built ? 'none' : '2 2');
|
|
344
|
+
});
|
|
345
|
+
push(shapes, rect(X + W + 16, GY - 64, ax - (X + W + 16), 10), built ? T.civic.f : 'none', built ? T.civic.s : T.glass.s, dashLot);
|
|
346
|
+
const topY = GY - (w.kind === 'district' ? 136 : 156);
|
|
347
|
+
if (!built) {
|
|
348
|
+
// the parked crane: a mast, its jib and a hook, standing still
|
|
349
|
+
const mx = ax + aw - 14, jy = topY - 64;
|
|
350
|
+
push(shapes, 'M' + mx + ' ' + GY + 'V' + jy + 'M' + (ax + 8) + ' ' + jy + 'H' + (mx + 14) + 'M' + (ax + 40) + ' ' + jy + 'V' + (topY - 28) + 'M' + mx + ' ' + (jy - 14) + 'L' + (ax + 8) + ' ' + jy + 'M' + mx + ' ' + (jy - 14) + 'L' + (mx + 14) + ' ' + jy, 'none', M2.kit);
|
|
351
|
+
}
|
|
352
|
+
txt(ax + aw / 2, topY - (built ? 26 : 92), cut(String(w.label || 'An annex'), 22), built ? M2.name : M2.faint, 12.5, 500, 'middle');
|
|
353
|
+
txt(ax + aw / 2, topY - (built ? 10 : 76), built ? 'built' : String(w.line || 'proposed · a vote is open'), M2.faint, 11, 400, 'middle');
|
|
354
|
+
}
|
|
254
355
|
const record = Array.isArray(b.record) ? b.record : [];
|
|
255
356
|
const selDrawer = record.length ? Math.max(0, Math.min(st.drawer === null || st.drawer === undefined ? 0 : Number(st.drawer) || 0, record.length - 1)) : -1;
|
|
256
357
|
const seatOf = (s) => s === 'you'
|
|
@@ -258,14 +359,16 @@
|
|
|
258
359
|
: s === '—' ? { key: s, init: '', bg: 'transparent', fg: T.faint, border: '1px dashed ' + T.seatOpen }
|
|
259
360
|
: { key: s, init: s, bg: T.seatFor.bg, fg: T.seatFor.fg, border: 'none' };
|
|
260
361
|
const g = City.glyph(b.rule, 9, 9, 16);
|
|
261
|
-
|
|
362
|
+
// the floors in use, out of the floors there are (the office is not one)
|
|
363
|
+
const capOf = office || works.length ? capFloors : cap;
|
|
364
|
+
const pct = Math.round(occ / capOf * 100);
|
|
262
365
|
const f = b.floors[selFloor];
|
|
263
366
|
const quietWord = f && QUIET[f.status] !== undefined ? ' · ' + (f.status === 'proposed' ? 'proposed' : QUIET[f.status]) : '';
|
|
264
367
|
return {
|
|
265
368
|
kicker: b.group + ' · ' + b.name.toLowerCase(), name: b.name, holds: b.holds,
|
|
266
369
|
seats: b.seats.map(seatOf), whoText: b.who, gf: g.fill, gl: g.line, howText: b.how,
|
|
267
370
|
fill: pct, fillColor: pct >= 80 ? '#fb9569' : T.line,
|
|
268
|
-
growth: (vacant ? 'Not built yet. ' : occ + ' of ' +
|
|
371
|
+
growth: (vacant ? 'Not built yet. ' : occ + ' of ' + capOf + ' floors in use. ') + b.growth,
|
|
269
372
|
openKicker: 'floor ' + (selFloor + 1) + (f && f.status === 'pending' ? ' · being decided' : quietWord),
|
|
270
373
|
sym: f ? Object.assign(symbolPath(floorSymbol(f.name)), { c: '#fb9569' }) : { d: '', f: '', c: 'none' },
|
|
271
374
|
flipA: ((st.picks || 0) % 2) === 0, flipB: ((st.picks || 0) % 2) === 1,
|
|
@@ -273,9 +376,12 @@
|
|
|
273
376
|
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 })),
|
|
274
377
|
drawerItems: selDrawer >= 0 ? record[selDrawer].items.map((it, i) => ({ key: 'r' + i, text: it[0], meta: it[1] })) : [],
|
|
275
378
|
onFile: b.onFile, shapes: shapes, texts: texts, syms: syms, floors: floors,
|
|
276
|
-
lift: { x: X - 38, y: GY - FH + 6, ty: -(selFloor * FH), h: FH - 16 },
|
|
277
|
-
hasAnnex: !!b.annex, annex: b.annex ? { x: 628, y: GY - 156, w: 160, h: 156, text: b.annex } : { x: 0, y: 0, w: 0, h: 0, text: '' },
|
|
379
|
+
lift: { x: X - 38, y: GY - FH + 6, ty: -((officeSel ? officeIdx : selFloor) * FH), h: FH - 16 },
|
|
380
|
+
hasAnnex: !!b.annex && !works.some((w) => w && (w.kind === 'annex' || w.kind === 'district')), annex: b.annex ? { x: 628, y: GY - 156, w: 160, h: 156, text: b.annex } : { x: 0, y: 0, w: 0, h: 0, text: '' },
|
|
278
381
|
selFloor: selFloor, selDrawer: selDrawer, cap: cap, occ: occ,
|
|
382
|
+
officeSel: officeSel, officeIdx: officeIdx, capFloors: capOf, builtSel: !officeSel && !!(f && f.built),
|
|
383
|
+
// where a live site stands, for the page's crane (CSS motion over the drawing)
|
|
384
|
+
sites: sites.map((s, j) => ({ label: s.label, state: s.state, x: X, y: GY - (occ + j + 1) * FH, w: W, h: FH - 5 })),
|
|
279
385
|
};
|
|
280
386
|
}
|
|
281
387
|
|
|
@@ -539,6 +645,11 @@
|
|
|
539
645
|
// The building interior as data: civicInfo() drawn by buildingView().
|
|
540
646
|
// input { key, docket, goals, mode, now, floor, drawer, picks, cleared }
|
|
541
647
|
// cleared: the floor indexes the viewer cleared at the desk this visit (street-desk.js)
|
|
648
|
+
// The living-city prototype (task 1004685, street-office.js) may add:
|
|
649
|
+
// office { seated, line, ini, rooms[{ name, proposed }] } — the Lead's office on top
|
|
650
|
+
// works [{ kind: floor|annex|district, state: site|proposed|built, label, line, progress }]
|
|
651
|
+
// extraFloors [{ name, line }] — floors a build-out put up (GET /government/leads structure)
|
|
652
|
+
// floor may be 'office'
|
|
542
653
|
function buildingModel(input) {
|
|
543
654
|
const o = input || {};
|
|
544
655
|
if (S.CIVIC_KEYS.indexOf(o.key) < 0) return null;
|
|
@@ -546,11 +657,15 @@
|
|
|
546
657
|
const T = S.theme(mode);
|
|
547
658
|
const goals = (Array.isArray(o.goals) ? o.goals : []).filter((g) => g && g.status === 'open');
|
|
548
659
|
const b = civicInfo(o.key, { docket: o.docket, goals: goals, now: o.now });
|
|
660
|
+
if (Array.isArray(o.extraFloors) && o.extraFloors.length) {
|
|
661
|
+
o.extraFloors.forEach((x) => { const fl = F(String(x.name || 'A new floor'), String(x.line || 'built by a build-out'), '', 'ok', [[String(x.line || 'built by a build-out'), 'now']]); fl.built = true; b.floors.push(fl); });
|
|
662
|
+
b.annex = !b.vacant && b.floors.length >= PLAN[o.key].cap ? PLAN[o.key].annex : null;
|
|
663
|
+
}
|
|
549
664
|
const docket = o.docket && Array.isArray(o.docket.matters) ? o.docket : null;
|
|
550
665
|
const evening = !!docket && !(docket.counts && Number(docket.counts.needs) > 0);
|
|
551
|
-
const bv = buildingView(T, o.key, b, { floor: o.floor, drawer: o.drawer, picks: o.picks, vacant: b.vacant, reviewHeld: !b.vacant, evening: evening, cleared: o.cleared });
|
|
666
|
+
const bv = buildingView(T, o.key, b, { floor: o.floor, drawer: o.drawer, picks: o.picks, vacant: b.vacant, reviewHeld: !b.vacant, evening: evening, cleared: o.cleared, office: o.office || null, works: o.works || null });
|
|
552
667
|
bv.key = o.key; bv.mode = mode; bv.vacant = b.vacant;
|
|
553
|
-
bv.label = b.name + ' in section: ' + b.floors.length + ' of ' + bv.
|
|
668
|
+
bv.label = b.name + ' in section: ' + b.floors.length + ' of ' + bv.capFloors + ' floors in use' + (o.office ? ', the Lead\'s office on top' : '') + '.';
|
|
554
669
|
return bv;
|
|
555
670
|
}
|
|
556
671
|
|