@bongos/core 1.24.28 → 1.24.30

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 (63) hide show
  1. package/.bongos-core.json +100 -70
  2. package/.claude/skills/builder-claim/SKILL.md +1 -1
  3. package/.claude/skills/builder-ship/SKILL.md +1 -1
  4. package/clients/bongos-client/README.md +1 -1
  5. package/clients/bongos-client/bongos-client.global.js +4 -0
  6. package/clients/bongos-client/index.cjs +4 -0
  7. package/clients/bongos-client/index.d.ts +4 -0
  8. package/clients/bongos-client/index.mjs +4 -0
  9. package/docs/api/openapi.json +36 -1
  10. package/docs/api-reference.md +7 -1
  11. package/docs/copy-inventory.md +247 -280
  12. package/docs/copy-registry.json +381 -691
  13. package/docs/module-api-changelog.md +4 -0
  14. package/docs/page-inventory.json +5 -10
  15. package/docs/page-readings.json +327 -393
  16. package/modules/builder-settings/interaction-prefs.js +2 -2
  17. package/modules/government/db.js +29 -0
  18. package/modules/government/docket.js +61 -4
  19. package/modules/government/routes/government.js +2 -0
  20. package/modules/hall-ui/public/builders.js +3 -79
  21. package/modules/hall-ui/public/hall-render.js +6 -81
  22. package/modules/hall-ui/public/hall-widgets.js +4 -45
  23. package/modules/hall-ui/public/home-city-draw.js +275 -0
  24. package/modules/hall-ui/public/home-city.css +66 -0
  25. package/modules/hall-ui/public/home-city.js +223 -0
  26. package/modules/hall-ui/public/index.html +5 -6
  27. package/modules/hall-ui/public/index.states.json +73 -8
  28. package/modules/hall-ui/public/my-agents.js +35 -2
  29. package/modules/hall-ui/public/palette.js +8 -0
  30. package/modules/hall-ui/public/settings-moved.js +41 -0
  31. package/modules/hall-ui/public/settings.css +4 -28
  32. package/modules/hall-ui/public/settings.html +19 -107
  33. package/modules/hall-ui/public/settings.js +20 -215
  34. package/modules/hall-ui/public/settings.states.json +2 -4
  35. package/modules/hall-ui/records/home-city.md +66 -0
  36. package/modules/hall-ui/records/my-agents.md +3 -2
  37. package/modules/hall-ui/records/panel-family.md +2 -0
  38. package/modules/lifecycle/home-city.js +340 -0
  39. package/modules/lifecycle/module.json +1 -0
  40. package/modules/lifecycle/routes/home-city.js +29 -0
  41. package/modules/lifecycle/ship-card.js +2 -2
  42. package/package-lock.json +2 -2
  43. package/package.json +1 -1
  44. package/release-notes.json +16 -0
  45. package/src/module-api.js +1 -1
  46. package/tests/claim_from_task_and_home.mjs +8 -43
  47. package/tests/government_tidy_up.mjs +21 -24
  48. package/tests/hall_board_world.mjs +10 -12
  49. package/tests/hall_home_ships_bounded.mjs +4 -3
  50. package/tests/hall_my_agents.mjs +88 -2
  51. package/tests/hall_palette.mjs +12 -0
  52. package/tests/hall_settings_world.mjs +64 -100
  53. package/tests/helpers.mjs +6 -5
  54. package/tests/home_bounded_reads.mjs +16 -31
  55. package/tests/home_city.mjs +385 -0
  56. package/tests/home_city_widget.mjs +120 -0
  57. package/tests/interaction_posture.mjs +3 -1
  58. package/tests/leaderboard_roles.mjs +3 -2
  59. package/tests/speciality_offer_ui.mjs +6 -5
  60. package/tests/speciality_walkthrough_ui.mjs +7 -5
  61. package/modules/hall-ui/public/settings-interaction.js +0 -132
  62. package/modules/hall-ui/public/settings-law-locks.js +0 -53
  63. package/modules/hall-ui/public/settings-render.js +0 -138
@@ -1,12 +1,15 @@
1
1
  // public-builders/settings.js — the personal Settings page (/builders/settings).
2
2
  //
3
3
  // Open to ANY authenticated builder (no rank gate — a builder owns their own
4
- // preferences; the server enforces the same posture on /me/skill-prefs and
5
- // /me/disciplines). Two sections:
4
+ // preferences; the server enforces the same posture on /me/disciplines). Among
5
+ // its sections:
6
6
  //
7
- // 1. VOICES — per-skill model picker (GET/PATCH /api/bongos/me/skill-prefs).
8
- // 2. CRAFT — preferred disciplines (GET /api/bongos/me, PATCH /api/bongos/me/disciplines).
9
- // 5. SOUNDS — per-sound on/off toggles (GET/PATCH /api/bongos/me/sound-prefs, #786).
7
+ // CRAFT — preferred disciplines (GET /api/bongos/me, PATCH /api/bongos/me/disciplines).
8
+ // SOUNDS — per-sound on/off toggles (GET/PATCH /api/bongos/me/sound-prefs, #786).
9
+ //
10
+ // The agent settings (how Claude talks to you, wandering, session cards, skill
11
+ // voices, specialities) are NOT here since task 1005044: My agents is their one
12
+ // page, and settings-moved.js sends their old deep links there.
10
13
  //
