@bongos/core 1.24.8 → 1.24.10

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 (88) hide show
  1. package/.bongos-core.json +131 -86
  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 +11 -1
  6. package/clients/bongos-client/index.mjs +8 -0
  7. package/docs/adr/0385-money-lives-with-a-processor-credits-stay-a-project-meter.md +1 -1
  8. package/docs/api/openapi.json +185 -6
  9. package/docs/api-reference.md +7 -3
  10. package/docs/architecture.md +2 -2
  11. package/docs/copy-inventory.md +58 -28
  12. package/docs/copy-registry.json +333 -33
  13. package/docs/module-api-changelog.md +4 -0
  14. package/docs/modules-contract.md +3 -3
  15. package/docs/page-inventory.json +34 -4
  16. package/docs/page-readings.json +294 -237
  17. package/migrations/core_287_module_prices_cash_only.sql +24 -0
  18. package/modules/hall-ui/public/ai-studio.html +2 -2
  19. package/modules/hall-ui/public/atlas.html +2 -2
  20. package/modules/hall-ui/public/blockers.html +2 -2
  21. package/modules/hall-ui/public/board-room.html +2 -2
  22. package/modules/hall-ui/public/city.html +2 -2
  23. package/modules/hall-ui/public/collab.html +2 -2
  24. package/modules/hall-ui/public/commands.html +2 -2
  25. package/modules/hall-ui/public/copy-desk.html +2 -2
  26. package/modules/hall-ui/public/craft-mode.js +1 -1
  27. package/modules/hall-ui/public/deploy.html +2 -2
  28. package/modules/hall-ui/public/diagrams.html +2 -2
  29. package/modules/hall-ui/public/drachmae.html +2 -2
  30. package/modules/hall-ui/public/fleet.html +2 -2
  31. package/modules/hall-ui/public/gate.html +2 -2
  32. package/modules/hall-ui/public/goal-map.html +2 -2
  33. package/modules/hall-ui/public/goals.html +2 -2
  34. package/modules/hall-ui/public/government.html +2 -2
  35. package/modules/hall-ui/public/idea.html +2 -2
  36. package/modules/hall-ui/public/ideas.html +2 -2
  37. package/modules/hall-ui/public/index.html +2 -2
  38. package/modules/hall-ui/public/market-watch-rules.html +2 -2
  39. package/modules/hall-ui/public/market-watch-settings.css +217 -0
  40. package/modules/hall-ui/public/market-watch-settings.html +113 -0
  41. package/modules/hall-ui/public/market-watch-settings.js +230 -0
  42. package/modules/hall-ui/public/market-watch-settings.states.json +14 -0
  43. package/modules/hall-ui/public/modules.html +2 -2
  44. package/modules/hall-ui/public/modules.js +5 -0
  45. package/modules/hall-ui/public/my-agents.html +2 -2
  46. package/modules/hall-ui/public/primer.html +2 -2
  47. package/modules/hall-ui/public/profile.html +2 -2
  48. package/modules/hall-ui/public/project-settings.html +2 -2
  49. package/modules/hall-ui/public/ranks.html +2 -2
  50. package/modules/hall-ui/public/roadmap.html +2 -2
  51. package/modules/hall-ui/public/roster.html +2 -2
  52. package/modules/hall-ui/public/sessions.html +2 -2
  53. package/modules/hall-ui/public/settings.html +2 -2
  54. package/modules/hall-ui/public/shell.js +3 -0
  55. package/modules/hall-ui/public/street.html +2 -2
  56. package/modules/hall-ui/public/studio.html +2 -2
  57. package/modules/hall-ui/public/task.html +2 -2
  58. package/modules/hall-ui/public/thinking.html +2 -2
  59. package/modules/hall-ui/public/tweak-editor.html +2 -2
  60. package/modules/hall-ui/public/watch.html +2 -2
  61. package/modules/hall-ui/public/work.html +2 -2
  62. package/modules/hall-ui/records/market-watch-settings.md +11 -0
  63. package/modules/market-watch/module.json +3 -2
  64. package/modules/market-watch/routes/settings.js +128 -0
  65. package/modules/market-watch/settings.js +207 -0
  66. package/modules/provisioning/starter-bundles.js +11 -6
  67. package/modules/ui-design/kit/serve.js +1 -0
  68. package/package-lock.json +2 -2
  69. package/package.json +1 -1
  70. package/release-notes.json +12 -0
  71. package/scripts/bongos/module-store-cli.js +21 -21
  72. package/scripts/bongos/module.js +8 -8
  73. package/src/bongos/module-listing.js +47 -20
  74. package/src/bongos/module-scope-map.js +1 -0
  75. package/src/bongos/module-store.js +20 -12
  76. package/src/bongos/routes/modules.js +31 -21
  77. package/src/bongos/serve-internal.js +2 -2
  78. package/src/module-api.js +1 -1
  79. package/tests/hall_nav.mjs +4 -2
  80. package/tests/hall_page_gate_map.mjs +4 -0
  81. package/tests/market_watch_data_db.mjs +27 -0
  82. package/tests/market_watch_module.mjs +8 -4
  83. package/tests/market_watch_settings.mjs +392 -0
  84. package/tests/module_store_cli.mjs +14 -3
  85. package/tests/module_store_listing.mjs +65 -14
  86. package/tests/module_store_price.mjs +97 -26
  87. package/tests/module_store_registry_migration.mjs +11 -0
  88. package/tests/nav_permission_atoms.mjs +3 -2
