@bongos/core 1.21.167 → 1.21.169

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 (83) hide show
  1. package/.bongos-core.json +157 -72
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +8 -0
  4. package/clients/bongos-client/index.cjs +8 -0
  5. package/clients/bongos-client/index.d.ts +13 -0
  6. package/clients/bongos-client/index.mjs +8 -0
  7. package/docs/api/openapi.json +308 -5
  8. package/docs/api-reference.md +7 -3
  9. package/docs/architecture.md +2 -0
  10. package/docs/copy-inventory.md +21 -20
  11. package/docs/copy-registry.json +34 -25
  12. package/docs/module-api-changelog.md +5 -1
  13. package/docs/modules-contract.md +2 -1
  14. package/docs/page-inventory.json +3 -0
  15. package/docs/page-readings.json +430 -430
  16. package/modules/autonomy/governor-docket.js +10 -5
  17. package/modules/builder-settings/builder-needs.js +29 -3
  18. package/modules/copy-desk/guarded-lines.js +89 -0
  19. package/modules/copy-desk/guarded-lines.json +14 -0
  20. package/modules/copy-desk/module.json +2 -2
  21. package/modules/copy-desk/page-status.js +77 -7
  22. package/modules/copy-desk/pages.js +4 -0
  23. package/modules/copy-desk/pollers/stuck-page-digest.js +34 -0
  24. package/modules/copy-desk/routes/copy-desk.js +8 -0
  25. package/modules/copy-desk/stuck-tweaks.js +79 -0
  26. package/modules/copy-desk/tests/copy_no_cms.mjs +6 -1
  27. package/modules/copy-desk/tests/copy_page_status.mjs +2 -2
  28. package/modules/government/asks-db.js +38 -2
  29. package/modules/government/catalog.js +1 -1
  30. package/modules/government/docket.js +20 -2
  31. package/modules/government/migrations/government_032_ask_flags.sql +35 -0
  32. package/modules/government/routes/government.js +43 -1
  33. package/modules/hall-ui/public/approval-queue-lib.js +4 -0
  34. package/modules/hall-ui/public/approval-queue.js +1 -0
  35. package/modules/hall-ui/public/city-decide.js +642 -0
  36. package/modules/hall-ui/public/city-draw.js +14 -3
  37. package/modules/hall-ui/public/city-switchboard.js +141 -0
  38. package/modules/hall-ui/public/city.css +176 -10
  39. package/modules/hall-ui/public/city.html +8 -3
  40. package/modules/hall-ui/public/city.js +185 -38
  41. package/modules/hall-ui/public/tweak-editor-lib.js +21 -2
  42. package/modules/hall-ui/public/tweak-editor.css +2 -0
  43. package/modules/hall-ui/public/tweak-editor.js +3 -1
  44. package/modules/lifecycle/db-claims.js +20 -5
  45. package/modules/lifecycle/rebase-gate.js +78 -0
  46. package/modules/lifecycle/routes/claims.js +10 -2
  47. package/package-lock.json +2 -2
  48. package/package.json +1 -1
  49. package/release-notes.json +28 -0
  50. package/scripts/gds/ask.js +68 -1
  51. package/scripts/gds/claim.js +6 -1
  52. package/scripts/gds/copy-apply.js +2 -0
  53. package/src/bongos/module-promotion.js +219 -0
  54. package/src/bongos/module-store.js +47 -9
  55. package/src/bongos/route-rank-check.js +7 -0
  56. package/src/bongos/routes/me.js +11 -0
  57. package/src/bongos/routes/modules.js +72 -10
  58. package/src/module-api.js +1 -1
  59. package/src/platform-server.js +5 -0
  60. package/tests/claim_gate_ci_unblock.mjs +4 -0
  61. package/tests/claim_gate_rebase.mjs +56 -3
  62. package/tests/copy_desk_page_editor.mjs +1 -1
  63. package/tests/copy_desk_page_submit.mjs +42 -0
  64. package/tests/copy_guarded_lines.mjs +54 -0
  65. package/tests/copy_stuck_page_tweaks.mjs +118 -0
  66. package/tests/government_ask_flags.mjs +80 -0
  67. package/tests/government_seed.mjs +1 -0
  68. package/tests/government_treasury.mjs +1 -0
  69. package/tests/governor_docket.mjs +1 -1
  70. package/tests/hall_approval_queue.mjs +15 -0
  71. package/tests/hall_city.mjs +5 -4
  72. package/tests/hall_city_living.mjs +118 -12
  73. package/tests/hall_city_rewording.mjs +51 -0
  74. package/tests/hall_city_switchboard.mjs +63 -0
  75. package/tests/hall_gov_real_site.mjs +2 -2
  76. package/tests/hall_street_deck.mjs +6 -4
  77. package/tests/hall_street_depth.mjs +1 -1
  78. package/tests/hall_street_desk.mjs +5 -3
  79. package/tests/hall_tweak_editor.mjs +26 -0
  80. package/tests/module_channel_install.mjs +112 -0
  81. package/tests/module_promotion.mjs +290 -0
  82. package/tests/module_upgrade_updates.mjs +2 -2
  83. package/tests/rebase_gate_set_aside.mjs +73 -0
@@ -766,6 +766,17 @@
766
766
  let n = 0;