11
14
  // On a 401 it redirects into the web sign-in flow, like the other hall pages.
12
15
  // Each section loads behind its own try/catch so one failing endpoint doesn't
@@ -32,102 +35,6 @@
32
35
  window.location.assign(`${API}/auth/web/start`);
33
36
  }
34
37
 
35
- // ---- model labels (match the hall's wording in builders.js) --------------
36
-
37
- const MODEL_LABELS = {
38
- opus: 'Opus',
39
- sonnet: 'Sonnet',
40
- haiku: 'Haiku',
41
- script: 'no LLM',
42
- };
43
- const modelLabel = (m) => MODEL_LABELS[m] || m || '—';
44
-
45
- // ---- section 1: VOICES (per-skill model picker) --------------------------
46
-
47
- // Render one row per skill in `effective`. Each row: skill name, the system
48
- // default (muted), and a <select> whose value is the builder's override (or
49
- // "" for "fall back to default"). The allowed model set is echoed back by the
50
- // endpoint so we don't mirror it from a second source.
51
- function renderVoices(data) {
52
- const body = $('#voices-body');
53
- if (!body) return;
54
- const rows = Array.isArray(data?.effective) ? data.effective : [];
55
- const allowed = Array.isArray(data?.allowed_models)
56
- ? data.allowed_models
57
- : ['opus', 'sonnet', 'haiku', 'script'];
58
- if (rows.length === 0) {
59
- body.innerHTML = `<tr><td colspan="3">${window.OTBKit.emptyStateHtml('No skills registered.')}</td></tr>`;
60
- return;
61
- }
62
- body.innerHTML = rows.map((row) => {
63
- const opts = [`<option value="">Default (${escapeHtml(modelLabel(row.default_model))})</option>`]
64
- .concat(allowed.map((m) => {
65
- const sel = row.override_model === m ? ' selected' : '';
66
- return `<option value="${escapeHtml(m)}"${sel}>${escapeHtml(modelLabel(m))}</option>`;
67
- }))
68
- .join('');
69
- return (
70
- `<tr data-skill="${escapeHtml(row.skill)}">` +
71
- `<td class="voices__skill"><code>${escapeHtml(row.skill)}</code></td>` +
72
- `<td><span class="voices__default" title="System default for this skill">${escapeHtml(modelLabel(row.default_model))}</span></td>` +
73
- '<td>' +
74
- `<select class="voices__select" data-skill="${escapeHtml(row.skill)}" aria-label="Voice for ${escapeHtml(row.skill)}">${opts}</select>` +
75
- '</td>' +
76
- '</tr>'
77
- );
78
- }).join('');
79
- }
80
-
81
- // PATCH a single skill's override. Body shape (see me.js + skill-prefs.js):
82
- // { "<skill-name>": "<model>" | null }
83
- // where null clears the override (fall back to the system default).
84
- async function saveVoice(skill, modelOrNull) {
85
- const status = $('#voices-status');
86
- if (status) status.textContent = 'Saving…';
87
- try {
88
- const body = { [skill]: modelOrNull };
89
- const res = await api.request('PATCH', `${API}/me/skill-prefs`, { body });
90
- if (res.status === 401) { toAuth(); return; }
91
- if (!res.ok) throw new Error(`skill-prefs PATCH → ${res.status}`);
92
- const data = res.data;
93
- renderVoices(data);
94
- const msg = modelOrNull
95
- ? `${skill} → ${modelLabel(modelOrNull)}.`
96
- : `Cleared ${skill} — falls back to default.`;
97
- if (status) status.textContent = msg;
98
- toast(msg);
99
- } catch (err) {
100
- console.error('[settings] save voice failed', err);
101
- if (status) status.textContent = 'Could not save. Try again.';
102
- toast('Could not save your choice. Try again.');
103
- }
104
- }
105
-
106
- async function loadVoices() {
107
- try {
108
- const res = await api.request('GET', `${API}/me/skill-prefs`);
109
- if (res.status === 401) { toAuth(); return; }
110
- if (!res.ok) throw new Error(`skill-prefs GET → ${res.status}`);
111
- const data = res.data;
112
- renderVoices(data);
113
- $('#voices-scroll').hidden = false;
114
- // Delegate change events so post-render selects stay wired.
115
- const table = $('#voices-table');
116
- table.addEventListener('change', (e) => {
117
- const sel = e.target.closest('.voices__select');
118
- if (!sel) return;
119
- const skill = sel.dataset.skill;
120
- const next = sel.value === '' ? null : sel.value;
121
- saveVoice(skill, next);
122
- });
123
- } catch (err) {
124
- console.error('[settings] failed to load voices', err);
125
- $('#voices-scroll').hidden = false;
126
- $('#voices-body').innerHTML =
127
- '<tr><td colspan="3" class="voices-empty">The skill list could not be loaded. Reload the page to try again.</td></tr>';
128
- }
129
- }
130
-
131
38
  // ---- section 5: SOUNDS (per-sound on/off toggles, #786) ------------------