@@ -0,0 +1,230 @@
1
+ // market-watch-settings.js — the owner's Market Watch settings (task 1004938, goal 1000137),
2
+ // built to docs/design/mocks/market-watch/settings.html, option B, the fuel gauge.
3
+ //
4
+ // One read and three writes, all owner-only on the server (the project.settings.manage atom plus the owner seat):
5
+ // GET /market-watch/settings everything drawn here
6
+ // PATCH /market-watch/settings { enabled } | { monthly_cap_usd } | { cadence }
7
+ // PUT /market-watch/settings/key { key } (write-only: the answer says set or not set)
8
+ // DELETE /market-watch/settings/key
9
+ // Every write answers with the whole view, so the page redraws from what the server
10
+ // stored, never from what it asked for.
11
+ //
12
+ // A 403 shows the owner-only panel and a 404 the "module is off" panel, so a refusal is
13
+ // never mistaken for an empty gauge.
14
+ (() => {
15
+ 'use strict';
16
+
17
+ const API = '/api/bongos/market-watch/settings';
18
+ const client = window.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false });
19
+ const { escapeHtml, toast } = window.OTB;
20
+ const $ = (id) => document.getElementById(id);
21
+
22
+ const DAY_MS = 24 * 60 * 60 * 1000;
23
+ const CADENCE_DAYS = { weekly: 7, biweekly: 14, monthly: 30 };
24
+ const CADENCE_WORDS = { weekly: 'weekly', biweekly: 'every 2 weeks', monthly: 'monthly' };
25
+ const REASONS = {
26
+ cap_reached: 'cap reached',
27
+ off: 'switched off',
28
+ no_key: 'no key',
29
+ spend_unreadable: 'spend unreadable',
30
+ error: 'error',
31
+ lease_lost: 'cut off',
32
+ };
33
+ const FUTURE = 2; // empty cells after the next run, as the mockup draws them
34
+
35
+ let view = null;
36
+ let busy = false;
37
+
38
+ const money = (n) => (Number.isInteger(n) ? `$${n}` : `$${n.toFixed(2)}`);
39
+ const shortDate = (iso) => {
40
+ const d = new Date(iso);
41
+ return `${d.getUTCDate()} ${d.toLocaleString('en-US', { month: 'short', timeZone: 'UTC' }).toLowerCase()}`;
42
+ };
43
+ const weekday = (iso) => new Date(iso).toLocaleString('en-US', { weekday: 'long', timeZone: 'UTC' }).toLowerCase();
44
+
45
+ function show(id) {
46
+ for (const s of ['mws-sealed', 'mws-off', 'mws-body']) $(s).hidden = s !== id;
47
+ }
48
+ function error(message) {
49
+ $('mws-error').hidden = !message;
50
+ $('mws-error').textContent = message || '';
51
+ }
52
+
53
+ async function send(method, body) {
54
+ let res;
55
+ try {
56
+ res = await client.request(method, API + (method === 'PUT' || method === 'DELETE' ? '/key' : ''), body === undefined ? undefined : { body });
57
+ } catch (_) {
58
+ return { ok: false, message: 'Could not reach the server.' };
59
+ }
60
+ if (res.status === 401) { window.location.assign('/api/bongos/auth/web/start'); return { ok: false }; }
61
+ if (res.status === 403) { show('mws-sealed'); return { ok: false }; }
62
+ if (res.status === 404) { show('mws-off'); return { ok: false }; }
63
+ if (!res.ok) {
64
+ const err = (res.data && res.data.error) || {};
65
+ return { ok: false, message: err.message && err.message !== err.code ? err.message : 'That did not save.' };
66
+ }
67
+ return { ok: true, data: res.data };
68
+ }
69
+
70
+ // ---- drawing ---------------------------------------------------------------------
71
+
72
+ function gauge(v, cap) {
73
+ const spent = v.spent_this_month_usd;
74
+ const run = v.run_estimate_usd || 0;
75
+ const p = Math.min(100, (spent / cap) * 100);
76
+ const n = Math.max(0, Math.min(100 - p, (run / cap) * 100));
77
+ $('mws-arc').setAttribute('stroke-dasharray', `${p} 100`);
78
+ $('mws-arc2').setAttribute('stroke-dasharray', `0 ${p} ${v.enabled ? n : 0} 100`);
79
+ $('mws-spent').textContent = money(spent);
80
+ $('mws-of').textContent = `of ${money(cap)} · ${v.month}`;
81
+ $('mws-ring').classList.toggle('mws-ring--off', !v.enabled);
82
+ let left;
83
+ if (!v.enabled) left = 'paused · $0 from now';
84
+ else if (!run) left = 'no run yet to measure';
85
+ else {
86
+ const l = Math.floor(Math.max(0, cap - spent) / run);
87
+ left = l > 0 ? `${l} run${l > 1 ? 's' : ''} of fuel left` : 'next run will be skipped';
88
+ }
89
+ $('mws-left').textContent = left;
90
+ $('mws-left').classList.toggle('mws-pill--empty', v.enabled && !!run && Math.floor(Math.max(0, cap - spent) / run) <= 0);
91
+ $('mws-cap-t').textContent = money(cap);
92
+ $('mws-hint').textContent = run
93
+ ? `about ${Math.floor(cap / run)} runs a month · a run costs ${v.run_estimate_basis === 'ceiling' ? 'at most' : 'about'} $${run.toFixed(2)}`
94
+ : 'a run’s cost shows here after the first one';
95
+ }
96
+
97
+ function strip(v) {
98
+ const ran = v.recent_runs.slice().reverse(); // oldest first, left to right
99
+ const cells = ran.map((r) => {
100
+ const skipped = r.status === 'skipped' || r.status === 'failed';
101
+ const why = REASONS[r.reason] || r.reason || '';
102
+ const label = skipped
103
+ ? `${shortDate(r.created_at)}: skipped${why ? `, ${why}` : ''}`
104
+ : `${shortDate(r.created_at)}: ${r.items_total} names watched${r.status === 'partial' ? `, stopped early${why ? ` (${why})` : ''}` : ''}`;
105
+ return `<li class="mws-wk ${skipped ? 'mws-wk--skip' : 'mws-wk--done'}" title="${escapeHtml(label)}" aria-label="${escapeHtml(label)}">`
106
+ + `<b>${skipped ? '—' : escapeHtml(String(r.items_total))}</b><span>${escapeHtml(shortDate(r.created_at))}</span></li>`;
107
+ });
108
+ if (v.enabled) {
109
+ const days = CADENCE_DAYS[v.cadence] || 7;
110
+ const next = v.next_run.due_at ? new Date(v.next_run.due_at) : new Date();
111
+ const nextLabel = v.next_run.state === 'soon' ? 'soon' : shortDate(next.toISOString());
112
+ cells.push(`<li class="mws-wk mws-wk--next" aria-label="next run: ${escapeHtml(nextLabel)}"><b>?</b><span>${escapeHtml(nextLabel)}</span></li>`);
113
+ for (let i = 1; i <= FUTURE; i += 1) {
114
+ const d = new Date(next.getTime() + i * days * DAY_MS).toISOString();
115
+ cells.push(`<li class="mws-wk" aria-hidden="true"><b></b><span>${escapeHtml(shortDate(d))}</span></li>`);
116
+ }
117
+ }
118
+ $('mws-weeks').style.setProperty('--mws-cells', String(Math.max(cells.length, 3)));
119
+ $('mws-weeks').innerHTML = cells.join('') || '';
120
+ const reasons = [...new Set(v.recent_runs.filter((r) => r.status === 'skipped' || r.status === 'failed').map((r) => REASONS[r.reason] || r.reason).filter(Boolean))];
121
+ const legend = [];
122
+ if (ran.length) legend.push('<span>number = names watched</span>');
123
+ if (reasons.length) legend.push(`<span class="mws-legend__skip">— = skipped, ${escapeHtml(reasons.join(', '))}</span>`);
124
+ if (v.enabled) legend.push('<span>dashed = next run</span>');
125
+ if (!ran.length) legend.unshift('<span>no runs yet</span>');
126
+ $('mws-legend').innerHTML = legend.join('');
127
+ }
128
+
129
+ function draw(v) {
130
+ view = v;
131
+ show('mws-body');
132
+ error('');
133
+ const cap = v.monthly_cap_usd;
134
+ $('mws-power').setAttribute('aria-pressed', String(v.enabled));
135
+ $('mws-power-t').textContent = v.enabled ? 'Market Watch is on' : 'Market Watch is off';
136
+ const slider = $('mws-cap');
137
+ slider.max = String(Math.max(60, Math.ceil(cap / 5) * 5));
138
+ slider.value = String(cap);
139
+ slider.setAttribute('aria-valuetext', `${money(cap)} a month`);
140
+ for (const b of $('mws-cad').querySelectorAll('button')) {
141
+ const on = b.dataset.c === v.cadence;
142
+ b.classList.toggle('mws-chip--on', on);
143
+ b.setAttribute('aria-pressed', String(on));
144
+ }
145
+ gauge(v, cap);
146
+ strip(v);
147
+ $('mws-key-state').textContent = v.key_set ? 'set' : 'not set';
148
+ $('mws-key-state').classList.toggle('mws-key__state--set', v.key_set);
149
+ $('mws-key-clear').hidden = !v.key_set;
150
+ $('mws-key-save').textContent = v.key_set ? 'Replace' : 'Save';
151
+ $('mws-key-in').disabled = !v.storage_configured;
152
+ $('mws-key-save').disabled = !v.storage_configured;
153
+ $('mws-key-note').textContent = !v.storage_configured
154
+ ? 'Key storage is not set up on this project yet, so a key cannot be saved.'
155
+ : v.enabled && !v.key_set
156
+ ? 'Market Watch is on but has no key, so each run will be skipped until one is set.'
157
+ : 'Runs use this key, so the project’s Anthropic account pays for them. It is stored encrypted and never shown again.';
158
+ }
159
+
160
+ async function write(method, body, done) {
161
+ if (busy) return;
162
+ busy = true;
163
+ const r = await send(method, body);
164
+ busy = false;
165
+ if (!r.ok) { if (r.message) error(r.message); if (view) draw(view); return; }
166
+ draw(r.data);
167
+ if (done) toast(done(r.data), { clear: true, duration: 2200 });
168
+ }
169
+
170
+ // ---- controls --------------------------------------------------------------------
171
+
172
+ $('mws-power').addEventListener('click', () => {
173
+ if (!view) return;
174
+ write('PATCH', { enabled: !view.enabled }, (v) => {
175
+ if (!v.enabled) return 'off · nothing runs';
176
+ return v.next_run.due_at ? `on · next run ${weekday(v.next_run.due_at)}` : 'on · next run soon';
177
+ });
178
+ });
179
+
180
+ $('mws-cap').addEventListener('input', (e) => {
181
+ if (!view) return;
182
+ const cap = Number(e.target.value);
183
+ e.target.setAttribute('aria-valuetext', `${money(cap)} a month`);
184
+ gauge(view, cap);
185
+ });
186
+ $('mws-cap').addEventListener('change', (e) => {
187
+ const cap = Number(e.target.value);
188
+ if (!view || cap === view.monthly_cap_usd) return;
189
+ write('PATCH', { monthly_cap_usd: cap }, (v) => `cap ${money(v.monthly_cap_usd)} a month`);
190
+ });
191
+
192
+ $('mws-cad').addEventListener('click', (e) => {
193
+ const b = e.target.closest('button[data-c]');
194
+ if (!b || !view || b.dataset.c === view.cadence) return;
195
+ write('PATCH', { cadence: b.dataset.c }, (v) => `runs ${CADENCE_WORDS[v.cadence] || v.cadence}`);
196
+ });
197
+
198
+ $('mws-key-save').addEventListener('click', () => {
199
+ const input = $('mws-key-in');
200
+ const key = input.value.trim();
201
+ if (!key) { error('Paste the key first.'); input.focus(); return; }
202
+ // The field is emptied before the request goes out: the key never sits in the page
203
+ // longer than it takes to send it.
204
+ input.value = '';
205
+ write('PUT', { key }, () => 'key saved');
206
+ });
207
+
208
+ // Clearing asks twice: the first press turns the button into its own question.
209
+ let clearArmed = null;
210
+ $('mws-key-clear').addEventListener('click', () => {
211
+ const b = $('mws-key-clear');
212
+ if (!clearArmed) {
213
+ b.textContent = 'Clear the key?';
214
+ clearArmed = setTimeout(() => { clearArmed = null; b.textContent = 'Clear'; }, 4000);
215
+ return;
216
+ }
217
+ clearTimeout(clearArmed);
218
+ clearArmed = null;
219
+ b.textContent = 'Clear';
220
+ write('DELETE', undefined, () => 'key cleared · runs will be skipped');
221
+ });
222
+
223
+ async function load() {
224
+ const r = await send('GET');
225
+ if (r.ok) draw(r.data);
226
+ else if (r.message) error(r.message);
227
+ }
228
+
229
+ document.addEventListener('DOMContentLoaded', load);
230
+ })();
@@ -0,0 +1,14 @@
1
+ {
2
+ "_": "MARKET WATCH SETTINGS (task 1004938; design of record docs/design/mocks/market-watch/settings.html, option B, the fuel gauge) through modules/ui-design/kit/render.js. The page reads ONE owner-only route, GET /market-watch/settings, so against the kit's own stub it renders its 'module is off' state (the 404), which the floors must hold too. To look at the populated gauge run it against the hall-preview harness, whose market-watch__settings.json fixture carries the mockup's month (12.40 of 20 spent, six runs, one skipped at the cap):\n\n node scripts/hall-preview/server.js --port 4620 --fixture-me --rank archon\n node modules/ui-design/kit/render.js --states modules/hall-ui/public/market-watch-settings.states.json --base-in http://localhost:4620 --base-out http://localhost:4620",
3
+ "page": "/builders/market-watch-settings",
4
+ "surface": "hall-ui",
5
+ "stub": { "prefix": "/builders/" },
6
+ "modeQuery": false,
7
+ "ignoreRequests": ["/api/(gds|bongos)/"],
8
+ "ignoreConsole": ["/api/(gds|bongos)/", "\\b401\\b", "\\b403\\b", "\\b404\\b", "Unauthorized", "Forbidden", "Not Found", "load failed"],
9
+ "states": {
10
+ "gauge": { "auth": true, "actions": [["wait", 900], ["clickIfVisible", "[data-craft-close]"], ["wait", 300]], "expect": { "visible": ["#mws-body"] } },
11
+ "out": { "auth": false, "actions": [["wait", 600]] }
12
+ },
13
+ "reducedMotion": { "auth": true, "actions": [["wait", 800]] }
14
+ }
@@ -127,8 +127,8 @@
127
127
 