767
767
  CIVIC_SLOTS.forEach((s) => {
768
768
  const b = living.byKey[s[0]];
769
+ // Automation is a switchboard (owner, 2026-10-23): its marker is a switch,
770
+ // on or off, never a count of asks; it opens the board
771
+ if (s[0] === 'automation') {
772
+ const fence = living.city && living.city.fence;
773
+ const on = !!(fence && fence.enabled);
774
+ const at = P(s[1] + 1, s[2] + 1, civicTop[s[0]] + 8);
775
+ const nm = (b && b.name) || 'Automation';
776
+ pins.push({ key: 'switchboard', building: s[0], state: 'switch', on: on, count: 0, matters: [], title: 'the switchboard', aria: nm + ': switchboard, AI building: ' + (on ? 'on' : 'off'), x: r1(at[0] - 15), y: r1(at[1] - 52), h: 52, gf: null, gl: null, delay: -(n * 0.9), age: null, oldest_days: null });
777
+ n += 1;
778
+ return;
779
+ }
769
780
  const v = asksAt(b, answered);
770
781
  if (!v.all.length) return;
771
782
  let tier, list;
@@ -827,7 +838,7 @@
827
838
  const r = ((b && b.readings) || []).find((x) => x && x.status === 'live' && x.figure);
828
839
  if (r) return { text: String(r.figure), line: String(r.line || '') };
829
840
  const fence = living.city && living.city.fence;
830
- if (b && b.key === 'automation' && fence) return { text: fence.enabled ? 'running unattended' : 'unattended off', line: '' };
841
+ if (b && b.key === 'automation' && fence) return { text: fence.enabled ? 'AI building: on' : 'AI building: off', line: '' };
831
842
  if (b && b.agents > 0) return { text: b.agents + ' ' + words('agents_at_work', b.agents), line: '' };
832
843
  if (b && b.decision && b.decision.rule_words) return { text: String(b.decision.rule_words), line: '' };
833
844
  return { text: 'quiet', line: '' };
@@ -862,8 +873,8 @@
862
873
  }
863
874
  function ctaFor(onYou) {
864
875
  return onYou > 0
865
- ? { label: 'decide ' + onYou + ' in a row', href: STREET_HREF + '#deck', aria: 'Decide the ' + onYou + ' questions on you, one card at a time' }
866
- : { label: 'start governing', href: STREET_HREF, aria: 'Start governing: enter the street view' };
876
+ ? { label: 'decide ' + onYou + ' in a row', href: '#decide', aria: 'Decide the ' + onYou + ' questions on you, one card at a time' }
877
+ : { label: 'start governing', href: '#decide', aria: 'Start governing: everything that waits, one card at a time' };
867
878
  }
868
879
  function livingLine(onYou, others, agents) {
869
880
  const parts = [onYou ? onYou + ' ' + words('on_you', onYou) : 'nothing to do'];
@@ -0,0 +1,141 @@
1
+ // modules/hall-ui/public/city-switchboard.js — AUTOMATION AS A SWITCHBOARD
2
+ // (owner, 2026-10-23): the Automation building's card is a board of switches,
3
+ // not a queue of asks. Unattended building is a standing state, so it never
4
+ // counts as a to-do (modules/autonomy/governor-docket.js settles the fence
5
+ // matter); what the viewer may change is drawn here as switches.
6
+ //
7
+ // ONLY THE SWITCHES YOU CONTROL. Every row comes from the Automation desks the
8
+ // docket hands THIS viewer (GovDesk.rowsFor: automation.switch, .goals,
9
+ // .runners), and the server decides those rows and their acts per viewer from
10
+ // the switch's own permission. So the board never draws a switch the server
11
+ // would refuse; a viewer who controls none is told who does.
12
+ //
13
+ // Each flip sends the desk row's own request (GovMatterCard.request) through
14
+ // the page's sender, then the page re-reads. No innerHTML.
15
+ (function () {
16
+ 'use strict';
17
+
18
+ var FLOORS = [
19
+ { floor: 0, title: 'AI builds on its own (Autobongos)', sub: 'On: AI agents pick up tasks and build them by themselves, even overnight. Off: nothing is built unless a person starts it.' },
20
+ { floor: 1, title: 'Goals the AI may work on', sub: 'Turn a goal on to let the AI build it by itself. Goals left off are never touched.' },
21
+ { floor: 2, title: 'Computers doing the building', sub: 'Each builder\'s computer that runs the AI. Pause one without stopping the rest.' },
22
+ ];
23
+
24
+ function str(v) { return v == null ? '' : String(v); }
25
+
26
+ // The board as data, for one viewer. Pure over the docket.
27
+ function model(docket, ctx) {
28
+ var D = window.GovDesk;
29
+ var c = ctx || {};
30
+ if (!D || !D.rowsFor || !docket) return { groups: [], any: false };
31
+ var groups = FLOORS.map(function (f) {
32
+ var rows = D.rowsFor('automation', f.floor, docket, { now: c.now || Date.now(), viewerId: c.viewerId, goals: c.goals || [] });
33
+ var switches = [];
34
+ rows.forEach(function (r) {
35
+ var keys = r.actions.map(function (a) { return a.key; });
36
+ var choices = r.choices || {};
37
+ if (keys.indexOf('switch_on') >= 0) switches.push({ id: r.key, name: 'For the whole project', on: false, fact: firstFact(r), flip: { row: r, key: 'switch_on', input: {} } });
38
+ else if (keys.indexOf('switch_off') >= 0) switches.push({ id: r.key, name: 'For the whole project', on: true, fact: firstFact(r), flip: { row: r, key: 'switch_off', reasons: (choices.switch_off || {}).options || [] } });
39
+ else if (keys.indexOf('allow') >= 0) {
40
+ // the "allow a goal" row: every open goal not yet allowed is an OFF switch
41
+ ((choices.allow || {}).options || []).forEach(function (o) {
42
+ switches.push({ id: r.key + ':' + o.key, name: str(o.input && o.input.label) || o.label, on: false, fact: 'off: the AI leaves it alone', flip: { row: r, key: 'allow', input: o.input } });
43
+ });
44
+ } else if (keys.indexOf('disallow') >= 0) {
45
+ var first = keys.indexOf('first') < 0;
46
+ switches.push({ id: r.key, name: r.name, on: true, fact: first ? 'on, and worked first' : 'on: the AI may build it', first: first, flip: { row: r, key: 'disallow', input: {} }, star: first ? null : { row: r, key: 'first', input: {} } });
47
+ } else if (keys.indexOf('pause') >= 0) switches.push({ id: r.key, name: r.name, on: true, fact: firstFact(r), flip: { row: r, key: 'pause', reasons: (choices.pause || {}).options || [] } });
48
+ else if (keys.indexOf('resume') >= 0) switches.push({ id: r.key, name: r.name, on: false, fact: firstFact(r) || 'paused', flip: { row: r, key: 'resume', input: {} } });
49
+ });
50
+ return { title: f.title, sub: f.sub, switches: switches };
51
+ }).filter(function (g) { return g.switches.length; });
52
+ return { groups: groups, any: groups.length > 0 };
53
+ }
54
+ function firstFact(r) { return str(r.fact) || (r.facts && r.facts[0]) || ''; }
55
+
56
+ // Draw the board into `host`. on { send(req) -> Promise<{ok,status,body}>, done(toast, ok), busy }
57
+ function draw(doc, host, docket, ctx, on) {
58
+ var h = function (tag, cls, text) { var n = doc.createElement(tag); if (cls) n.className = cls; if (text != null) n.textContent = String(text); return n; };
59
+ var m = model(docket, ctx);
60
+ var board = h('div', 'swb');
61
+ if (!m.any) {
62
+ var b = (docket && docket.buildings || []).filter(function (x) { return x && x.key === 'automation'; })[0];
63
+ var who = b && b.decision && b.decision.who ? b.decision.who : 'whoever may turn the AI building on and off';
64
+ board.appendChild(h('p', 'swb__none', 'None of these switches are yours. They belong to ' + who + '.'));
65
+ host.appendChild(board);
66
+ return m;
67
+ }
68
+ var open = null; // the switch waiting on a reason
69
+ function send(flip, input) {
70
+ var C = window.GovMatterCard;
71
+ var req = C && C.request ? C.request(flip.row.card, flip.key, input || flip.input || {}) : null;
72
+ if (!req) { on.done('That switch cannot be flipped from here.', false); return; }
73
+ on.send(req).then(function (res) {
74
+ var ok = res && res.ok;
75
+ var preview = res && res.status === 501 && res.body && res.body.error && res.body.error.code === 'preview_readonly';
76
+ var toast = ok ? (C.answer(flip.row.card, flip.key, res.body).toast || 'Done.') : preview ? 'Sent. This preview stores nothing.' : C.refusal(res.status, res.body);
77
+ on.done(toast, ok || preview);
78
+ });
79
+ }
80
+ m.groups.forEach(function (g) {
81
+ var sec = h('section', 'swb__group');
82
+ var head = h('div', 'swb__head');
83
+ head.appendChild(h('h4', 'swb__title', g.title));
84
+ head.appendChild(h('span', 'swb__sub', g.sub));
85
+ sec.appendChild(head);
86
+ g.switches.forEach(function (s) {
87
+ var row = h('div', 'swb__row' + (s.on ? ' swb__row--on' : ''));
88
+ var label = h('div', 'swb__label');
89
+ label.appendChild(h('span', 'swb__name', s.name));
90
+ if (s.fact) label.appendChild(h('span', 'swb__fact', s.fact));
91
+ row.appendChild(label);
92
+ if (s.star) {
93
+ var star = h('button', 'swb__star', 'do this first');
94
+ star.setAttribute('type', 'button');
95
+ star.setAttribute('title', 'Work this goal first');
96
+ if (on.busy) star.setAttribute('disabled', '');
97
+ star.addEventListener('click', function () { send(s.star); });
98
+ row.appendChild(star);
99
+ }
100
+ var t = h('button', 'swb__toggle');
101
+ t.setAttribute('type', 'button');
102
+ t.setAttribute('role', 'switch');
103
+ t.setAttribute('aria-checked', s.on ? 'true' : 'false');
104
+ t.setAttribute('aria-label', s.name + ': ' + (s.on ? 'on' : 'off'));
105
+ if (on.busy) t.setAttribute('disabled', '');
106
+ t.appendChild(h('span', 'swb__knob'));
107
+ t.appendChild(h('span', 'swb__state', s.on ? 'on' : 'off'));
108
+ row.appendChild(t);
109
+ sec.appendChild(row);
110
+ // turning something off asks why, in one tap
111
+ var why = null;
112
+ if (s.flip.reasons && s.flip.reasons.length) {
113
+ why = h('div', 'swb__why');
114
+ why.setAttribute('hidden', '');
115
+ why.appendChild(h('span', 'swb__why-ask', 'why?'));
116
+ s.flip.reasons.forEach(function (o) {
117
+ var c = h('button', 'swb__chip', o.label);
118
+ c.setAttribute('type', 'button');
119
+ c.addEventListener('click', function () { send(s.flip, o.input); });
120
+ why.appendChild(c);
121
+ });
122
+ sec.appendChild(why);
123
+ }
124
+ t.addEventListener('click', function () {
125
+ if (why) {
126
+ if (open && open !== why) open.setAttribute('hidden', '');
127
+ if (why.hasAttribute('hidden')) { why.removeAttribute('hidden'); open = why; var f = why.querySelector('.swb__chip'); if (f && f.focus) f.focus(); } else why.setAttribute('hidden', '');
128
+ return;
129
+ }
130
+ send(s.flip);
131
+ });
132
+ });
133
+ board.appendChild(sec);
134
+ });
135
+ board.appendChild(h('p', 'swb__note', 'Only the switches you control are shown.'));
136
+ host.appendChild(board);
137
+ return m;
138
+ }
139
+
140
+ window.GovCitySwitchboard = { model: model, draw: draw, FLOORS: FLOORS };
141
+ }());
@@ -218,17 +218,33 @@ html[data-fullscreen="on"] .city-top { right: 184px; }
218
218
  glass and its tokens street-deck.css's (the ask inside is the deck's card) */
219
219
  .city-card { position: absolute; left: 50%; top: 96px; z-index: 8; width: min(432px, calc(100% - 32px)); max-height: calc(100% - 80px); overflow-y: auto; box-sizing: border-box; display: flex; flex-direction: column; gap: 16px; padding: 22px 24px 18px; border-radius: 28px; color: var(--ink); }
220
220
  .city-card[hidden] { display: none; }
221
- .city-card__head { display: flex; flex-direction: column; gap: 4px; }
222
- .city-card__top { display: flex; align-items: center; gap: 10px; }
223
- .city-card__name { flex: 1; min-width: 0; margin: 0; font-size: 24px; font-weight: 400; letter-spacing: -0.02em; }
224
- .city-card__close { flex: none; display: inline-flex; background: none; border: 0; padding: 0; min-width: 24px; min-height: 24px; cursor: pointer; }
221
+ .city-card__top { display: flex; align-items: baseline; gap: 10px; }
222
+ .city-card__name { flex: 1; min-width: 0; margin: 0; font-size: 22px; font-weight: 400; letter-spacing: -0.02em; }
223
+ .city-card__counts { flex: none; font-size: 12.5px; color: var(--ink-soft); font-variant-numeric: tabular-nums; white-space: nowrap; }
224
+ .city-card__counts b { font-weight: 500; color: var(--ink); }
225
+ .city-card__count--you, .city-card__counts b.city-card__count--you { color: var(--accent); }
225
226
  .city-card__f { margin: 0; font-size: 14px; line-height: 1.45; color: var(--ink-soft); }
226
- .city-card__count--you { color: var(--you-ink); }
227
- .city-card__said { margin: -4px 0 0; font-size: 13.5px; line-height: 1.45; color: var(--ink-soft); }
228
- .city-card__close:focus-visible, .city-card__go:focus-visible, .city-card__enter:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
229
- .city-card__acts { display: flex; align-items: center; gap: 14px; }
230
- .city-card__go { display: inline-flex; align-items: center; height: 46px; padding: 0 22px; box-sizing: border-box; border-radius: 999px; font-size: 15px; text-decoration: none; background: var(--ink); color: var(--bg); }
231
- .city-card__enter { display: inline-flex; align-items: center; min-height: 24px; font-size: 15px; color: var(--ink); text-decoration: underline; text-underline-offset: 4px; text-decoration-color: color-mix(in srgb, var(--ink) 35%, transparent); }
227
+ .city-card__said { margin: -4px 0 0; font-size: 13px; line-height: 1.45; color: var(--ink-soft); }
228
+ /* a building that waits on you wears the project colour, and glows it */
229
+ .city-card.city-card--you.gov-glass { border: 1px solid color-mix(in srgb, var(--accent) 70%, transparent); background: color-mix(in srgb, var(--accent) 14%, var(--bg-card)); box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent), 0 0 38px color-mix(in srgb, var(--accent) 45%, transparent); }
230
+ /* the ask inside: a smaller question, its answers inside the card, no "only you" line (the glow says it) */
231
+ .city-card .city-ask.ask { padding: 14px 16px; gap: 10px; }
232
+ .city-card .city-ask .ask__q { font-size: 17px; line-height: 1.3; font-weight: 400; }
233
+ .city-card .city-ask .ask__said { font-size: 13px; }
234
+ .city-card .city-ask .ask__opt { min-height: 44px; padding: 6px 12px 6px 10px; border-radius: 14px; }
235
+ .city-card .city-ask .ask__label { font-size: 14px; }
236
+ .city-card .city-ask .ask__result { font-size: 12px; }
237
+ .city-card .city-ask .ask__foot { font-size: 12px; }
238
+ .city-card__acts { display: flex; align-items: center; gap: 8px; }
239
+ .city-card__gap { flex: 1; }
240
+ .city-card__arrow { display: inline-flex; align-items: center; justify-content: center; width: 34px; height: 34px; border-radius: 50%; border: 1px solid var(--gov-chip-edge); background: var(--gov-chip); color: var(--ink); font-size: 18px; line-height: 1; cursor: pointer; }
241
+ .city-card__arrow:hover { background: var(--gov-chip-pick); }
242
+ .city-card__go { display: inline-flex; align-items: center; height: 34px; margin-left: 6px; padding: 0 16px; box-sizing: border-box; border: 0; border-radius: 999px; font-family: var(--font-body); font-size: 13.5px; cursor: pointer; background: var(--ink); color: var(--bg); }
243
+ .city-card--you .city-card__go { background: var(--accent); color: var(--accent-on); }
244
+ .city-card__close { flex: none; display: inline-flex; align-items: center; justify-content: center; background: none; border: 0; padding: 0; min-width: 24px; min-height: 24px; cursor: pointer; opacity: .6; }
245
+ .city-card__close kbd { font-size: 10px; padding: 1px 4px; }
246
+ .city-card__close:hover { opacity: 1; }
247
+ .city-card__close:focus-visible, .city-card__go:focus-visible, .city-card__arrow:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
232
248
  .city-spot { z-index: 7; }