132
39
 
133
40
  // Render one row per sound from `effective`. Each row: label + description and
@@ -392,104 +299,6 @@
392
299
  }
393
300
  }
394
301
 
395
- // ---- section 2b: WANDERING (off-task thread suppression, task 1268) -------
396
- //
397
- // GET/PATCH /api/bongos/me/wandering. The payload (wandering-prefs.describeForApi)
398
- // carries the level catalog with per-rank `allowed`, the effective level, the
399
- // rank default, and the ceiling. Single-select: clicking an allowed level
400
- // PATCHes { wandering: n }. Levels above the rank cap render disabled — the
401
- // server enforces the same clamp, this is just the friendly UI half. Reuses
402
- // the .sound-row / .sound-toggle styling.
403
- function renderWandering(data) {
404
- const wrap = $('#wander-rows');
405
- if (!wrap) return;
406
- const levels = Array.isArray(data?.levels) ? data.levels : [];
407
- const eff = typeof data?.effective_level === 'number' ? data.effective_level : null;
408
- const def = typeof data?.default_level === 'number' ? data.default_level : null;
409
- if (levels.length === 0) {
410
- wrap.innerHTML = window.OTBKit.emptyStateHtml('No levels registered.');
411
- return;
412
- }
413
- // One pick among four: a radio row per level (panel.css `.prow--pick`), the
414
- // whole row its label. A level above the rank ceiling is a disabled radio
415
- // that says so in the label voice rather than a dead "Rank-locked" button.
416
- // a project law may fix or cap the level too (task 1004724, settings-law-locks.js)
417
- const Laws = window.OTBSettingsLaws || null;
418
- const lawLock = Laws ? Laws.lockFor(data, 'wandering') : null;
419
- wrap.innerHTML = levels.map((lv) => {
420
- const chosen = lv.n === eff;
421
- const byLaw = lv.allowed && !!lawLock && !Laws.allows(lawLock, lv.key);
422
- const locked = !lv.allowed || byLaw;
423
- const id = `wander-${lv.n}`;
424
- const tags = (lv.n === def ? '<span class="prow__tag">default for your rank</span>' : '') +
425
- (!lv.allowed ? '<span class="prow__tag">rank-locked</span>' : '') +
426
- (byLaw ? `<span class="prow__tag">${escapeHtml(Laws.tagFor(lawLock))}</span>` : '');
427
- return (
428
- `<label class="prow prow--pick" for="${id}" data-level="${lv.n}"${locked ? ' aria-disabled="true"' : ''}>` +
429
- `<input class="pradio" type="radio" name="wandering" id="${id}" value="${lv.n}"` +
430
- `${chosen ? ' checked' : ''}${locked ? ' disabled' : ''}>` +
431
- '<span class="prow__main">' +
432
- `<span class="prow__label">${escapeHtml(lv.label)}${tags}</span>` +
433
- `<span class="prow__desc">${escapeHtml(lv.blurb || '')}</span>` +
434
- '</span></label>'
435
- );
436
- }).join('');
437
- const note = $('#wandering-note');
438
- if (note) {
439
- note.textContent = ((data && data.effective_label && data.max_key)
440
- ? `Now: ${data.effective_label}. Your rank allows up to ${data.max_key}.`
441
- : '') + (lawLock ? ' ' + Laws.lawLine(lawLock) : '');
442
- }
443
- }
444
-
445
- // PATCH the wandering level. Body: { wandering: <0..3> } (see me.js +
446
- // wandering-prefs.js). A level above the rank ceiling is rejected server-side
447
- // (422) — but those buttons render disabled, so it shouldn't be reachable.
448
- async function saveWandering(level, btn) {
449
- const status = $('#wandering-status');
450
- if (status) status.textContent = 'Saving…';
451
- if (btn) btn.disabled = true;
452
- try {
453
- const res = await api.request('PATCH', `${API}/me/wandering`, { body: { wandering: level } });
454
- if (res.status === 401) { toAuth(); return; }
455
- if (!res.ok) throw new Error(`wandering PATCH → ${res.status}`);
456
- const data = res.data;
457
- renderWandering(data);
458
- const msg = `Wandering → ${data.effective_label || level}.`;
459
- if (status) status.textContent = msg;
460
- toast(msg);
461
- } catch (err) {
462
- console.error('[settings] save wandering failed', err);
463
- if (status) status.textContent = 'Could not save. Try again.';
464
- toast('Could not save your wandering level. Try again.');
465
- if (btn) btn.disabled = false;
466
- }
467
- }
468
-
469
- async function loadWandering() {
470
- try {
471
- const res = await api.request('GET', `${API}/me/wandering`);
472
- if (res.status === 401) { toAuth(); return; }
473
- if (!res.ok) throw new Error(`wandering GET → ${res.status}`);
474
- const data = res.data;
475
- renderWandering(data);
476
- $('#wandering-scroll').hidden = false;
477
- // Delegate the change so post-render radios stay wired (single-select).
478
- const wrap = $('#wander-rows');
479
- wrap.addEventListener('change', (e) => {
480
- const r = e.target.closest('.pradio');
481
- if (!r || r.disabled || !r.checked) return; // ignore locked; a radio only fires on becoming checked
482
- const level = Number(r.value);
483
- if (Number.isInteger(level)) saveWandering(level, r);
484
- });
485
- } catch (err) {
486
- console.error('[settings] failed to load wandering', err);
487
- $('#wandering-scroll').hidden = false;
488
- $('#wander-rows').innerHTML =
489
- '<p class="voices-empty">Your wandering level could not be loaded. Reload the page to try again.</p>';
490
- }
491
- }
492
-
493
302
  // ---- section: PROFILE AND PRIVACY — a link out to the hub (task 1004259)