128
128
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
129
129
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
130
- <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
131
- <script src="/builders/shell.js?v=2026-10-25-escape"></script>
130
+ <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
131
+ <script src="/builders/shell.js?v=2026-10-30-market-watch"></script>
132
132
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
133
133
  <script src="/builders/palette.js" defer></script>
134
134
  <script src="/builders/brand-apply.js"></script>
@@ -342,6 +342,11 @@
342
342
  const facts = [m.is_new
343
343
  ? '<span class="fact-pill fact-pill--info" title="No real-world data yet, so it is listed but not ranked.">new</span>'
344
344
  : `<span class="ov-fact">scores <b>${escapeHtml(String(m.overall))}</b></span>`];
345
+ // A module that still carries only an old credit price stays listed, marked
346
+ // until its author sets a cash price (task 1004989): credits never buy one.
347
+ if (m.price && m.price.coming) {
348
+ facts.push('<span class="fact-pill fact-pill--info" title="Its author has been asked to set a cash price. Credits cannot buy a module.">price coming</span>');
349
+ }
345
350
  const sorted = sortedPartFact(m);
346
351
  if (sorted) facts.push(sorted);
347
352
  const strong = Array.isArray(m.strengths) ? m.strengths : [];
@@ -61,8 +61,8 @@
61
61
 
