@bongos/core 1.20.59 → 1.20.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bongos-core.json +151 -91
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +10 -0
- package/clients/bongos-client/index.cjs +10 -0
- package/clients/bongos-client/index.d.ts +14 -1
- package/clients/bongos-client/index.mjs +10 -0
- package/docs/adr/0358-a-hall-wears-a-look-and-its-night-takes-a-tint.md +52 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +324 -3
- package/docs/api-reference.md +8 -3
- package/docs/copy-inventory.md +343 -333
- package/docs/copy-registry.json +527 -433
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +4 -1
- package/docs/page-readings.json +758 -707
- package/modules/autonomy/db.js +13 -6
- package/modules/autonomy/migrations/autonomy_004_runner_identity.sql +30 -0
- package/modules/autonomy/routes/autonomy.js +16 -0
- package/modules/autonomy/runner-health.js +66 -5
- package/modules/hall-ui/public/atlas.html +1 -1
- package/modules/hall-ui/public/blockers.html +1 -1
- package/modules/hall-ui/public/board-room.html +1 -1
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/commands.html +1 -1
- package/modules/hall-ui/public/copy-desk.html +1 -1
- package/modules/hall-ui/public/deploy.html +1 -1
- package/modules/hall-ui/public/diagrams.html +1 -1
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/fleet.html +1 -1
- package/modules/hall-ui/public/gate.html +1 -1
- package/modules/hall-ui/public/gate.js +24 -8
- package/modules/hall-ui/public/goal-map.html +1 -1
- package/modules/hall-ui/public/goals.html +1 -1
- package/modules/hall-ui/public/government.html +1 -1
- package/modules/hall-ui/public/idea.html +1 -1
- package/modules/hall-ui/public/ideas.html +1 -1
- package/modules/hall-ui/public/index.html +1 -1
- package/modules/hall-ui/public/modules.html +1 -1
- package/modules/hall-ui/public/primer.html +1 -1
- package/modules/hall-ui/public/profile.html +1 -1
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/ranks.html +1 -1
- package/modules/hall-ui/public/roadmap.html +1 -1
- package/modules/hall-ui/public/roster.html +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings.html +1 -1
- package/modules/hall-ui/public/shell.js +5 -1
- package/modules/hall-ui/public/studio.html +1 -1
- package/modules/hall-ui/public/style.css +8 -0
- package/modules/hall-ui/public/task.html +1 -1
- package/modules/hall-ui/public/thinking.html +1 -1
- package/modules/hall-ui/public/tweak-editor.html +1 -1
- package/modules/hall-ui/public/watch.html +1 -1
- package/modules/hall-ui/public/work.html +1 -1
- package/modules/provisioning/look.js +169 -0
- package/modules/provisioning/migrations/provisioning_034_project_logo.sql +35 -0
- package/modules/provisioning/module.json +2 -1
- package/modules/provisioning/provisioning.js +4 -3
- package/modules/provisioning/routes/look.js +204 -0
- package/modules/provisioning/routes/provisioning.js +5 -59
- package/modules/provisioning/settings-push.js +76 -0
- package/modules/public-landing/public/assets/cosmos.css +1 -1
- package/modules/public-landing/public/projects.html +499 -28
- package/modules/public-landing/public/projects.probes.json +2 -2
- package/modules/public-landing/public/projects.states.json +6 -3
- package/modules/ui-design/kit/serve.js +50 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/gds/autobongos-run.js +43 -2
- package/scripts/gds/fitness.js +4 -0
- package/src/bongos/routes.js +4 -1
- package/src/bongos/serve-internal.js +6 -0
- package/src/branding.js +26 -1
- package/src/hall-look.js +99 -0
- package/src/module-api.js +1 -1
- package/src/night-tint.js +26 -0
- package/tests/autonomy_runner_identity.mjs +215 -0
- package/tests/currency_label.mjs +7 -4
- package/tests/hall_tokens.mjs +10 -1
- package/tests/hall_tweak_editor.mjs +1 -1
- package/tests/look_night_tint.mjs +169 -0
- package/tests/nav_permission_atoms.mjs +1 -1
- package/tests/projects_hub.mjs +5 -4
- package/tests/projects_hub_app_status.mjs +1 -1
- package/tests/projects_hub_app_step.mjs +8 -7
- package/tests/projects_hub_look.mjs +255 -0
- package/tests/projects_hub_module_picker.mjs +2 -2
- package/tests/provision_settings_apply.mjs +12 -4
- package/tests/provisioning_look.mjs +376 -0
- package/tests/provisioning_settings.mjs +10 -7
- package/tests/provisioning_settings_apply.mjs +4 -4
- package/tests/provisioning_settings_env.mjs +4 -0
- package/tests/wizard_draft_resume.mjs +2 -2
- package/tests/wizard_front_door.mjs +2 -2
- package/tests/wizard_intent_resume.mjs +16 -15
package/modules/autonomy/db.js
CHANGED
|
@@ -162,11 +162,11 @@ async function disallowGoal(goalId) {
|
|
|
162
162
|
// recordRunnerHeartbeat — upsert one host's check-in. ON CONFLICT so the normal
|
|
163
163
|
// case is one write, and `host` is the key so a second runner appears as a second
|
|
164
164
|
// row rather than silently overwriting the first.
|
|
165
|
-
async function recordRunnerHeartbeat({ builderId, host, pid = null, startedAt = null, mode = null, consecutiveFailures = null, lastEvent = null, workingTaskId = null, workingGoalId = null }) {
|
|
165
|
+
async function recordRunnerHeartbeat({ builderId, host, pid = null, startedAt = null, mode = null, consecutiveFailures = null, lastEvent = null, workingTaskId = null, workingGoalId = null, head = null, diskHead = null, mainHead = null, claudeAccount = null }) {
|
|
166
166
|
const { rows } = await pool.query(
|
|
167
167
|
`INSERT INTO autonomy_runner_heartbeat
|
|
168
|
-
(builder_id, host, pid, started_at, mode, consecutive_failures, last_event, working_task_id, working_goal_id, last_seen_at, updated_at)
|
|
169
|
-
VALUES ($8, $1, $2, $3, $4, $5, $6, $7, $9, now(), now())
|
|
168
|
+
(builder_id, host, pid, started_at, mode, consecutive_failures, last_event, working_task_id, working_goal_id, head, disk_head, main_head, claude_account, last_seen_at, updated_at)
|
|
169
|
+
VALUES ($8, $1, $2, $3, $4, $5, $6, $7, $9, $10, $11, $12, $13, now(), now())
|
|
170
170
|
ON CONFLICT (builder_id, host) DO UPDATE SET
|
|
171
171
|
pid = EXCLUDED.pid,
|
|
172
172
|
started_at = COALESCE(EXCLUDED.started_at, autonomy_runner_heartbeat.started_at),
|
|
@@ -175,10 +175,16 @@ async function recordRunnerHeartbeat({ builderId, host, pid = null, startedAt =
|
|
|
175
175
|
last_event = EXCLUDED.last_event,
|
|
176
176
|
working_task_id = EXCLUDED.working_task_id,
|
|
177
177
|
working_goal_id = EXCLUDED.working_goal_id,
|
|
178
|
+
-- Overwritten, never COALESCEd (task 1004407): a fresh process that cannot
|
|
179
|
+
-- read its own commit must say "unknown", not inherit the last process's.
|
|
180
|
+
head = EXCLUDED.head,
|
|
181
|
+
disk_head = EXCLUDED.disk_head,
|
|
182
|
+
main_head = EXCLUDED.main_head,
|
|
183
|
+
claude_account = EXCLUDED.claude_account,
|
|
178
184
|
last_seen_at = now(),
|
|
179
185
|
updated_at = now()
|
|
180
|
-
RETURNING host, pid, started_at, mode, consecutive_failures, last_event, working_task_id, working_goal_id, last_seen_at`,
|
|
181
|
-
[String(host).toLowerCase().slice(0, 255), pid, startedAt, mode, consecutiveFailures, lastEvent, workingTaskId, builderId, workingGoalId]
|
|
186
|
+
RETURNING host, pid, started_at, mode, consecutive_failures, last_event, working_task_id, working_goal_id, head, disk_head, main_head, claude_account, last_seen_at`,
|
|
187
|
+
[String(host).toLowerCase().slice(0, 255), pid, startedAt, mode, consecutiveFailures, lastEvent, workingTaskId, builderId, workingGoalId, head, diskHead, mainHead, claudeAccount]
|
|
182
188
|
);
|
|
183
189
|
return rows[0] || null;
|
|
184
190
|
}
|
|
@@ -193,7 +199,8 @@ async function recordRunnerHeartbeat({ builderId, host, pid = null, startedAt =
|
|
|
193
199
|
// liveness view entirely (grader finding, task 1003905).
|
|
194
200
|
async function readRunnerHeartbeats(builderId) {
|
|
195
201
|
const { rows } = await pool.query(
|
|
196
|
-
`SELECT host, pid, started_at, mode, consecutive_failures, last_event, working_task_id, working_goal_id,
|
|
202
|
+
`SELECT host, pid, started_at, mode, consecutive_failures, last_event, working_task_id, working_goal_id,
|
|
203
|
+
head, disk_head, main_head, claude_account, last_seen_at
|
|
197
204
|
FROM autonomy_runner_heartbeat
|
|
198
205
|
WHERE builder_id = $1
|
|
199
206
|
ORDER BY last_seen_at DESC
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
-- autonomy_004_runner_identity.sql — what the runner card can say about a runner
|
|
2
|
+
-- (task 1004407).
|
|
3
|
+
--
|
|
4
|
+
-- Owner ruling, 2026-10-01: the gate page must show which code each runner is
|
|
5
|
+
-- running, whether it is live or restarting, which always-on PC it is on, and
|
|
6
|
+
-- which Claude account its workers spend. `host` already names the PC
|
|
7
|
+
-- (autonomy_002); these four columns carry the rest.
|
|
8
|
+
--
|
|
9
|
+
-- head the commit the supervisor process is RUNNING (40-char hex)
|
|
10
|
+
-- disk_head the commit on disk, when the checkout was refreshed in place
|
|
11
|
+
-- past what is running (task 1004406) — null otherwise
|
|
12
|
+
-- main_head the newest main the runner has seen, so the card can say
|
|
13
|
+
-- "behind main" without the server reaching GitHub
|
|
14
|
+
-- claude_account a LABEL for the Claude login its workers use (an email, or
|
|
15
|
+
-- "API key" / "Claude token" when one is set). Never a token or
|
|
16
|
+
-- a key: the route caps it at 200 characters and the runner only
|
|
17
|
+
-- ever sends the label.
|
|
18
|
+
--
|
|
19
|
+
-- Nullable, no defaults: a runner that predates this reports nothing, and the
|
|
20
|
+
-- card says nothing rather than guessing. Additive and idempotent.
|
|
21
|
+
|
|
22
|
+
BEGIN;
|
|
23
|
+
|
|
24
|
+
ALTER TABLE autonomy_runner_heartbeat
|
|
25
|
+
ADD COLUMN IF NOT EXISTS head text,
|
|
26
|
+
ADD COLUMN IF NOT EXISTS disk_head text,
|
|
27
|
+
ADD COLUMN IF NOT EXISTS main_head text,
|
|
28
|
+
ADD COLUMN IF NOT EXISTS claude_account text;
|
|
29
|
+
|
|
30
|
+
COMMIT;
|
|
@@ -52,6 +52,11 @@ function runsLimit(raw) {
|
|
|
52
52
|
return Math.min(RUNS_MAX_LIMIT, Math.max(1, n));
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// commitOrNull — a full lowercase commit sha, or null (task 1004407).
|
|
56
|
+
function commitOrNull(v) {
|
|
57
|
+
return typeof v === 'string' && /^[0-9a-f]{40}$/.test(v) ? v : null;
|
|
58
|
+
}
|
|
59
|
+
|
|
55
60
|
module.exports = function buildAutonomyRouter() {
|
|
56
61
|
const router = express.Router();
|
|
57
62
|
|
|
@@ -219,6 +224,11 @@ module.exports = function buildAutonomyRouter() {
|
|
|
219
224
|
last_event: { type: 'string', maxLength: 64 },
|
|
220
225
|
working_task_id: { type: 'number' },
|
|
221
226
|
working_goal_id: { type: 'number' },
|
|
227
|
+
// What the runner card says about the runner (task 1004407).
|
|
228
|
+
head: { type: 'string', maxLength: 40 },
|
|
229
|
+
disk_head: { type: 'string', maxLength: 40 },
|
|
230
|
+
main_head: { type: 'string', maxLength: 40 },
|
|
231
|
+
claude_account: { type: 'string', maxLength: 200 },
|
|
222
232
|
})) return;
|
|
223
233
|
const b = req.body || {};
|
|
224
234
|
try {
|
|
@@ -234,6 +244,12 @@ module.exports = function buildAutonomyRouter() {
|
|
|
234
244
|
lastEvent: b.last_event || null,
|
|
235
245
|
workingTaskId: Number.isInteger(b.working_task_id) ? b.working_task_id : null,
|
|
236
246
|
workingGoalId: Number.isInteger(b.working_goal_id) ? b.working_goal_id : null,
|
|
247
|
+
// A commit is stored only if it IS one: anything that is not 40 hex
|
|
248
|
+
// characters is dropped to null rather than rendered as a version.
|
|
249
|
+
head: commitOrNull(b.head),
|
|
250
|
+
diskHead: commitOrNull(b.disk_head),
|
|
251
|
+
mainHead: commitOrNull(b.main_head),
|
|
252
|
+
claudeAccount: typeof b.claude_account === 'string' && b.claude_account.trim() ? b.claude_account.trim() : null,
|
|
237
253
|
});
|
|
238
254
|
res.json({ ok: true, heartbeat: row });
|
|
239
255
|
} catch (err) {
|
|
@@ -19,6 +19,11 @@
|
|
|
19
19
|
// exactly what it is for, and crying dead every time it works is worse than
|
|
20
20
|
// saying nothing.
|
|
21
21
|
// · dead — quiet longer than any legitimate explanation.
|
|
22
|
+
// · restarting — its last word was "exiting to pick up new code" (upgrade_exit,
|
|
23
|
+
// task 1004406), and that was recently. The wrapper fetches, resets and
|
|
24
|
+
// relaunches, which takes a minute or two, and the owner ruled that a restart
|
|
25
|
+
// must read as a restart, never as a death (task 1004407). Past RESTART_S the
|
|
26
|
+
// relaunch has failed and it is dead again — with a detail that says so.
|
|
22
27
|
|
|
23
28
|
// Reasoned from the runner's own numbers, not picked. The loop beats at the top of
|
|
24
29
|
// every pass and again on each poll inside a wait (every 60s), so a healthy runner
|
|
@@ -28,6 +33,43 @@ const ALIVE_S = 15 * 60;
|
|
|
28
33
|
// worker holds the loop for its whole run. Two hours is that ceiling plus room for
|
|
29
34
|
// the ship, the grade and the verify that follow it.
|
|
30
35
|
const LATE_S = 2 * 60 * 60;
|
|
36
|
+
// A relaunch is fetch + reset + boot + the first beat. Ten minutes is several
|
|
37
|
+
// times that, so a slow fetch is not called a death, and short enough that a
|
|
38
|
+
// relaunch which never happened is noticed the same evening.
|
|
39
|
+
const RESTART_S = 10 * 60;
|
|
40
|
+
|
|
41
|
+
const SHA_RE = /^[0-9a-f]{40}$/;
|
|
42
|
+
const sha = (v) => (typeof v === 'string' && SHA_RE.test(v) ? v : null);
|
|
43
|
+
|
|
44
|
+
// describeCode — what the card says about the code a runner is on (task 1004407).
|
|
45
|
+
// `behind` is true only when BOTH sides are known and differ: the runner reports
|
|
46
|
+
// the newest main it has seen, and an unknown is never drawn as "out of date".
|
|
47
|
+
// A checkout refreshed in place counts by what is on disk, because that is what
|
|
48
|
+
// every worker and child process runs (task 1004406).
|
|
49
|
+
function describeCode(hb) {
|
|
50
|
+
const head = sha(hb && hb.head);
|
|
51
|
+
if (!head) return null;
|
|
52
|
+
const disk = sha(hb.disk_head);
|
|
53
|
+
const main = sha(hb.main_head);
|
|
54
|
+
const current = disk || head;
|
|
55
|
+
return {
|
|
56
|
+
head, short: head.slice(0, 8),
|
|
57
|
+
diskShort: disk && disk !== head ? disk.slice(0, 8) : null,
|
|
58
|
+
mainShort: main ? main.slice(0, 8) : null,
|
|
59
|
+
behind: main ? main !== current : null,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// What the runner was last doing, in words. Keyed on the run-log event names the
|
|
64
|
+
// runner sends as last_event; an unknown event says nothing rather than guessing.
|
|
65
|
+
const ACTIVITY = {
|
|
66
|
+
boot: 'just started',
|
|
67
|
+
waiting: 'waiting for work',
|
|
68
|
+
code_refreshed: 'updated its code in place',
|
|
69
|
+
nothing_claimable: 'waiting — nothing to claim',
|
|
70
|
+
fenced: 'held by the fence',
|
|
71
|
+
hold: 'pausing',
|
|
72
|
+
};
|
|
31
73
|
|
|
32
74
|
function ageS(lastSeenAt, nowMs) {
|
|
33
75
|
const t = Date.parse(lastSeenAt || '');
|
|
@@ -68,19 +110,38 @@ function describeRunner(heartbeat, { nowMs = Date.now(), aliveS = ALIVE_S, lateS
|
|
|
68
110
|
const mode = heartbeat.mode === 'probing' ? 'probing' : (heartbeat.mode === 'full' ? 'full' : null);
|
|
69
111
|
const working = heartbeat.working_task_id ? String(heartbeat.working_task_id) : null;
|
|
70
112
|
const where = host ? ` on ${host}` : '';
|
|
113
|
+
const ident = {
|
|
114
|
+
host, mode, workingTaskId: working,
|
|
115
|
+
account: typeof heartbeat.claude_account === 'string' && heartbeat.claude_account ? heartbeat.claude_account : null,
|
|
116
|
+
code: describeCode(heartbeat),
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
if (heartbeat.last_event === 'upgrade_exit') {
|
|
120
|
+
if (age <= RESTART_S) {
|
|
121
|
+
return {
|
|
122
|
+
state: 'restarting', ok: true, ageS: age, age: humanAge(age), ...ident,
|
|
123
|
+
detail: `restarting${where} to pick up new code`,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
state: 'dead', ok: false, ageS: age, age: humanAge(age), ...ident,
|
|
128
|
+
detail: `exited${where} ${humanAge(age)} ago to restart on new code and has not come back`,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
71
131
|
|
|
72
132
|
if (age <= aliveS) {
|
|
133
|
+
const doing = ACTIVITY[heartbeat.last_event];
|
|
73
134
|
return {
|
|
74
|
-
state: 'alive', ok: true, ageS: age, age: humanAge(age),
|
|
135
|
+
state: 'alive', ok: true, ageS: age, age: humanAge(age), ...ident,
|
|
75
136
|
detail: working
|
|
76
137
|
? `working task ${working}${where}`
|
|
77
|
-
: `${mode === 'probing' ? 'probing after failures' : 'running'}${where}`,
|
|
138
|
+
: `${mode === 'probing' ? 'probing after failures' : (doing || 'running')}${where}`,
|
|
78
139
|
};
|
|
79
140
|
}
|
|
80
141
|
|
|
81
142
|
if (age <= lateS) {
|
|
82
143
|
return {
|
|
83
|
-
state: 'late', ok: true, ageS: age, age: humanAge(age),
|
|
144
|
+
state: 'late', ok: true, ageS: age, age: humanAge(age), ...ident,
|
|
84
145
|
// Deliberately NOT an alarm. This is what a runner doing its job looks like.
|
|
85
146
|
detail: working
|
|
86
147
|
? `quiet for ${humanAge(age)} — expected while task ${working} builds`
|
|
@@ -89,9 +150,9 @@ function describeRunner(heartbeat, { nowMs = Date.now(), aliveS = ALIVE_S, lateS
|
|
|
89
150
|
}
|
|
90
151
|
|
|
91
152
|
return {
|
|
92
|
-
state: 'dead', ok: false, ageS: age, age: humanAge(age),
|
|
153
|
+
state: 'dead', ok: false, ageS: age, age: humanAge(age), ...ident,
|
|
93
154
|
detail: `no check-in for ${humanAge(age)}${where} — longer than any worker takes, so the runner is not running`,
|
|
94
155
|
};
|
|
95
156
|
}
|
|
96
157
|
|
|
97
|
-
module.exports = { ALIVE_S, LATE_S, ageS, humanAge, describeRunner };
|
|
158
|
+
module.exports = { ALIVE_S, LATE_S, RESTART_S, ageS, humanAge, describeRunner };
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
|
|
92
92
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
93
93
|
|
|
94
|
-
<script src="/builders/shell.js?v=2026-
|
|
94
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
95
95
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
96
96
|
<script src="/builders/palette.js" defer></script>
|
|
97
97
|
<script src="/builders/atlas.js?v=2026-09-02-index" defer></script>
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
|
|
51
51
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
52
52
|
chrome exists before the page script runs. -->
|
|
53
|
-
<script src="/builders/shell.js?v=2026-
|
|
53
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
54
54
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
55
55
|
<script src="/builders/palette.js" defer></script>
|
|
56
56
|
<!-- shared frontend toolkit (window.OTB — rank labels, number formats) + the
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
|
|
61
61
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
62
62
|
|
|
63
|
-
<script src="/builders/shell.js?v=2026-
|
|
63
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
64
64
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
65
65
|
<script src="/builders/palette.js" defer></script>
|
|
66
66
|
<script src="/builders/dom-utils.js?v=2026-06-16-board"></script>
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
|
|
149
149
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
150
150
|
|
|
151
|
-
<script src="/builders/shell.js?v=2026-
|
|
151
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
152
152
|
<script src="/builders/palette.js" defer></script>
|
|
153
153
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
154
154
|
<script src="/builders/collab-lib.js?v=2026-09-24-archive"></script>
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
|
|
55
55
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
56
56
|
|
|
57
|
-
<script src="/builders/shell.js?v=2026-
|
|
57
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
58
58
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
59
59
|
<script src="/builders/palette.js" defer></script>
|
|
60
60
|
<script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
|
|
@@ -177,7 +177,7 @@
|
|
|
177
177
|
|
|
178
178
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
179
179
|
|
|
180
|
-
<script src="/builders/shell.js?v=2026-
|
|
180
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
181
181
|
<script src="/builders/palette.js" defer></script>
|
|
182
182
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
183
183
|
<script src="/builders/hall-kit.js"></script>
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
|
|
75
75
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
76
76
|
|
|
77
|
-
<script src="/builders/shell.js?v=2026-
|
|
77
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
78
78
|
<script src="/builders/palette.js" defer></script>
|
|
79
79
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
80
80
|
<script src="/builders/hall-kit.js"></script>
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
|
|
98
98
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
99
99
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
100
|
-
<script src="/builders/shell.js?v=2026-
|
|
100
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
101
101
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
102
102
|
<script src="/builders/palette.js" defer></script>
|
|
103
103
|
<script src="/builders/diagram-viewer.js?v=2026-06-14-diagviewer-1" defer></script>
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
|
|
99
99
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
100
100
|
|
|
101
|
-
<script src="/builders/shell.js?v=2026-
|
|
101
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
102
102
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
103
103
|
<script src="/builders/palette.js" defer></script>
|
|
104
104
|
<script src="/builders/md-reader.js?v=2026-09-02-mdreader-2"></script>
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
|
|
55
55
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
56
56
|
|
|
57
|
-
<script src="/builders/shell.js?v=2026-
|
|
57
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
58
58
|
<script src="/builders/palette.js" defer></script>
|
|
59
59
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
60
60
|
<script src="/builders/hall-kit.js"></script>
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
<div id="gate-modal-root"></div>
|
|
77
77
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
78
78
|
|
|
79
|
-
<script src="/builders/shell.js?v=2026-
|
|
79
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
80
80
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
81
81
|
<script src="/builders/palette.js" defer></script>
|
|
82
82
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
@@ -358,7 +358,7 @@
|
|
|
358
358
|
// exist here, resolved to inherited grey, and drew a dead runner in exactly the
|
|
359
359
|
// same colour as a healthy one. Named classes cannot fail that way silently —
|
|
360
360
|
// a missing class is visible in the stylesheet, a missing var is not.
|
|
361
|
-
const HEALTH_CLASS = { alive: 'fact-pill--ok', late: 'fact-pill--warn', dead: 'fact-pill--danger', never_seen: 'fact-pill--info' };
|
|
361
|
+
const HEALTH_CLASS = { alive: 'fact-pill--ok', late: 'fact-pill--warn', dead: 'fact-pill--danger', never_seen: 'fact-pill--info', restarting: 'fact-pill--info' };
|
|
362
362
|
|
|
363
363
|
async function loadRunners() {
|
|
364
364
|
const el = document.getElementById('fence-runner');
|
|
@@ -384,13 +384,29 @@
|
|
|
384
384
|
// NOT the word "running": the switch below already uses it for a different
|
|
385
385
|
// thing (allowed to work), and the two lines sit one above the other. A
|
|
386
386
|
// reader should never have to work out which sense is meant.
|
|
387
|
-
const label = { dead: 'Not checking in', late: 'Quiet', alive: 'Checking in' }[h.state] || 'Unknown';
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
</
|
|
387
|
+
const label = { dead: 'Not checking in', late: 'Quiet', alive: 'Checking in', restarting: 'Restarting' }[h.state] || 'Unknown';
|
|
388
|
+
// Which PC, which Claude account, which code (task 1004407). Each line is
|
|
389
|
+
// drawn only when the runner reported it, so an older runner shows less
|
|
390
|
+
// rather than a guess.
|
|
391
|
+
const facts = [];
|
|
392
|
+
if (h.host) facts.push(`<span class="ov-fact">PC <strong>${escapeHtml(h.host)}</strong></span>`);
|
|
393
|
+
if (h.account) facts.push(`<span class="ov-fact">Claude account ${escapeHtml(h.account)}</span>`);
|
|
394
|
+
if (h.code) {
|
|
395
|
+
const c = h.code;
|
|
396
|
+
const status = c.behind === true ? ` — behind main (${escapeHtml(c.mainShort)})`
|
|
397
|
+
: c.behind === false ? ' — up to date' : '';
|
|
398
|
+
const disk = c.diskShort ? `, updated in place to ${escapeHtml(c.diskShort)}` : '';
|
|
399
|
+
facts.push(`<span class="ov-fact">code <code>${escapeHtml(c.short)}</code>${disk}${status}</span>`);
|
|
400
|
+
}
|
|
401
|
+
return `<div class="ov-foot">
|
|
402
|
+
<p style="display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;margin:0;">
|
|
403
|
+
<span class="fact-pill ${cls}">${escapeHtml(label)}</span>
|
|
404
|
+
<span>${escapeHtml(h.detail || '')}</span>
|
|
405
|
+
${r.working_goal_id ? `<span class="ov-fact">from goal ${escapeHtml(String(r.working_goal_id))}</span>` : ''}
|
|
406
|
+
<span class="ov-fact">last check-in ${escapeHtml(h.age || 'unknown')} ago</span>
|
|
407
|
+
</p>
|
|
408
|
+
${facts.length ? `<p style="display:flex;gap:14px;flex-wrap:wrap;margin:4px 0 0;">${facts.join('')}</p>` : ''}
|
|
409
|
+
</div>`;
|
|
394
410
|
}).join('');
|
|
395
411
|
}
|
|
396
412
|
|
|
@@ -137,7 +137,7 @@
|
|
|
137
137
|
</div>
|
|
138
138
|
</div>
|
|
139
139
|
|
|
140
|
-
<script src="/builders/shell.js?v=2026-
|
|
140
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
141
141
|
<script src="/builders/palette.js" defer></script>
|
|
142
142
|
|
|
143
143
|
<script src="/builders/idea-objects.js?v=2026-09-27-split"></script>
|
|
@@ -230,7 +230,7 @@
|
|
|
230
230
|
|
|
231
231
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
232
232
|
|
|
233
|
-
<script src="/builders/shell.js?v=2026-
|
|
233
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
234
234
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
235
235
|
<script src="/builders/palette.js" defer></script>
|
|
236
236
|
<script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
|
|
@@ -134,7 +134,7 @@
|
|
|
134
134
|
|
|
135
135
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
136
136
|
|
|
137
|
-
<script src="/builders/shell.js?v=2026-
|
|
137
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
138
138
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
139
139
|
<script src="/builders/palette.js" defer></script>
|
|
140
140
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
|
|
53
53
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
54
54
|
|
|
55
|
-
<script src="/builders/shell.js?v=2026-
|
|
55
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
56
56
|
<script src="/builders/palette.js" defer></script>
|
|
57
57
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib" defer></script>
|
|
58
58
|
<!-- task-detail.js still owns the #/idea/N and #/blocker/N overlay, so a ref
|
|
@@ -225,7 +225,7 @@
|
|
|
225
225
|
|
|
226
226
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
227
227
|
|
|
228
|
-
<script src="/builders/shell.js?v=2026-
|
|
228
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
229
229
|
<script src="/builders/palette.js" defer></script>
|
|
230
230
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
231
231
|
<!-- task 1003069 (R08) — the quick-filing form's routed result links a task
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
<script src="/builders/brand-apply.js?v=2026-06-27-r75" defer></script>
|
|
107
107
|
<!-- The app shell (sidebar + top bar + view router). Dependency-free; loaded
|
|
108
108
|
BEFORE builders.js so the chrome ids exist when events are wired. -->
|
|
109
|
-
<script src="/builders/shell.js?v=2026-
|
|
109
|
+
<script src="/builders/shell.js?v=2026-10-01-look" defer></script>
|
|
110
110
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
111
111
|
<script src="/builders/palette.js" defer></script>
|
|
112
112
|
<!-- task 2166: the shared component kit (window.OTBKit) — pager/ledger/rail.
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
|
|
102
102
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
103
103
|
|
|
104
|
-
<script src="/builders/shell.js?v=2026-
|
|
104
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
105
105
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
106
106
|
<script src="/builders/palette.js" defer></script>
|
|
107
107
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
|
|
116
116
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
117
117
|
|
|
118
|
-
<script src="/builders/shell.js?v=2026-
|
|
118
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
119
119
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
120
120
|
<script src="/builders/palette.js" defer></script>
|
|
121
121
|
<!-- the shared markdown reader (window.OTBMdReader) — primer.js renders through
|
|
@@ -187,7 +187,7 @@
|
|
|
187
187
|
|
|
188
188
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
189
189
|
chrome exists before the page script runs. -->
|
|
190
|
-
<script src="/builders/shell.js?v=2026-
|
|
190
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
191
191
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
192
192
|
<script src="/builders/palette.js" defer></script>
|
|
193
193
|
<!-- shared frontend toolkit (window.OTB) — loaded before profile.js, which pulls from it. -->
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
|
|
59
59
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
60
60
|
|
|
61
|
-
<script src="/builders/shell.js?v=2026-
|
|
61
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
62
62
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
63
63
|
<script src="/builders/palette.js" defer></script>
|
|
64
64
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
|
|
78
78
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
79
79
|
chrome exists before the page script runs. -->
|
|
80
|
-
<script src="/builders/shell.js?v=2026-
|
|
80
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
81
81
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
82
82
|
<script src="/builders/palette.js" defer></script>
|
|
83
83
|
<!-- shared frontend toolkit (window.OTB) — loaded before ranks.js, which pulls from it. -->
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
43
43
|
|
|
44
|
-
<script src="/builders/shell.js?v=2026-
|
|
44
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
45
45
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
46
46
|
<script src="/builders/palette.js" defer></script>
|
|
47
47
|
<script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
|
|
51
51
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
52
52
|
chrome exists before the page script runs. -->
|
|
53
|
-
<script src="/builders/shell.js?v=2026-
|
|
53
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
54
54
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
55
55
|
<script src="/builders/palette.js" defer></script>
|
|
56
56
|
<!-- shared frontend toolkit (window.OTB — rank labels, number formats) + the
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
|
|
100
100
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
101
101
|
chrome exists before the page script runs. -->
|
|
102
|
-
<script src="/builders/shell.js?v=2026-
|
|
102
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
103
103
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
104
104
|
<script src="/builders/palette.js" defer></script>
|
|
105
105
|
<!-- shared frontend toolkit (window.OTB) — loaded BEFORE sessions.js so it can pull from it. -->
|
|
@@ -400,7 +400,7 @@
|
|
|
400
400
|
|
|
401
401
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
402
402
|
chrome exists before the page scripts run. -->
|
|
403
|
-
<script src="/builders/shell.js?v=2026-
|
|
403
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
404
404
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
405
405
|
<script src="/builders/palette.js" defer></script>
|
|
406
406
|
<!-- shared frontend toolkit (window.OTB) — loaded before consuming IIFEs. -->
|
|
@@ -442,7 +442,11 @@
|
|
|
442
442
|
|
|
443
443
|
el.innerHTML =
|
|
444
444
|
'<div class="app-sidebar__brand"><a class="brand-mark" href="/" id="brand-mark">' +
|
|
445
|
-
'
|
|
445
|
+
// A project's logo (ADR 0358, task 1004421) takes the dot's place; the
|
|
446
|
+
// platform validated it as an https image address before it was pushed.
|
|
447
|
+
(brand.theme && brand.theme.logo
|
|
448
|
+
? '<img class="brand-mark__logo" src="' + esc(brand.theme.logo) + '" alt="" width="24" height="24" decoding="async">'
|
|
449
|
+
: '<span class="brand-mark__dot" aria-hidden="true"></span>') +
|
|
446
450
|
'<span class="brand-mark__name" id="brand-name"></span></a></div>' +
|
|
447
451
|
'<nav class="app-sidebar__nav" aria-label="Primary">' + groups + '</nav>' +
|
|
448
452
|
'<div class="app-sidebar__foot">' +
|
|
@@ -176,7 +176,7 @@
|
|
|
176
176
|
|
|
177
177
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
178
178
|
|
|
179
|
-
<script src="/builders/shell.js?v=2026-
|
|
179
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
180
180
|
<script src="/builders/palette.js" defer></script>
|
|
181
181
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
182
182
|
<!-- The kit, for its ONE renderer: the full-size ship-time visual figure
|
|
@@ -561,6 +561,14 @@ main:focus { outline: none; }
|
|
|
561
561
|
background: none;
|
|
562
562
|
flex-shrink: 0;
|
|
563
563
|
}
|
|
564
|
+
/* A project's logo in the dot's place (ADR 0358): the image is the founder's, so
|
|
565
|
+
it gets a soft square, never a recolour. */
|
|
566
|
+
.brand-mark__logo {
|
|
567
|
+
width: 24px; height: 24px;
|
|
568
|
+
border-radius: 6px;
|
|
569
|
+
object-fit: contain;
|
|
570
|
+
flex-shrink: 0;
|
|
571
|
+
}
|
|
564
572
|
.brand-mark__name {
|
|
565
573
|
display: none;
|
|
566
574
|
white-space: nowrap;
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
|
|
58
58
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
59
59
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
60
|
-
<script src="/builders/shell.js?v=2026-
|
|
60
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
61
61
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
62
62
|
<script src="/builders/palette.js" defer></script>
|
|
63
63
|
<!-- shared frontend toolkit (window.OTB) — loaded FIRST so task.js can pull from it. -->
|
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
</div>
|
|
139
139
|
</div>
|
|
140
140
|
|
|
141
|
-
<script src="/builders/shell.js?v=2026-
|
|
141
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
142
142
|
<script src="/builders/palette.js" defer></script>
|
|
143
143
|
|
|
144
144
|
<script src="/builders/idea-objects.js?v=2026-09-27-split"></script>
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
|
|
141
141
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
142
142
|
|
|
143
|
-
<script src="/builders/shell.js?v=2026-
|
|
143
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
144
144
|
<script src="/builders/palette.js" defer></script>
|
|
145
145
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
146
146
|
<script src="/builders/brand-holes.js?v=2026-09-29-aqfix"></script>
|