494
303
  //
495
304
  // Handle, bio, links, public/private, recruiters and the hide-my-numbers switch
@@ -1100,7 +909,7 @@
1100
909
  const RAIL_GROUPS = RAIL_ITEMS.map((i) => i.id).concat('extensions');
1101
910
  let rail = null;
1102
911
 
1103
- // task 2172 — stable per-panel anchors (/settings#cli, /settings#voices, …)
912
+ // task 2172 — stable per-panel anchors (/settings#cli, /settings#sounds, …)
1104
913
  // so skills and docs can deep-link a panel without depending on internal
1105
914
  // section ids. #art-key predates the map and IS its section id (the hall's
1106
915
  // "Add your key" action + the CLI nudge target it) — kept verbatim.
@@ -1117,10 +926,6 @@
1117
926
  'cli': 'cli-scroll',
1118
927
  'autobongos': 'autobongos-scroll',
1119
928
  'art-key': 'art-key',
1120
- 'interaction': 'interaction-scroll',
1121
- 'wandering': 'wandering-scroll',
1122
- 'rendering': 'render-scroll',
1123
- 'voices': 'voices-scroll',
1124
929
  'sounds': 'sounds-scroll',
1125
930
  'event-sounds': 'event-sounds-scroll',
1126
931
  'software-update': 'software-update-scroll',
@@ -1162,6 +967,14 @@
1162
967
  }
1163
968
  }
1164
969
 
970
+ // task 1005044 — a link to an agent setting that moved to My agents
971
+ // (settings-moved.js) goes on there; replace(), so Back does not bounce here.
972
+ function leaveIfMoved() {
973
+ const to = window.OTBSettingsMoved && window.OTBSettingsMoved.movedTarget(window.location.hash);
974
+ if (to) window.location.replace(to);
975
+ return !!to;
976
+ }
977
+
1165
978
  function wireRail() {
1166
979
  const mount = document.getElementById('settings-rail');
1167
980
  if (!mount || !window.OTBKit) return;
@@ -1178,7 +991,7 @@
1178
991
  });
1179
992
  rail.activate('account');
1180
993
  applyHash({ scroll: false });
1181
- window.addEventListener('hashchange', () => applyHash());
994
+ window.addEventListener('hashchange', () => { if (!leaveIfMoved()) applyHash(); });
1182
995
  }
1183
996
 
1184
997
  // Hide the rail + groups entirely while the not-signed-in gate is showing —
@@ -1251,21 +1064,12 @@
1251
1064
  rail.activate($('#settings-groups')?.dataset.active || 'account');
1252
1065
  }
1253
1066
 
1254
- // Voices loads independently — its own endpoint + its own try/catch.
1255
- await loadVoices();
1256
1067
  // Sounds are switched off for now (task 1004554 — .claude/sounds/play.js's
1257
1068
  // SOUNDS_ON). The per-sound toggles (#786) and the per-event picker (task 582)
1258
1069
  // would only offer choices that do nothing, so neither loads; the Sound group
1259
1070
  // shows the one-line note instead. To restore: call loadSounds() and
1260
1071
  // loadEventSounds() here again and drop the note.
1261
1072
  $('#sounds-off-scroll').hidden = false;
1262
- // (The interaction profile panel self-boots from settings-interaction.js —
1263
- // its own script on the settings-sessions.js precedent, task 1002514.)
1264
- // Wandering loads independently too (off-task thread suppression, task 1268).
1265
- await loadWandering();
1266
- // (Rendering — the card-richness ladder — is settings-render.js, on the same
1267
- // precedent: it became a four-rung picker in task 1002518 and this file is
1268
- // against the 1500-line ratchet.)
1269
1073
  // Profile and privacy — one row linking out to the hub's account page
1270
1074
  // (task 1004259); hidden where the page carries no hub origin.
1271
1075
  loadAccountHub();
@@ -1273,10 +1077,11 @@
1273
1077
  // the `needs` art_key item that drives the covered/action-needed banner.
1274
1078
  await loadArtKey(me);
1275
1079
  // Every section has now loaded/revealed — land any panel-anchor deep link
1276
- // (/settings#cli, #voices, …) on its visible section.
1080
+ // (/settings#cli, #art-key, …) on its visible section.
1277
1081
  applyHash();
1278
1082
  }