62
62
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
63
63
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
64
- <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
65
- <script src="/builders/shell.js?v=2026-10-25-escape"></script>
64
+ <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
65
+ <script src="/builders/shell.js?v=2026-10-30-market-watch"></script>
66
66
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
67
67
  <script src="/builders/palette.js" defer></script>
68
68
  <script src="/builders/gov-living.js?v=2026-10-10-real1"></script>
@@ -112,8 +112,8 @@
112
112
 
113
113
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
114
114
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
115
- <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
116
- <script src="/builders/shell.js?v=2026-10-25-escape"></script>
115
+ <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
116
+ <script src="/builders/shell.js?v=2026-10-30-market-watch"></script>
117
117
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
118
118
  <script src="/builders/palette.js" defer></script>
119
119
  <!-- the shared markdown reader (window.OTBMdReader) — primer.js renders through
@@ -207,8 +207,8 @@
207
207
  chrome exists before the page script runs. -->
208
208
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
209
209
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
210
- <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
211
- <script src="/builders/shell.js?v=2026-10-25-escape"></script>
210
+ <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
211
+ <script src="/builders/shell.js?v=2026-10-30-market-watch"></script>
212
212
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
213
213
  <script src="/builders/palette.js" defer></script>
214
214
  <!-- task 1004727: the ONE karma vote control every task/idea list renders -->