233
249
 
234
250
  @media (max-width: 640px) {
@@ -285,3 +301,153 @@ html[data-fullscreen="on"] .city-top { right: 184px; }
285
301
  body[data-page="city"] *, body[data-page="city"] *::before, body[data-page="city"] *::after { animation: none !important; transition: none !important; }
286
302
  .city-parcels { display: none; }
287
303
  }
304
+
305
+ /* the archived street: two faint links at the city foot */
306
+ .city-archived { position: absolute; left: 0; right: 0; bottom: 14px; z-index: 2; margin: 0; text-align: center; font-size: 12px; color: var(--ink-faint); opacity: .7; }
307
+ .city-archived a { display: inline-block; min-height: 24px; line-height: 24px; color: inherit; text-decoration: underline; text-underline-offset: 3px; text-decoration-color: color-mix(in srgb, var(--ink-faint) 50%, transparent); }
308
+ .city-archived a:hover { color: var(--ink-soft); }
309
+
310
+ /* the decide view (city-decide.js): one by one, or a table to bulk-answer */
311
+ .decide { position: absolute; inset: 16px; z-index: 9; display: flex; flex-direction: column; gap: 14px; padding: 18px 22px; border-radius: 24px; box-sizing: border-box; overflow: auto; color: var(--ink); }
312
+ .decide[hidden] { display: none; }
313
+ .decide__head { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
314
+ .decide__title { margin: 0; font-size: 22px; font-weight: 400; letter-spacing: -0.02em; }
315
+ .decide__scope { min-height: 32px; padding: 0 10px; border-radius: 10px; border: 1px solid var(--gov-chip-edge); background: var(--gov-chip); color: var(--ink); font-family: var(--font-body); font-size: 13px; }
316
+ .decide__tabs { display: inline-flex; }
317
+ .decide__tabs .kit-tabs { border-bottom: 0; }
318
+ .decide__gap { flex: 1; }
319
+ .decide__counts { font-size: 13px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
320
+ .decide__counts b { font-weight: 500; color: var(--ink); }
321
+ .decide__counts b.decide__yours { color: var(--accent); }
322
+ .decide__close { display: inline-flex; align-items: center; min-width: 24px; min-height: 24px; background: none; border: 0; padding: 0; cursor: pointer; opacity: .6; }
323
+ .decide__close kbd { font-size: 10px; }
324
+ .decide__empty, .decide__pos, .decide__said { margin: 0; font-size: 13px; color: var(--ink-soft); text-align: center; }
325
+ .decide__pos { color: var(--ink-faint); }
326
+ .decide__one { display: flex; align-items: center; justify-content: center; gap: 18px; flex: 1; }
327
+ .decide__one .decide-ask { width: min(620px, 100%); box-sizing: border-box; display: flex; flex-direction: column; gap: 14px; padding: 24px 28px; border-radius: 24px; background: var(--bg-card); border: 1px solid var(--gov-chip-edge); }
328
+ .decide .decide-ask .ask__q { font-size: 22px; line-height: 1.25; }
329
+ .decide .ask.decide-ask--you { border: 1px solid color-mix(in srgb, var(--accent) 70%, transparent); box-shadow: 0 0 36px color-mix(in srgb, var(--accent) 40%, transparent); }
330
+ .decide__arrow { flex: none; width: 44px; height: 44px; border-radius: 50%; border: 1px solid var(--gov-chip-edge); background: var(--gov-chip); color: var(--ink); font-size: 22px; cursor: pointer; }
331
+ .decide__arrow[disabled] { opacity: .3; cursor: default; }
332
+ .decide__bulk { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; min-height: 36px; }
333
+ .decide__bulk-n { font-size: 13px; color: var(--ink-soft); margin-right: 6px; }
334
+ .decide__bulk-btn, .decide__opt { min-height: 28px; padding: 0 12px; border-radius: 999px; border: 1px solid var(--gov-chip-edge); background: var(--gov-chip); color: var(--ink); font-family: var(--font-body); font-size: 12.5px; cursor: pointer; white-space: nowrap; }
335
+ .decide__bulk-btn--rec, .decide__opt--rec { border-color: var(--ink-soft); }
336
+ .decide__bulk-btn:hover, .decide__opt:hover { background: var(--gov-chip-pick); }
337
+ .decide__opt[disabled] { opacity: .45; cursor: not-allowed; }
338
+ .decide__bulk-clear { background: none; border: 0; min-height: 24px; color: var(--ink-soft); font-size: 12.5px; text-decoration: underline; cursor: pointer; }
339
+ .decide__table-wrap { overflow: auto; flex: 1; }
340
+ .decide__table { width: 100%; border-collapse: collapse; font-size: 13px; }
341
+ .decide__table th { position: sticky; top: 0; text-align: left; font-weight: 500; font-size: 12px; color: var(--ink-soft); padding: 8px 10px; background: var(--bg-card); }
342
+ .decide__table td { padding: 8px 10px; border-top: 1px solid var(--rule-soft); vertical-align: top; }
343
+ .decide__tr--you td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
344
+ .decide__bld { white-space: nowrap; font-weight: 500; }
345
+ .decide__q { min-width: 240px; }
346
+ .decide__age, .decide__table td:nth-child(4) { white-space: nowrap; color: var(--ink-soft); }
347
+ .decide__acts { display: flex; flex-wrap: wrap; gap: 6px; }
348
+ .decide__row-box { width: 18px; height: 18px; margin: 3px; accent-color: var(--ink); }
349
+ .decide__desk { color: var(--ink); font-size: 12.5px; }
350
+ .decide__arrow:focus-visible, .decide__bulk-btn:focus-visible, .decide__opt:focus-visible, .decide__close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
351
+ @media (max-width: 640px) { .decide { inset: 8px; padding: 14px; } .decide__arrow { display: none; } }
352
+
353
+ /* a typed answer, in the card (city-decide.js drawCard) */
354
+ .ask-field { display: flex; gap: 8px; }
355
+ .ask-field__input { flex: 1; min-width: 0; min-height: 40px; padding: 0 14px; box-sizing: border-box; border-radius: 12px; border: 1px solid var(--gov-chip-edge); background: var(--gov-chip); color: var(--ink); font-family: var(--font-body); font-size: 14px; }
356
+ .ask-field__send { flex: none; min-height: 40px; padding: 0 16px; border-radius: 12px; border: 1px solid var(--gov-chip-edge-pick); background: var(--gov-chip-pick); color: var(--ink); font-family: var(--font-body); font-size: 14px; cursor: pointer; }
357
+ .ask-field__input:focus-visible, .ask-field__send:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
358
+ /* a building with more than one item: which one, and the keys that step through them */
359
+ .city-card__step { display: flex; align-items: center; gap: 8px; margin-top: -6px; font-size: 12.5px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
360
+ .city-card__stepbtn { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; border-radius: 8px; border: 1px solid var(--gov-chip-edge); background: var(--gov-chip); color: var(--ink); font-size: 13px; cursor: pointer; }
361
+ .city-card__stepbtn:hover { background: var(--gov-chip-pick); }
362
+ .city-card__stepbtn:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
363
+ .city-card__stepn { min-width: 46px; text-align: center; color: var(--ink); }
364
+ .city-card__hint { margin-left: auto; font-size: 11.5px; color: var(--ink-faint); white-space: pre; }
365
+ .decide__pos-n { font-weight: 500; color: var(--ink); }
366
+ .decide__text { min-height: 28px; min-width: 120px; padding: 0 10px; border-radius: 8px; border: 1px solid var(--gov-chip-edge); background: var(--gov-chip); color: var(--ink); font-family: var(--font-body); font-size: 12.5px; }
367
+
368
+ /* the beat a decision lands with (city-decide.js flash): a pulse and a word */
369
+ .decide .decide-ask { position: relative; }
370
+ .is-decided { animation: city-decided 900ms cubic-bezier(.2,.7,.3,1); }
371
+ @keyframes city-decided {
372
+ 0% { transform: scale(1); }
373
+ 25% { transform: scale(.975); box-shadow: 0 0 0 2px var(--ink), 0 0 48px color-mix(in srgb, var(--ink) 30%, transparent); }
374
+ 100% { transform: scale(1); }
375
+ }
376
+ .decided-tag { position: absolute; top: 14px; left: 50%; z-index: 3; transform: translateX(-50%); padding: 6px 14px; border-radius: 999px; background: var(--ink); color: var(--bg); font-size: 13px; font-weight: 500; pointer-events: none; animation: city-decided-tag 1100ms ease-out forwards; }
377
+ @keyframes city-decided-tag {
378
+ 0% { opacity: 0; transform: translate(-50%, 6px); }
379
+ 20% { opacity: 1; transform: translate(-50%, 0); }
380
+ 75% { opacity: 1; }
381
+ 100% { opacity: 0; transform: translate(-50%, -8px); }
382
+ }
383
+ @media (prefers-reduced-motion: reduce) {
384
+ .is-decided { animation: none; }
385
+ .decided-tag { animation: none; }
386
+ }
387
+
388
+ /* Automation as a switchboard (city-switchboard.js): groups of on/off rows */
389
+ .swb { display: flex; flex-direction: column; gap: 14px; }
390
+ .swb__group { display: flex; flex-direction: column; gap: 6px; padding: 12px 14px; border-radius: 16px; border: 1px solid var(--gov-chip-edge); background: var(--gov-chip); }
391
+ .swb__head { display: flex; flex-direction: column; gap: 2px; margin-bottom: 4px; }
392
+ .swb__title { margin: 0; font-size: 13px; font-weight: 500; letter-spacing: .02em; text-transform: uppercase; color: var(--ink-soft); }
393
+ .swb__sub { font-size: 12px; line-height: 1.4; color: var(--ink-soft); }
394
+ .swb__row { display: flex; align-items: center; gap: 10px; min-height: 40px; border-top: 1px solid var(--rule-soft); padding-top: 6px; }
395
+ .swb__row:first-of-type { border-top: 0; padding-top: 0; }
396
+ .swb__label { flex: 1; min-width: 0; display: flex; flex-direction: column; }
397
+ .swb__name { font-size: 14px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
398
+ .swb__fact { font-size: 12px; color: var(--ink-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
399
+ .swb__toggle { flex: none; position: relative; display: inline-flex; align-items: center; width: 64px; height: 30px; padding: 0 8px; box-sizing: border-box; border-radius: 999px; border: 1px solid var(--gov-chip-edge); background: var(--gov-chip-pick); color: var(--ink-soft); font-family: var(--font-body); font-size: 11.5px; cursor: pointer; transition: background 160ms ease; }
400
+ .swb__knob { position: absolute; top: 3px; left: 3px; width: 22px; height: 22px; border-radius: 50%; background: var(--ink-soft); transition: left 160ms ease, background 160ms ease; }
401
+ .swb__state { margin-left: auto; }
402
+ .swb__row--on .swb__toggle { background: var(--ink); color: var(--bg); justify-content: flex-start; }
403
+ .swb__row--on .swb__knob { left: 37px; background: var(--bg); }
404
+ .swb__row--on .swb__state { margin-left: 0; }
405
+ .swb__toggle[disabled] { cursor: progress; opacity: .6; }
406
+ .swb__toggle:focus-visible, .swb__chip:focus-visible, .swb__star:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
407
+ .swb__star { flex: none; min-height: 26px; padding: 0 10px; border-radius: 999px; border: 1px solid var(--gov-chip-edge); background: none; color: var(--ink-soft); font-family: var(--font-body); font-size: 12px; cursor: pointer; }
408
+ .swb__why { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 0 0 4px; }
409
+ .swb__why[hidden] { display: none; }
410
+ .swb__why-ask { font-size: 12px; color: var(--ink-faint); margin-right: 2px; }
411
+ .swb__chip { min-height: 26px; padding: 0 12px; border-radius: 999px; border: 1px solid var(--gov-chip-edge-pick); background: var(--gov-chip-pick); color: var(--ink); font-family: var(--font-body); font-size: 12.5px; cursor: pointer; }
412
+ .swb__note, .swb__none { margin: 0; font-size: 12px; color: var(--ink-faint); }
413
+ .swb__none { font-size: 13.5px; color: var(--ink-soft); }
414
+ @media (prefers-reduced-motion: reduce) { .swb__toggle, .swb__knob { transition: none; } }
415
+
416
+ /* the crab: open this question in Claude to help decide it (city-decide.js) */
417
+ .ask-qline { display: flex; align-items: flex-start; gap: 10px; }
418
+ .ask-qline .ask__q { flex: 1; min-width: 0; }
419
+ .ask-crab { flex: none; display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; margin-top: 1px; border-radius: 50%; border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent); background: none; color: var(--accent); cursor: pointer; }
420
+ .ask-crab:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); }
421
+ .ask-crab:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
422
+
423
+ /* the switchboard's marker over Automation: a small toggle (city-draw.js state 'switch') */
424
+ .city-pin--switch .city-pin__head { width: 34px; height: 20px; background: var(--bg-card); border: 1px solid color-mix(in srgb, var(--ink) 40%, transparent); }
425
+ .city-pin--switch .city-pin__stem { border-left-color: color-mix(in srgb, var(--ink) 40%, transparent); }
426
+ .city-pin__toggle { position: relative; width: 24px; height: 12px; border-radius: 999px; background: color-mix(in srgb, var(--ink-faint) 60%, transparent); }
427
+ .city-pin__toggle-knob { position: absolute; top: 2px; left: 2px; width: 8px; height: 8px; border-radius: 50%; background: var(--ink-soft); }
428
+ .city-pin--on .city-pin__toggle { background: var(--ink); }
429
+ .city-pin--on .city-pin__toggle-knob { left: 14px; background: var(--bg-card); }
430
+ .city-pin--switch { animation: none; }
431
+
432
+ /* a number key pressed once picks that answer; the same key again confirms it */
433
+ .ask__opt.ask__opt--armed { outline: 2px solid var(--focus); outline-offset: 2px; background: var(--gov-chip-pick); }
434
+ .ask__confirm { grid-column: 2 / span 2; font-size: 12px; color: var(--ink); }
435
+
436
+ /* "?": I don't understand this, ask Claude to rewrite it (city-decide.js) */
437
+ .ask-unclear { flex: none; display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; margin-top: 1px; border-radius: 50%; border: 1px solid var(--gov-chip-edge); background: none; color: var(--ink-soft); font-family: var(--font-body); font-size: 15px; font-weight: 600; cursor: pointer; }
438
+ .ask-unclear:hover { color: var(--ink); background: var(--gov-chip-pick); }
439
+ .ask-unclear--on { background: var(--ink); color: var(--bg); border-color: var(--ink); }
440
+ .ask-unclear:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
441
+
442
+ /* the "?" confirm: what a rewrite does, an optional note, then send (city-decide.js) */
443
+ .ask-reword { display: flex; flex-direction: column; gap: 8px; padding: 12px 14px; border-radius: 14px; border: 1px solid var(--gov-chip-edge-pick); background: var(--gov-chip-pick); }
444
+ .ask-reword__title { margin: 0; font-size: 14px; font-weight: 500; color: var(--ink); }
445
+ .ask-reword__list { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 3px; font-size: 12.5px; line-height: 1.4; color: var(--ink-soft); }
446
+ .ask-reword__note { min-height: 34px; padding: 0 12px; box-sizing: border-box; border-radius: 10px; border: 1px solid var(--gov-chip-edge); background: var(--bg-card); color: var(--ink); font-family: var(--font-body); font-size: 13px; }
447
+ .ask-reword__acts { display: flex; gap: 8px; }
448
+ .ask-reword__go { min-height: 32px; padding: 0 16px; border-radius: 999px; border: 0; background: var(--ink); color: var(--bg); font-family: var(--font-body); font-size: 13px; cursor: pointer; }
449
+ .ask-reword__go[disabled] { opacity: .6; cursor: progress; }
450
+ .ask-reword__no { min-height: 32px; padding: 0 12px; border: 0; background: none; color: var(--ink-soft); font-family: var(--font-body); font-size: 13px; text-decoration: underline; cursor: pointer; }
451
+ .ask-reword__go:focus-visible, .ask-reword__no:focus-visible, .ask-reword__note:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
452
+ /* only drawn when something is out for a rewrite; a quiet footnote to the counts */
453
+ .city-card__rewording, .decide__rewording { display: block; margin-top: 1px; font-size: 10.5px; color: var(--ink-faint); text-align: right; }
@@ -12,7 +12,7 @@
12
12
  (tests/hall_tokens.mjs: no hall page hardcodes a web-font stylesheet).
13
13
  The prototype carried its own Google Fonts link for the same two faces. -->
14
14
  <link rel="stylesheet" href="/builders/style.css?v=2026-07-02-shell1">
15
- <link rel="stylesheet" href="/builders/city.css?v=2026-10-17-tidy">
15
+ <link rel="stylesheet" href="/builders/city.css?v=2026-10-23-decide11">
16
16
  <!-- the ask card in the glass language, shared with the street's deck (task 1004702) -->
17
17
  <link rel="stylesheet" href="/builders/street-deck.css?v=2026-10-17-tidy">
18
18
  </head>
@@ -96,9 +96,11 @@
96
96
  <div class="city-card" id="cityCard" role="dialog" aria-label="A building" hidden></div>
97
97
 
98
98
  <div class="city-door">
99
- <a class="city-start" id="cityStart" href="/builders/street" aria-label="Start governing: enter the street view">start governing</a>
99
+ <a class="city-start" id="cityStart" href="#decide" aria-label="Start governing: everything that waits, one card at a time">start governing</a>
100
100
  <h1 class="city-line" id="cityLine" aria-live="polite">reading the docket</h1>
101
101
  </div>
102
+ <!-- the street and its buildings are archived: reachable, not on the way -->
103
+ <p class="city-archived">Archived: <a href="/builders/street">Street</a> &amp; <a href="/builders/street#building=charter">Building Level Views</a>.</p>
102
104
  </main>
103
105
  </div>
104
106
  </div>
@@ -113,11 +115,14 @@
113
115
  <!-- a building's card answers its first ask in place through the deck's own
114
116
  route (task 1004702): the desk rows (street-desk.js over the street's kit)
115
117
  and the deck's card and sender (street-deck.js) -->
118
+ <script src="/builders/hall-kit.js?v=2026-10-23-decide11"></script>
116
119
  <script src="/builders/street-draw.js?v=2026-10-09-living3"></script>
117
120
  <script src="/builders/street-card.js?v=2026-10-04-record1"></script>
118
121
  <script src="/builders/street-inside.js?v=2026-10-09-living3"></script>
119
122
  <script src="/builders/street-desk.js?v=2026-10-04-record1"></script>
120
123
  <script src="/builders/street-deck.js?v=2026-10-13-glass1"></script>
121
- <script src="/builders/city.js?v=2026-10-13-glass1"></script>
124
+ <script src="/builders/city-decide.js?v=2026-10-23-decide11"></script>
125
+ <script src="/builders/city-switchboard.js?v=2026-10-23-board4"></script>
126
+ <script src="/builders/city.js?v=2026-10-23-decide11"></script>
122
127
  </body>
123
128
  </html>