1279
1083
 
1084
+ if (leaveIfMoved()) return;
1280
1085
  wireRail();
1281
1086
  load();
1282
1087
  })();
@@ -9,11 +9,9 @@
9
9
  "states": {
10
10
  "account": { "auth": true, "actions": [["wait", 900]], "expect": { "visible": ["#settings-h1", "#display-name-input", "#craft-chips"] } },
11
11
  "access": { "auth": true, "url": "/settings#access", "actions": [["wait", 900]], "expect": { "visible": ["#cli-reissue-btn", "#sessions-list", "#artkey-card"] } },
12
- "preferences": { "auth": true, "url": "/settings#preferences", "actions": [["wait", 900]], "expect": { "visible": ["#wander-rows", "#render-rows", "#specialities-rows"] } },
13
- "skills-walkthrough": { "auth": true, "url": "/settings#preferences", "actions": [["wait", 900], ["click", "#specialities-rows button[data-act=\"adopt\"]"], ["wait", 700]], "expect": { "visible": [".skill-walk", ".skill-walk__facts", "button[data-walk=\"on\"]", "button[data-walk=\"off\"]"] } },
14
- "offered-skills": { "auth": true, "url": "/settings#preferences", "actions": [["wait", 900], ["click", "#specialities-rows button[data-act=\"offer\"]"], ["wait", 700]], "expect": { "visible": ["form.speciality-offer", ".speciality-offer__list", ".speciality-offer__filter"] } },
12
+ "preferences": { "auth": true, "url": "/settings#preferences", "actions": [["wait", 900]], "expect": { "visible": ["#agents-moved-preferences", "#agents-moved-preferences a[href=\"/my-agents#talk\"]", "#usage-reports-h"] } },
15
13
  "install-reports": { "_": "The Module install reports switch (task 1003793): the opt-out for counting your store-module installs in a module's score, on by default. Scrolled into view by focusing its default choice.", "auth": true, "url": "/settings#preferences", "actions": [["wait", 900], ["clickIfVisible", "[data-craft-close]"], ["wait", 300], ["focus", "#usage-reports-on"], ["wait", 300]], "expect": { "visible": ["#usage-reports-h", "#usage-reports-on", "#usage-reports-off"] } },
16
- "sound": { "auth": true, "url": "/settings#sound", "actions": [["wait", 900]], "expect": { "visible": ["#voices-table", "#sound-rows", "#event-sound-rows"] } },
14
+ "sound": { "auth": true, "url": "/settings#sound", "actions": [["wait", 900]], "expect": { "visible": ["#agents-moved-sound", "#agents-moved-sound a[href=\"/my-agents#skills\"]", "#sounds-off-h"] } },
17
15
  "update": { "auth": true, "url": "/settings#software-update", "actions": [["wait", 1200]], "expect": { "visible": ["#software-update-h", "#su-body", "#su-update", ".su-versions"] } }
18
16
  },
19
17
  "reducedMotion": { "auth": true, "actions": [["wait", 900]] }