@@ -71,8 +71,8 @@
71
71
 
72
72
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
73
73
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
74
- <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
75
- <script src="/builders/shell.js?v=2026-10-25-escape"></script>
74
+ <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
75
+ <script src="/builders/shell.js?v=2026-10-30-market-watch"></script>
76
76
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
77
77
  <script src="/builders/palette.js" defer></script>
78
78
  <script src="/builders/hall-kit.js"></script>
@@ -73,8 +73,8 @@
73
73
  chrome exists before the page script runs. -->
74
74
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
75
75
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
76
- <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
77
- <script src="/builders/shell.js?v=2026-10-25-escape"></script>
76
+ <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
77
+ <script src="/builders/shell.js?v=2026-10-30-market-watch"></script>
78
78
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
79
79
  <script src="/builders/palette.js" defer></script>
80
80
  <script src="/builders/founding-mark.js?v=2026-09-30-founding"></script>
@@ -43,8 +43,8 @@
43
43
 
44
44
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
45
45
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
46
- <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
47
- <script src="/builders/shell.js?v=2026-10-25-escape"></script>
46
+ <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
47
+ <script src="/builders/shell.js?v=2026-10-30-market-watch"></script>
48
48
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
49
49
  <script src="/builders/palette.js" defer></script>
50
50
  <!-- The chart kit and the timeline's series builders (task 1003447). Both MUST
@@ -55,8 +55,8 @@
55
55
  chrome exists before the page script runs. -->
56
56
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
57
57
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
58
- <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
59
- <script src="/builders/shell.js?v=2026-10-25-escape"></script>
58
+ <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
59
+ <script src="/builders/shell.js?v=2026-10-30-market-watch"></script>
60
60
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
61
61
  <script src="/builders/palette.js" defer></script>
62
62
  <!-- the branding pack's currency label, loaded BEFORE roster.js. -->
@@ -111,8 +111,8 @@
111
111
  chrome exists before the page script runs. -->
112
112
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
113
113
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
114
- <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
115
- <script src="/builders/shell.js?v=2026-10-25-escape"></script>
114
+ <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
115
+ <script src="/builders/shell.js?v=2026-10-30-market-watch"></script>
116
116
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
117
117
  <script src="/builders/palette.js" defer></script>
118
118
  <script src="/builders/task-detail.js?v=2026-07-03-taskdetail"></script>
@@ -486,8 +486,8 @@
486
486
  chrome exists before the page scripts run. -->
487
487
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
488
488
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
489
- <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
490
- <script src="/builders/shell.js?v=2026-10-25-escape"></script>
489
+ <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
490
+ <script src="/builders/shell.js?v=2026-10-30-market-watch"></script>
491
491
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
492
492
  <script src="/builders/palette.js" defer></script>
493
493
  <!-- shared component kit (window.OTBKit) — section rail (task 2166). -->
@@ -125,6 +125,7 @@
125
125
  settings: '<circle cx="9" cy="9" r="2.5"/><path d="M9 2.5v2m0 9v2M2.5 9h2m9 0h2M4.4 4.4l1.4 1.4m6.4 6.4 1.4 1.4m0-9.2-1.4 1.4M5.8 12.2l-1.4 1.4"/>',
126
126
  deploy: '<path d="M9 12V3M5.5 6.5 9 3l3.5 3.5M3 12.5v2a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1v-2"/>',
127
127
  fleet: '<path d="M4 2.5h10v13l-1.7-1-1.6 1-1.7-1-1.7 1-1.6-1-1.7 1v-13Z"/><path d="M6.5 6h5M6.5 9h5M6.5 12h3"/>',