@@ -0,0 +1,66 @@
1
+ # The project city — Home's body becomes one scene: towers, a ladder of builders, a time slider (task 1005054)
2
+
3
+ The owner's Home redesign of 2026-11-03, agreed over fourteen mock rounds; the approved mock is
4
+ [`docs/design/mocks/hall/home/v14.html`](../../../docs/design/mocks/hall/home/v14.html) (v2 to v13 beside it
5
+ are the rounds, each with its own header comment on what changed and why). The brief in the owner's words:
6
+ "I don't want to reach, I want to see and feel", and the UI "subtle and free from overuse of text".
7
+
8
+ **What people see** (agreed before drawing, round 6): who is involved, the speed of builds as a time slider,
9
+ what is waiting on them, what is being built now, then spend, health and just shipped as small things. Not on
10
+ Home: how close the version is to done, what is ready to go live, work to pick up. The same page for everyone.
11
+
12
+ - **The scene** (`home-city-draw.js`, pure; the city's own iso palette from `city-draw.js`): one tower per
13
+ working area (the open, non-maintenance goals of the version being built), as tall as what has shipped
14
+ there; artist and ui work clads it in glass from the ground up; one hairline per floor. The sites worked in
15
+ the selected week carry a crane lifting a slab, scaffolding round the floor going up, that floor lit, and the
16
+ face of who built there (a robot face when an Autobongos runner is working it). That week's ideas are lights
17
+ in the sky, the week before's dimmer; one that became work drops a dotted line to its tower. A salmon dot
18
+ with a count sits on each tower where something waits on the viewer, on the latest week only.
19
+ - **The ladder** beside the city: a small face and one thin line per builder, length = their week's total,
20
+ split into art · build · ideas · govern, each piece marked with the hall's own craft icon (`craft-mode.js`
21
+ `iconHtml`, never a second copy) and its count. Ranked by the total, the top seven, and the viewer appended
22
+ when they fall below. No rank numbers, no names, no tabs: the words are on hover. One colour, not four: four
23
+ craft colours from this pack fail the dataviz validator's normal-vision floor (v11's note).
24
+ - **The slider** under the scene, with play: every part redraws for the week it is on. Play steps a week at a
25
+ time on a timer and stops on now — state, never animation, so no motion lives in JavaScript. The ladder's
26
+ rows slide and a crane's slab lifts in CSS; the hall's reduced-motion rule stops both.
27
+ - **Placement.** The widget is `home-city` in the `home-stats` zone (budget 1), which the stats strip held.
28
+
29
+ **Left Home:** the stats strip and version tab (`home-stats`, and the `/public/progress` read that only fed the
30
+ tab), the Goals card (`goals.js` is no longer loaded by `index.html`), and Your work — Next up with its claim
31
+ button and `GET /tasks/claimable?limit=5`, and the Recently shipped table (`home-recent`). `claim-action.js` left
32
+ `index.html` with the claim button. **Kept:** the conditional welcome, setup, genesis and founding banners, the
33
+ Standing chip (its Shipped figure now comes straight from the ships payload, `shipState`, not a DOM tile), and
34
+ the Needs-your-attention inbox below the city, because it holds actions with no other home (join requests,
35
+ invitations, triage). The `/me/recent-ships?limit=5` read stays: it feeds the chip and Accomplishments.
36
+
37
+ **The data** is ONE bounded read, `GET /home/city?weeks=10` (`modules/lifecycle/home-city.js`, any signed-in
38
+ builder): counts grouped by week in SQL, so the payload grows with areas, builders and weeks, never with the
39
+ corpus; `tests/home_city.mjs` proves every query is bounded and selects no prose. Four things it is honest about:
40
+
41
+ - **Art** is economy's credits-by-craft rule: `artist` and `ui` disciplines. Page tweaks file under each version's
42
+ artist-review goal, so that tower is mostly glass.
43
+ - **An agent is a site fact, never a person.** Autobongos runs under its builder's account; a site whose task an
44
+ `autonomy_runner_heartbeat` row is working (seen in the last 15 minutes) is drawn with the robot face and named
45
+ "<builder>'s agent". `people[].agent` is always false from the server.
46
+ - **"Waits on you" is the docket's own routing (task 1005056):** the government port's `yoursByGoal(viewer)`
47
+ routes the docket's goal-shaped matters (blockers, criteria awaiting sign-off, goal join requests) with
48
+ `modules/government/routing.js` and counts, per area, those routed to the viewer — so a seated Lead, a board
49
+ member or an access holder sees their own dots, not only an Archon. With government off, an older government
50
+ without the method, or seats it could not read, the server falls back to open blockers whose stored ask waits
51
+ on `owner` (or carry none), shown only to an Archon.
52
+ - Government and autonomy are optional modules: their tables are probed with `to_regclass` first and left out
53
+ when absent, so a project with either switched off still gets its city.
54
+
55
+ **Verifying:** the hall-preview harness serves `fixtures/home__city.json` (hand-shaped from the approved mock, ids
56
+ from the other fixtures); `tests/home_city_widget.mjs` runs the real widget in a browser (one read, the slider
57
+ redraws, play stops on now, a refused read leaves no empty plate, no sideways scroll at 390); the kit stub does
58
+ not serve the read, so `index.states.json` ignores it and its renders show the city hidden, as for a visitor.
59
+
60
+ ## Private areas
61
+
62
+ A private goal the viewer is neither a member of nor an Archon over still stands as a tower (its counts are structure), but the server sends no title for it or for the ideas tied to it; the drawing labels them "A private area" and "A private idea" (ADR 0112, as the goal map and the sky apply it). Pinned in `tests/home_city.mjs`.
63
+
64
+ ## Sign-off
65
+
66
+ The owner chose v14 from fourteen mock rounds, approved the before and after review sheet (1280 dark and light, 390 dark) on 2026-11-02, and said "Yes, ship it".
@@ -1,6 +1,6 @@
1
1
  # My agents, and governing as a held seat — /my-agents and the nav (task 1004685, the living-city prototype)
2
2
 
3
- Covers: `my-agents.{html,js,css,states.json}`, the speciality tools it shares with Settings (`speciality-tools.{js,css}`, task 1005052), `gov-positions.js`, the `position` flag in `shell.js` NAV, the seat in `craft-mode.js`, and the harness recorder `scripts/hall-preview/pref-writes.js`. Built on round-3 boards 6 ("the city and My agents"), 7 ("how strict") and 8 ("governance is a position") of the living-city design canvas. **Every row is real since task 1004714**: the settings, the laws' locks and caps, and Make it a law. The strictness preset strip is real too since task 1004715 (`GET /government/presets`, [ADR 0380](../../../docs/adr/0380-a-strictness-preset-is-a-bundle-of-laws-switched-by-one-laws-vote.md)).
3
+ Covers: `my-agents.{html,js,css,states.json}`, the speciality tools (`speciality-tools.{js,css}`, task 1005052), Settings' pointer here and its old-link map (`settings-moved.js`, task 1005044), `gov-positions.js`, the `position` flag in `shell.js` NAV, the seat in `craft-mode.js`, and the harness recorder `scripts/hall-preview/pref-writes.js`. Built on round-3 boards 6 ("the city and My agents"), 7 ("how strict") and 8 ("governance is a position") of the living-city design canvas. **Every row is real since task 1004714**: the settings, the laws' locks and caps, and Make it a law. The strictness preset strip is real too since task 1004715 (`GET /government/presets`, [ADR 0380](../../../docs/adr/0380-a-strictness-preset-is-a-bundle-of-laws-switched-by-one-laws-vote.md)).
4
4
 