128
+ fuel: '<path d="M3 13a6 6 0 1 1 12 0"/><path d="M9 13l3-4"/>',
128
129
  permissions: '<circle cx="6" cy="12" r="3.5"/><path d="M8.5 9.5 15 3M12.5 5.5l2 2M10.5 7.5l1.5 1.5"/>',
129
130
  // The governor city: a pediment over columns, the Governing mode's own glyph.
130
131
  city: '<path d="M2.5 7 9 3l6.5 4M4 7.5v6.5M7 7.5v6.5M11 7.5v6.5M14 7.5v6.5M2.5 15.5h13"/>',
@@ -386,6 +387,8 @@
386
387
  // of `provisioning.fleet.manage`, the atom its page and its one read check (the
387
388
  // Deploy precedent above); dropped where the provisioning module is not enabled.
388
389
  { id: 'fleet', label: 'Fleet costs', icon: 'fleet', href: '/fleet', gate: 'government', module: 'provisioning', position: true },
390
+ // task 1004938: owner-only Market Watch settings; 'archon' mirrors its routes (cosmetic, ADR 0016).
391
+ { id: 'market-watch-settings', label: 'Market Watch', icon: 'fuel', href: '/market-watch-settings', gate: 'archon', module: 'market-watch', position: true },
389
392
  // BV1.R113: the roles-and-permissions tab (ADR 0151). The one genuinely
390
393
  // Archon-only item the 'government' gate comment anticipated — cosmetic
391
394
  // only. The wall is the `government.manage` atom checked on each of the
@@ -230,8 +230,8 @@
230
230
 
231
231
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
232
232
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
233
- <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
234
- <script src="/builders/shell.js?v=2026-10-25-escape"></script>
233
+ <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
234
+ <script src="/builders/shell.js?v=2026-10-30-market-watch"></script>
235
235
  <script src="/builders/palette.js" defer></script>
236
236
  <script src="/builders/gov-living.js?v=2026-10-09-positions1"></script>
237
237
  <script src="/builders/gov-feeds.js?v=2026-10-09-living3"></script>
@@ -218,8 +218,8 @@
218
218
 
219
219
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
220
220
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
221
- <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
222
- <script src="/builders/shell.js?v=2026-10-25-escape"></script>
221
+ <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
222
+ <script src="/builders/shell.js?v=2026-10-30-market-watch"></script>
223
223
  <script src="/builders/palette.js" defer></script>
224
224
  <!-- The kit, for its ONE renderer: the full-size ship-time visual figure
225
225
  (task 1003829), which the older reviews still show. -->
@@ -58,8 +58,8 @@
58
58
  time so the chrome exists before the deferred page scripts execute. -->
59
59
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
60
60
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
61
- <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
62
- <script src="/builders/shell.js?v=2026-10-25-escape"></script>
61
+ <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
62
+ <script src="/builders/shell.js?v=2026-10-30-market-watch"></script>
63
63
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
64
64
  <script src="/builders/palette.js" defer></script>
65
65
  <!-- task-detail.js still owns the #/idea/N and #/blocker/N deep-link overlay
@@ -140,8 +140,8 @@
140
140
 
141
141
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
142
142
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
143
- <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
144
- <script src="/builders/shell.js?v=2026-10-25-escape"></script>
143
+ <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
144
+ <script src="/builders/shell.js?v=2026-10-30-market-watch"></script>
145
145
  <script src="/builders/palette.js" defer></script>
146
146
 
147
147
  <script src="/builders/idea-objects.js?v=2026-09-27-split"></script>
@@ -144,8 +144,8 @@
144
144
 
145
145
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
146
146
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
147
- <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
148
- <script src="/builders/shell.js?v=2026-10-25-escape"></script>
147
+ <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
148
+ <script src="/builders/shell.js?v=2026-10-30-market-watch"></script>
149
149
  <script src="/builders/palette.js" defer></script>
150
150
  <script src="/builders/brand-holes.js?v=2026-09-29-aqfix"></script>
151
151
  <script src="/builders/tweak-editor-lib.js?v=2026-09-29-tw27"></script>
@@ -165,8 +165,8 @@
165
165
  chrome exists before the page script runs. -->
166
166
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
167
167
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
168
- <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
169
- <script src="/builders/shell.js?v=2026-10-25-escape"></script>
168
+ <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
169
+ <script src="/builders/shell.js?v=2026-10-30-market-watch"></script>
170
170
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
171
171
  <script src="/builders/palette.js" defer></script>
172
172
  <!-- shared component kit (window.OTBKit) — tabs, ledger, pager (task 2166). -->
@@ -173,8 +173,8 @@
173
173
  time so the chrome exists before the deferred page scripts execute. -->
174
174
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
175
175
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
176
- <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
177
- <script src="/builders/shell.js?v=2026-10-25-escape"></script>
176
+ <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
177
+ <script src="/builders/shell.js?v=2026-10-30-market-watch"></script>
178
178
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
179
179
  <script src="/builders/palette.js" defer></script>
180
180
  <!-- task 1004727: the ONE karma vote control every task/idea list renders -->
@@ -0,0 +1,11 @@
1
+ # Market Watch settings, at /market-watch-settings (task 1004938)
2
+
3
+ **The owner's fuel gauge for Market Watch (goal 1000137), built to `docs/design/mocks/market-watch/settings.html`, option B.** One panel: the gauge (spend this month against the monthly AI cap, with a faint arc for what the next run will use), the on/off pill, the cap slider, the cadence chips (weekly, every 2 weeks, monthly), the strip of recent runs, and the project's Anthropic key. Five things it settled:
4
+
5
+ - **The routes are the wall, not the page.** `GET`/`PATCH /market-watch/settings` and `PUT`/`DELETE /market-watch/settings/key` are owner-only: the `project.settings.manage` atom, then an in-route owner-seat (Archon) check (modules/market-watch/routes/settings.js). The shell joins `SIGNEDIN_BUILDERS_PAGE_RE` and holds no data; a 403 draws the owner-only panel and a 404 (module off, so its routes are not mounted) the "turn the module on" panel. The nav item sits in Government, `archon`, `module: 'market-watch'`.
6
+ - **The key is write-only.** The page learns `key_set` and nothing else, not the last four. The field is emptied before the request goes out; Clear asks twice by turning into its own question.
7
+ - **The page redraws from the server's answer.** Every write returns the whole view, so the gauge never shows a value the server did not store.
8
+ - **The strip is the run ledger, oldest left.** A ran run shows the names it watched; a skipped one is a hatched dash, with its reason in the cell's label and the legend (`cap reached`, `switched off`, `no key`, …); the dashed tile is the next run and the two after it are empty. When the watch is off the future tiles go. The seed run never shows. At 620px and below the dates stack day over month, or nine tiles overflow at 390px.
9
+ - **Mockup colours were not kept.** The ring, chips and tiles use the hall's `--accent`, `--accent-tint`, `--accent-text`, `--rule*` and `--ok-weak`; the mock's own fonts were dropped for the pack's.
10
+
11
+ Fixture: `scripts/hall-preview/fixtures/market-watch__settings.json` (the mockup's month). Tests: `tests/market_watch_settings.mjs` (403s for a Metic and a Xenos on all four routes, switching off stops the next run against `run.js`'s own gate, the key never in a response).
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "key": "market-watch",
3
3
  "title": "Market Watch (a weekly AI watch on a project's market)",
4
- "description": "A weekly AI watch on a project's competitors, partners and topics, with a sourced log per tracked item and a weekly question to members (goal 1000137). Its data model (tables, read queries and the hub seed of the 2026-10-28 research), its weekly run (an hourly poller that researches each tracked item on the project's own Anthropic key, under the monthly cap), the weekly ask card in the hall's attention inbox (task 1004940) and its rules page (the watch rules in plain words, members' proposed changes, and the owner's or the government's decision on each, task 1004941) are in. Off by default, and it cannot be turned on behind an open join door with no payer, because every run will spend the project's AI budget.",
4
+ "description": "A weekly AI watch on a project's competitors, partners and topics, with a sourced log per tracked item and a weekly question to members (goal 1000137). Its data model (tables, read queries and the hub seed of the 2026-10-28 research), its weekly run (an hourly poller that researches each tracked item on the project's own Anthropic key, under the monthly cap), the weekly ask card in the hall's attention inbox (task 1004940), its rules page (the watch rules in plain words, members' proposed changes, and the owner's or the government's decision on each, task 1004941) and the owner's settings (on/off, monthly cap, cadence, the project key; task 1004938) are in. Off by default, and it cannot be turned on behind an open join door with no payer, because every run will spend the project's AI budget.",
5
5
  "version": "0.4.0",
6
6
  "coreVersion": "^1.22.0",
7
7
  "category": {
@@ -15,7 +15,8 @@
15
15
  "migrations": true,
16
16
  "routes": [
17
17
  "ask",
18
- "rules"
18
+ "rules",
19
+ "settings"
19
20
  ],
20
21
  "pollers": [
21
22
  "hub-seed",