5
5
  ## What it is
6
6
 
@@ -9,7 +9,8 @@ Covers: `my-agents.{html,js,css,states.json}`, the speciality tools it shares wi
9
9
  - **What your agent reads first** quotes the REAL contract text `/me` carries (the Conductor relays it), says plainly when a knob is not sent (permissive wandering is `inject: false`), and puts the project's REAL laws above it: `/me`'s `laws` block, the very words every session gets first (task 1004707). With no law in force that block is not drawn.
10
10
  - **A real law's lock is real.** `/me`'s `laws.locks` says what each law in force did to your settings; the server already applied it (the slice's effective value obeys it), so the row is locked, naming the law and its number in the real book, with your own choice underneath. A law that only CAPS a setting (`{ setting, max }`) does not lock the row: the server names every value it still allows (`allowed` on each lock, `law-locks.js` allowedFor — the same list a refused PATCH carries), the chips offer those, and the line says `capped by <law> · Law N`. A cap that leaves one value, or a server that names none, reads as the whole-row lock. A lock the server could not enforce (a setting no table holds yet) locks nothing. A write the law forbids answers 409 `locked_by_law`, and the toast says the law.
11
11
  - **Make it a law** lists your own non-default choices that no law already locks or caps; proposing one files a real laws vote (`POST /government/board/laws`, task 1004706): one law, in the desk's voice, locking the setting to your value, sat under the constitution in force. It is offered only to a builder `/me` says may open a board item (`permissions['board.item.open']`, one of `NAV_PERMISSION_ATOMS`); an unreadable map offers nothing. A filed habit links to its vote in the Board Room.
12
- - **What it knows is the speciality tools, not desk rows** (task 1005052). The section `#specialities-scroll` under the desk is filled by `speciality-tools.js` — the SAME component Settings mounts (it was `settings-specialities.js`), finding its hosts by id: create (it renders the create form into the page's empty `<form id="speciality-create">`, so the markup exists once), train, offered skills, adopt with someone else's full instructions open in the same card, the skills walkthrough, stop using, and the project's suggestions. It is its own section because a desk row holds at most two buttons and `my-agents.js` redraws `#ma-rows` on every change, which would wipe a half-filled form. Its markup is in the panel family's words (`.prow`, `.btn`, `.pbtn`); this page has no `panel.css`, so `my-agents.css`'s `.ma-know` skin draws them here as hairline rows and ghost pills, no accent (the structure is `speciality-tools.css`, shared). The desk's old one-tap adopt chips are gone: they adopted someone else's speciality without showing its instructions. After an adopt, a stop or a skills save the component dispatches `otb:specialities-changed` and this page re-reads `/me`, so "What your agent reads first" stays true. Task 1005044 removes the Settings mount; the code stays. Tests: `tests/speciality_offer_ui.mjs` and `tests/speciality_walkthrough_ui.mjs` run every tool on BOTH pages' own markup (`tests/helpers.mjs` `bootSpecialityTools`). Kit states `what-it-knows`, `speciality-walk`, `speciality-forms`; the harness serves an in-use Pricing (`me__specialities.json`) and canned stop-using and train answers.
12
+ - **What it knows is the speciality tools, not desk rows** (task 1005052). The section `#specialities-scroll` under the desk is filled by `speciality-tools.js` — the SAME component Settings mounts (it was `settings-specialities.js`), finding its hosts by id: create (it renders the create form into the page's empty `<form id="speciality-create">`, so the markup exists once), train, offered skills, adopt with someone else's full instructions open in the same card, the skills walkthrough, stop using, and the project's suggestions. It is its own section because a desk row holds at most two buttons and `my-agents.js` redraws `#ma-rows` on every change, which would wipe a half-filled form. Its markup is in the panel family's words (`.prow`, `.btn`, `.pbtn`); this page has no `panel.css`, so `my-agents.css`'s `.ma-know` skin draws them here as hairline rows and ghost pills, no accent (the structure is `speciality-tools.css`, shared). The desk's old one-tap adopt chips are gone: they adopted someone else's speciality without showing its instructions. After an adopt, a stop or a skills save the component dispatches `otb:specialities-changed` and this page re-reads `/me`, so "What your agent reads first" stays true. Since task 1005044 this is the component's only host. Tests: `tests/speciality_offer_ui.mjs` and `tests/speciality_walkthrough_ui.mjs` run every tool on this page's own markup (`tests/helpers.mjs` `bootSpecialityTools`, `SPECIALITY_PAGES`). Kit states `what-it-knows`, `speciality-walk`, `speciality-forms`; the harness serves an in-use Pricing (`me__specialities.json`) and canned stop-using and train answers.
13
+ - **This is the ONE page for the agent settings** (task 1005044, the owner's call on idea 1001309). How Claude talks to you, Wandering, Session cards, Skill voices and the specialities were edited on Settings too, on the same routes, with no hint the two were one setting. Settings now carries none of them: a line in its Preferences group ("Your agents") and one in Sound ("Skill voices") link here, and `settings-moved.js` sends every old deep link (`/settings#interaction`, `#wandering`, `#rendering`, `#render`, `#voices`, `#specialities` and the old section ids) on with `location.replace`. **The anchors this page lands** are `my-agents.js` `ANCHORS`: `#talk`, `#work`, `#models` (the desk groups, `id="ma-group-<key>"`), `#skills` (opens Per skill) and `#knows` (What it knows, watched until speciality-tools.js reveals it — no timer, the motion test allows one). The palette offers the five (`palette.js` SECTIONS, `tests/hall_palette.mjs`). Settings' old panels went with their only host: `settings-interaction.js`, `settings-render.js`, `settings-law-locks.js` are deleted; what each promised is now proven here (`tests/hall_my_agents.mjs` `MOVED:`, `tests/government_tidy_up.mjs` case 5). Not carried over on purpose: Settings drew wandering levels above your rank's ceiling as disabled rows; here they are simply not offered.
13
14
  - **The fact keeps a floor.** The row grid is `minmax(118px, 160px) minmax(132px, 1fr) auto` since task 1005052: session cards' five chips wrap rather than squeeze its fact to one word a line.
14
15
  - **Governing is a held seat** (real since task 1004713, ADR 0377). `GET /me` carries `positions`, resolved by the server (`modules/government/positions.js`: the owner seat, a Lead in office, a seat on a building's sitting, the board under the constitution in force). `gov-positions.js` only reads that block — no constitution or rank. Items flagged `position: true` (the City, the Board Room, the Government group) hide for a builder the server says holds none, and the craft mode names Governing by the seat ("Planning Lead") or drops it. A `/me` with no block is unknown and keeps today's nav.
15
16
 
@@ -32,3 +32,5 @@ Also fixed here, as the board task handed forward: the two sub-24px links on Hom
32
32
  **Settings: the speciality panel became a shared component (task 1005052)** — `settings-specialities.js` is now `speciality-tools.js`, mounted by Settings and by My agents (records/my-agents.md); it renders the create form into settings.html's now-empty `<form id="speciality-create">`, and its rules moved verbatim from `settings.css` to `speciality-tools.css`, linked right after it. Settings was verified unchanged against a before-capture: the same elements, every computed style and the same text in five states (rest, train, offered skills, the create picker, the walkthrough). **Known gap, older than this:** the `skills-walkthrough` and `offered-skills` states never close the first-visit craft picker, so their clicks time out under the kit and the page reader (both notes are on main); with a `clickIfVisible [data-craft-close]` step they are ALL CLEAN.
33
33
 
34
34
  **Settings: a speciality's owner chooses what it offers (task 1004404, ADR 0310 §1)** — until this only the API could set an offer, so the walkthrough above never appeared for a speciality made in the hall. The create form gains a `<details>` "Skills it offers" picker (filled on first open, so a builder who never offers one never pays for the list), and an owned card gains "Offered skills", an inline `form.speciality-offer` like Train. Both list `GET /specialities/installed-skills` — only what this instance has — each row a checkbox with the skill's plain line and cost, the words adopters will see, under a line saying offering turns nothing on. A "Find a skill" box narrows the sixty-odd rows (Enter never submits), the list scrolls in its own box, an offered skill removed since is named and dropped on save rather than silently kept, and saving is ONE `PATCH { skills }`. **The checkboxes take the shared 24px rule in `panel.css`** (the `.chip-ctl input` / `.craft-pref` block, one selector added) rather than a fourth copy of the `::before` box; without it 128 targets measured 13px. **Kit:** `settings.states.json` `offered-skills` + `preferences`, ALL CLEAN at 1440/390/320 × dark/light, with fixtures `specialities__installed-skills.json` (the real 64 skills, explained) and an owned speciality in `specialities.json`, plus canned POST/PATCH answers. Tests: `tests/speciality_offer_ui.mjs` (runs the real page script).
35
+
36
+ **Settings: the agent settings left for My agents (task 1005044)** — How Claude talks to you, the specialities, Wandering, Rendering and Skill voices are gone from Settings (owner decision, idea 1001309): they were edited here and on My agents on the same routes. Preferences keeps Module install reports plus a `#agents-moved-preferences` line linking `/my-agents#talk`; Sound keeps the sounds-off note plus `#agents-moved-sound` linking `/my-agents#skills`. Old deep links go on through `settings-moved.js` (records/my-agents.md). `settings-interaction.js`, `settings-render.js` and `settings-law-locks.js` were deleted with their only host, and `settings.css` lost the voices table. The `skills-walkthrough` and `offered-skills` states left `settings.states.json` (My agents' `speciality-walk` and `speciality-forms` cover them). `tests/hall_settings_world.mjs` pins the absence, the two lines and the redirects.