@bongos/core 1.24.39 → 1.24.41
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 +87 -102
- package/docs/canonical-permissions.md +2 -1
- package/docs/copy-inventory.md +122 -127
- package/docs/copy-registry.json +179 -244
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +11 -69
- package/docs/page-readings.json +434 -822
- package/modules/hall-ui/public/ai-studio.html +2 -2
- package/modules/hall-ui/public/atlas.html +2 -2
- package/modules/hall-ui/public/blockers.html +2 -2
- package/modules/hall-ui/public/board-room.html +2 -2
- package/modules/hall-ui/public/city.html +2 -2
- package/modules/hall-ui/public/collab.html +2 -2
- package/modules/hall-ui/public/commands.html +2 -2
- package/modules/hall-ui/public/copy-desk.html +2 -2
- package/modules/hall-ui/public/craft-mode.js +1 -1
- package/modules/hall-ui/public/deploy.html +2 -2
- package/modules/hall-ui/public/gate.js +12 -5
- package/modules/hall-ui/public/goal-map.html +2 -2
- package/modules/hall-ui/public/goals.html +2 -2
- package/modules/hall-ui/public/government.html +2 -2
- package/modules/hall-ui/public/idea.html +2 -2
- package/modules/hall-ui/public/ideas.html +2 -2
- package/modules/hall-ui/public/index.html +2 -2
- package/modules/hall-ui/public/market-watch-item.html +2 -2
- package/modules/hall-ui/public/market-watch-rules.html +2 -2
- package/modules/hall-ui/public/market-watch.html +2 -2
- package/modules/hall-ui/public/modules.html +2 -2
- package/modules/hall-ui/public/money.html +2 -2
- package/modules/hall-ui/public/my-agents.html +2 -2
- package/modules/hall-ui/public/palette.js +10 -0
- package/modules/hall-ui/public/primer.html +2 -2
- package/modules/hall-ui/public/profile.html +2 -2
- package/modules/hall-ui/public/project-settings.html +2 -2
- package/modules/hall-ui/public/roadmap.html +2 -2
- package/modules/hall-ui/public/roster.css +10 -0
- package/modules/hall-ui/public/roster.html +6 -3
- package/modules/hall-ui/public/roster.js +168 -9
- package/modules/hall-ui/public/sessions.js +21 -10
- package/modules/hall-ui/public/settings.html +2 -2
- package/modules/hall-ui/public/shell.js +1 -2
- package/modules/hall-ui/public/street.html +2 -2
- package/modules/hall-ui/public/studio.html +2 -2
- package/modules/hall-ui/public/task.html +2 -2
- package/modules/hall-ui/public/thinking.html +2 -2
- package/modules/hall-ui/public/tweak-editor.html +2 -2
- package/modules/hall-ui/public/watch.css +0 -6
- package/modules/hall-ui/public/watch.html +121 -15
- package/modules/hall-ui/public/watch.js +28 -166
- package/modules/hall-ui/public/watch.states.json +49 -1
- package/modules/hall-ui/public/work.html +2 -2
- package/modules/hall-ui/records/builders-roster.md +2 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/bongos/gen-hall-sitemap.js +2 -0
- package/src/bongos/routes/me.js +3 -1
- package/src/bongos/serve-internal.js +15 -22
- package/src/module-api.js +1 -1
- package/tests/autonomy_builder_scope.mjs +2 -1
- package/tests/hall_audit.mjs +1 -1
- package/tests/hall_loading.mjs +13 -2
- package/tests/hall_nav.mjs +5 -2
- package/tests/hall_oversight_tabs.mjs +162 -0
- package/tests/hall_oversight_world.mjs +23 -9
- package/tests/hall_page_gate_map.mjs +7 -2
- package/tests/hall_roster_world.mjs +14 -9
- package/tests/hall_sessions_ledger.mjs +2 -1
- package/tests/nav_permission_atoms.mjs +3 -2
- package/tests/rank_tier_single_source.mjs +3 -1
- package/tests/roster_government_columns.mjs +369 -0
- package/tests/sessions_kit_pager.mjs +8 -4
- package/tests/watch_sealed_floor.mjs +6 -4
- package/modules/hall-ui/public/gate.html +0 -76
- package/modules/hall-ui/public/gate.states.json +0 -60
- package/modules/hall-ui/public/sessions.html +0 -128
- package/modules/hall-ui/public/sessions.states.json +0 -59
- package/tests/watch_roster_self_row.mjs +0 -296
|
@@ -136,6 +136,125 @@
|
|
|
136
136
|
var sortBy = '';
|
|
137
137
|
var role = '';
|
|
138
138
|
|
|
139
|
+
// ---- the government columns (task 1005048, owner decision on idea 1001313) --
|
|
140
|
+
// Watch kept a second list of the same people; it is this page now. A viewer
|
|
141
|
+
// the server lets read GET /builders/roster (the builder.roster.read atom) also
|
|
142
|
+
// sees each builder's active claims, the stale-claim flag and standing, and
|
|
143
|
+
// gets the rank and offboard controls that list carried. For anyone else that
|
|
144
|
+
// read is refused and the page draws the public list unchanged: the wall is
|
|
145
|
+
// the route, never this file.
|
|
146
|
+
var gov = null; // builder id → their GET /builders/roster row, once it answers
|
|
147
|
+
var viewerId = null;
|
|
148
|
+
var claims = '';
|
|
149
|
+
var RANK_OPTIONS = ['xenos', 'thetes', 'metic', 'archon'];
|
|
150
|
+
var client = window.BongosClient
|
|
151
|
+
? window.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false })
|
|
152
|
+
: null;
|
|
153
|
+
var toast = function (m) { if (OTB && OTB.toast) OTB.toast(m); };
|
|
154
|
+
// By value: /me and the roster can disagree on string vs number ids (#348).
|
|
155
|
+
function isViewer(b) { return viewerId != null && b && b.id != null && String(b.id) === String(viewerId); }
|
|
156
|
+
function govOf(b) { return gov && b.id != null ? gov[String(b.id)] || null : null; }
|
|
157
|
+
// Resolves to the government rows, or null for every refusal and failure.
|
|
158
|
+
// The roster read is asked only of a viewer whose /me map says they hold
|
|
159
|
+
// builder.roster.read, so a stranger or a builder without the seat costs this
|
|
160
|
+
// public page no extra round trip. /me itself is the shell's read, shared on
|
|
161
|
+
// window.__hallReads (the gov-feeds.js precedent); asked here only without it.
|
|
162
|
+
function readMe() {
|
|
163
|
+
var shared = window.__hallReads && window.__hallReads['/me'];
|
|
164
|
+
if (shared) return Promise.resolve(shared);
|
|
165
|
+
return client.request('GET', '/api/bongos/me').then(function (res) { return res && res.ok ? res.data : null; });
|
|
166
|
+
}
|
|
167
|
+
function loadGov() {
|
|
168
|
+
if (!client) return Promise.resolve(null);
|
|
169
|
+
return readMe().then(function (me) {
|
|
170
|
+
var perms = me && me.permissions;
|
|
171
|
+
if (!perms || perms['builder.roster.read'] !== true) return null;
|
|
172
|
+
var who = me.builder || me;
|
|
173
|
+
// No id from /me marks no row as yours, rather than the wrong one.
|
|
174
|
+
viewerId = who && who.id != null ? who.id : null;
|
|
175
|
+
return client.request('GET', '/api/bongos/builders/roster').then(function (roster) {
|
|
176
|
+
if (!roster || !roster.ok || !roster.data || !Array.isArray(roster.data.builders)) return null;
|
|
177
|
+
return roster.data.builders;
|
|
178
|
+
});
|
|
179
|
+
}).catch(function () { return null; });
|
|
180
|
+
}
|
|
181
|
+
function applyGov(rows) {
|
|
182
|
+
gov = {};
|
|
183
|
+
rows.forEach(function (r) { if (r.id != null) gov[String(r.id)] = r; });
|
|
184
|
+
// The rank a control just changed reads from the fresh row.
|
|
185
|
+
all.forEach(function (b) { var g = govOf(b); if (g && g.rank) b.rank = g.rank; });
|
|
186
|
+
}
|
|
187
|
+
function claimsHtml(g) {
|
|
188
|
+
var stale = g.stale_claim ? ' <span class="badge badge--sev-medium">stale</span>' : '';
|
|
189
|
+
return '<td class="ledger__num' + (g.stale_claim ? ' roster-warn' : '') + '">' + esc(fmtInt(g.active_claim_count || 0)) + stale + '</td>';
|
|
190
|
+
}
|
|
191
|
+
function rankControlHtml(b) {
|
|
192
|
+
var opts = RANK_OPTIONS.slice();
|
|
193
|
+
if (b.rank && opts.indexOf(b.rank) === -1) opts.unshift(b.rank);
|
|
194
|
+
return '<select data-roster-rank data-id="' + esc(b.id) + '" data-current="' + esc(b.rank || '') + '" aria-label="Rank">'
|
|
195
|
+
+ opts.map(function (rk) {
|
|
196
|
+
return '<option value="' + esc(rk) + '"' + (rk === b.rank ? ' selected' : '') + '>' + esc(rankLabel(rk)) + '</option>';
|
|
197
|
+
}).join('') + '</select>';
|
|
198
|
+
}
|
|
199
|
+
// The viewer's own row offers nothing (task 1003720): both routes refuse a
|
|
200
|
+
// self-change outright, the guard that stops the last top-rank builder locking
|
|
201
|
+
// everyone out, so a live control there could only answer with a refusal.
|
|
202
|
+
function actionHtml(b, g) {
|
|
203
|
+
if (isViewer(b)) return '<span class="roster-self">That’s you</span>';
|
|
204
|
+
var inactive = String(g.status) === 'inactive';
|
|
205
|
+
return '<button type="button" class="btn-ghost' + (inactive ? '' : ' btn-ghost--danger') + '" data-roster-status="'
|
|
206
|
+
+ (inactive ? 'active' : 'inactive') + '" data-id="' + esc(b.id) + '">' + (inactive ? 'Reactivate' : 'Offboard') + '</button>';
|
|
207
|
+
}
|
|
208
|
+
function govFoot() {
|
|
209
|
+
var foot = document.getElementById('roster-gov-foot');
|
|
210
|
+
if (!foot) return;
|
|
211
|
+
if (!gov) { foot.hidden = true; return; }
|
|
212
|
+
foot.textContent = 'Because you hold a government seat you also see active claims and standing, and can change a builder’s rank. '
|
|
213
|
+
+ (all.some(isViewer)
|
|
214
|
+
? 'Your own row carries no controls. Nobody can change their own rank or standing — that guard is what stops the last '
|
|
215
|
+
+ rankLabel('archon') + ' locking everyone out — so another ' + rankLabel('archon') + ' does it.'
|
|
216
|
+
: '');
|
|
217
|
+
foot.hidden = false;
|
|
218
|
+
}
|
|
219
|
+
function patchBuilder(id, what, payload) {
|
|
220
|
+
return client.request('PATCH', '/api/bongos/builders/' + encodeURIComponent(id) + '/' + what, { body: payload })
|
|
221
|
+
.then(function (res) {
|
|
222
|
+
if (res && res.ok) return res.data;
|
|
223
|
+
var d = (res && res.data) || {};
|
|
224
|
+
var env = d.error && typeof d.error === 'object' ? d.error : null;
|
|
225
|
+
throw new Error((env && env.message) || d.message || (typeof d.error === 'string' ? d.error : '') || ('http ' + (res && res.status)));
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
function refreshGov() {
|
|
229
|
+
return loadGov().then(function (rows) { if (rows) applyGov(rows); else gov = null; makeRosterLedger(); rerender(); govFoot(); });
|
|
230
|
+
}
|
|
231
|
+
function wireGov() {
|
|
232
|
+
if (body['wired:gov']) return;
|
|
233
|
+
body['wired:gov'] = true;
|
|
234
|
+
// Delegated on the mount, so a page flip cannot strand a row without one.
|
|
235
|
+
body.addEventListener('change', function (e) {
|
|
236
|
+
var sel = e.target && e.target.closest ? e.target.closest('[data-roster-rank]') : null;
|
|
237
|
+
if (!sel) return;
|
|
238
|
+
var prev = sel.getAttribute('data-current');
|
|
239
|
+
var rank = sel.value;
|
|
240
|
+
if (rank === prev) return;
|
|
241
|
+
sel.disabled = true;
|
|
242
|
+
patchBuilder(sel.getAttribute('data-id'), 'rank', { rank: rank })
|
|
243
|
+
.then(function () { toast('Rank set to ' + rankLabel(rank) + '.'); return refreshGov(); })
|
|
244
|
+
.catch(function (err) { sel.value = prev; sel.disabled = false; toast('Could not change rank: ' + (err.message || err)); });
|
|
245
|
+
});
|
|
246
|
+
body.addEventListener('click', function (e) {
|
|
247
|
+
var btn = e.target && e.target.closest ? e.target.closest('[data-roster-status]') : null;
|
|
248
|
+
if (!btn) return;
|
|
249
|
+
var status = btn.getAttribute('data-roster-status');
|
|
250
|
+
var reason = status === 'inactive' ? (window.prompt('Reason for offboarding (optional):', '') || null) : null;
|
|
251
|
+
btn.disabled = true;
|
|
252
|
+
patchBuilder(btn.getAttribute('data-id'), 'status', { status: status, reason: reason })
|
|
253
|
+
.then(function () { toast(status === 'inactive' ? 'Builder offboarded.' : 'Builder reactivated.'); return refreshGov(); })
|
|
254
|
+
.catch(function (err) { btn.disabled = false; toast('Could not change standing: ' + (err.message || err)); });
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
139
258
|
// PLACE IS CANONICAL, not the row's position in the current sort.
|
|
140
259
|
// hall-render's leaderboardPlace() already defines a builder's place as
|
|
141
260
|
// credits-descending "independent of whatever column the table is currently
|
|
@@ -165,6 +284,12 @@
|
|
|
165
284
|
|
|
166
285
|
function matches(b) {
|
|
167
286
|
if (role && shownOf(b).indexOf(role) === -1) return false;
|
|
287
|
+
if (claims) {
|
|
288
|
+
var g = govOf(b);
|
|
289
|
+
if (!g) return false;
|
|
290
|
+
if (claims === 'stale' && !g.stale_claim) return false;
|
|
291
|
+
if (claims === 'holding' && !(Number(g.active_claim_count) > 0)) return false;
|
|
292
|
+
}
|
|
168
293
|
if (!query) return true;
|
|
169
294
|
var hay = ((b.github_login || '') + ' ' + (b.display_name || '')).toLowerCase();
|
|
170
295
|
return hay.indexOf(query) !== -1;
|
|
@@ -229,22 +354,31 @@
|
|
|
229
354
|
var tokens = hasTokens(b)
|
|
230
355
|
? esc(fmtInt(b.tokens_used))
|
|
231
356
|
: '<span class="lb-none" aria-label="No AI sessions">—</span>';
|
|
357
|
+
var g = govOf(b);
|
|
358
|
+
var govCells = g
|
|
359
|
+
? claimsHtml(g)
|
|
360
|
+
+ '<td><span class="badge badge--status">' + esc(g.status || '—') + '</span></td>'
|
|
361
|
+
+ '<td>' + actionHtml(b, g) + '</td>'
|
|
362
|
+
: (gov ? '<td class="ledger__num">—</td><td>—</td><td></td>' : '');
|
|
363
|
+
var rankCell = g && !isViewer(b) ? rankControlHtml(b) : rankBadgeHtml(b.rank);
|
|
232
364
|
return '<tr>'
|
|
233
365
|
+ '<td class="ledger__num ledger__place">' + esc(String(place || '—')) + idHtml(b) + '</td>'
|
|
234
366
|
+ '<td>' + whoHtml(b) + foundingHtml(b) + achRowHtml(b.achievements) + '</td>'
|
|
235
367
|
+ '<td class="ledger__roles">' + rolesHtml(b) + '</td>'
|
|
236
|
-
+ '<td class="ledger__rank">' +
|
|
368
|
+
+ '<td class="ledger__rank">' + rankCell + '</td>'
|
|
237
369
|
+ '<td class="ledger__num">' + esc(fmtInt(creditOf(b))) + '</td>'
|
|
238
370
|
+ '<td class="ledger__num">' + esc(fmtInt(b.karma)) + '</td>'
|
|
239
371
|
+ '<td class="ledger__num" title="' + esc(b.__eff || '') + '">' + tokens + '</td>'
|
|
372
|
+
+ govCells
|
|
240
373
|
+ '</tr>';
|
|
241
374
|
}
|
|
242
375
|
|
|
243
376
|
function columns() {
|
|
244
|
-
|
|
377
|
+
var cols = ['Place', 'Builder', 'Roles', 'Rank',
|
|
245
378
|
{ label: role ? currencyLabel() + ' as ' + CRAFT_WORD[role].toLowerCase() : currencyLabel(), class: 'ledger__num' },
|
|
246
379
|
{ label: 'Karma', class: 'ledger__num' },
|
|
247
380
|
{ label: 'Tokens', class: 'ledger__num' }];
|
|
381
|
+
return gov ? cols.concat([{ label: 'Active claims', class: 'ledger__num' }, 'Standing', 'Action']) : cols;
|
|
248
382
|
}
|
|
249
383
|
|
|
250
384
|
function makeRosterLedger() {
|
|
@@ -294,8 +428,21 @@
|
|
|
294
428
|
body.textContent = msg;
|
|
295
429
|
}
|
|
296
430
|
|
|
297
|
-
function mountRoster(rows) {
|
|
298
|
-
all =
|
|
431
|
+
function mountRoster(rows, govRows) {
|
|
432
|
+
all = Array.isArray(rows) ? rows : [];
|
|
433
|
+
if (govRows) {
|
|
434
|
+
// The government list is every builder; the public one only those who have
|
|
435
|
+
// earned. Nobody drops off the one list for having earned nothing yet.
|
|
436
|
+
var seen = {};
|
|
437
|
+
all.forEach(function (b) { if (b.id != null) seen[String(b.id)] = true; });
|
|
438
|
+
govRows.forEach(function (r) {
|
|
439
|
+
if (r.id == null || seen[String(r.id)]) return;
|
|
440
|
+
all.push({ id: r.id, github_login: r.github_login, display_name: r.display_name, avatar_url: r.avatar_url,
|
|
441
|
+
rank: r.rank, total_credits: r.total_credits, karma: r.karma, tokens_used: 0 });
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
all = stampPlaces(all);
|
|
445
|
+
if (govRows) applyGov(govRows);
|
|
299
446
|
if (!all.length) {
|
|
300
447
|
notice('Nobody has earned anything on this project yet. A builder joins the roster the '
|
|
301
448
|
+ 'first time they ship a claimed task.');
|
|
@@ -307,10 +454,19 @@
|
|
|
307
454
|
+ ' on this project, ranked by ' + currencyLabel().toLowerCase() + ' earned. This '
|
|
308
455
|
+ 'project’s own ledger — everyone who builds here is listed.';
|
|
309
456
|
}
|
|
457
|
+
// Watch's stale-claims tile links here as ?claims=stale.
|
|
458
|
+
var asked = '';
|
|
459
|
+
try { asked = new URLSearchParams(window.location.search).get('claims') || ''; } catch (e) { asked = ''; }
|
|
460
|
+
claims = gov && (asked === 'stale' || asked === 'holding') ? asked : '';
|
|
461
|
+
var claimsControl = gov ? [{ id: 'roster-claims', label: 'Claims', value: claims, options: [
|
|
462
|
+
{ value: '', label: 'anyone' },
|
|
463
|
+
{ value: 'holding', label: 'holding a claim' },
|
|
464
|
+
{ value: 'stale', label: 'stale claims' },
|
|
465
|
+
] }] : [];
|
|
310
466
|
kit.makeFilterBar({
|
|
311
467
|
mount: document.getElementById('roster-filters'),
|
|
312
468
|
search: { id: 'roster-q', label: 'Search', placeholder: 'name or login' },
|
|
313
|
-
primary: [
|
|
469
|
+
primary: claimsControl.concat([
|
|
314
470
|
{ id: 'roster-sort',
|
|
315
471
|
label: 'Sort',
|
|
316
472
|
options: [
|
|
@@ -319,10 +475,11 @@
|
|
|
319
475
|
{ value: 'karma', label: 'most karma' },
|
|
320
476
|
{ value: 'tokens', label: 'most tokens' },
|
|
321
477
|
] },
|
|
322
|
-
],
|
|
478
|
+
]),
|
|
323
479
|
onChange: function (vals) {
|
|
324
480
|
query = String(vals['roster-q'] || '').trim().toLowerCase();
|
|
325
481
|
sortBy = String(vals['roster-sort'] || '');
|
|
482
|
+
if (gov) claims = String(vals['roster-claims'] || '');
|
|
326
483
|
pager.page = 1;
|
|
327
484
|
rerender();
|
|
328
485
|
},
|
|
@@ -330,21 +487,23 @@
|
|
|
330
487
|
mountRoleChips();
|
|
331
488
|
makeRosterLedger();
|
|
332
489
|
rerender();
|
|
490
|
+
if (gov) { wireGov(); govFoot(); }
|
|
333
491
|
}
|
|
334
492
|
|
|
335
493
|
var foundingLoad = window.OTBFounding ? window.OTBFounding.load() : Promise.resolve([]);
|
|
336
494
|
// The PUBLIC list (task 1004437): the same rows the authed /leaderboard
|
|
337
495
|
// carries plus each builder's roles, and the one the status page reads, so
|
|
338
496
|
// the hall and the public page cannot rank a role differently.
|
|
497
|
+
var govLoad = loadGov();
|
|
339
498
|
fetch('/api/bongos/public/leaderboard', { headers: { Accept: 'application/json' } })
|
|
340
499
|
.then(function (r) {
|
|
341
500
|
if (!r.ok) throw new Error('http ' + r.status);
|
|
342
501
|
return r.json();
|
|
343
502
|
})
|
|
344
503
|
.then(function (data) {
|
|
345
|
-
return foundingLoad.then(function (
|
|
346
|
-
marks = window.OTBFounding ? window.OTBFounding.byId(
|
|
347
|
-
mountRoster(data && data.leaderboard);
|
|
504
|
+
return Promise.all([foundingLoad, govLoad]).then(function (got) {
|
|
505
|
+
marks = window.OTBFounding ? window.OTBFounding.byId(got[0]) : {};
|
|
506
|
+
mountRoster(data && data.leaderboard, got[1]);
|
|
348
507
|
});
|
|
349
508
|
})
|
|
350
509
|
.catch(function () { notice('Couldn’t load the roster right now.'); });
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
// public-builders/sessions.js — the Session Ledger
|
|
1
|
+
// public-builders/sessions.js — the Session Ledger: Watch's Sessions tab
|
|
2
|
+
// (/watch#sessions; the /sessions page it was folded in task 1005049).
|
|
2
3
|
//
|
|
3
4
|
// Metic+ monitoring view of the BFG session corpus (ADR 0027 / cluster 6D;
|
|
4
5
|
// the floor moved archon→metic in ADR 0157). Read-only. Fetches /api/bongos/me
|
|
@@ -285,12 +286,12 @@
|
|
|
285
286
|
// A failed access check says so in the same panel, under its own heading.
|
|
286
287
|
function showSealed(message, heading) {
|
|
287
288
|
const floor = rankLabel('metic');
|
|
288
|
-
const gate = $('#
|
|
289
|
-
$('#
|
|
290
|
-
if (message) $('#
|
|
289
|
+
const gate = $('#sess-sealed');
|
|
290
|
+
$('#sess-sealed-h').textContent = heading || `${floor} and up`;
|
|
291
|
+
if (message) $('#sess-sealed-body').textContent = message;
|
|
291
292
|
gate.hidden = false;
|
|
292
293
|
$('#stats-scroll').hidden = true;
|
|
293
|
-
$('#
|
|
294
|
+
$('#sess-ledger-scroll').hidden = true;
|
|
294
295
|
}
|
|
295
296
|
|
|
296
297
|
// ---- paging --------------------------------------------------------------
|
|
@@ -312,7 +313,7 @@
|
|
|
312
313
|
// the columns. Keeping it a separate sibling below the ledger is what holds it still.
|
|
313
314
|
// (This page used to carry a .ledger-table-wrap scrollport; task 1003470 removed it
|
|
314
315
|
// because the wrapper broke the shell's sticky th. The separate mount outlived it.)
|
|
315
|
-
// Table markup stays in
|
|
316
|
+
// Table markup stays in watch.html's Sessions tab; the PAGER is the kit's.
|
|
316
317
|
|
|
317
318
|
// Guarded the way every other kit consumer guards it (profile.js): the kit is a
|
|
318
319
|
// separate <script>, so a cache miss or a load-order slip must degrade to a named
|
|
@@ -428,9 +429,9 @@
|
|
|
428
429
|
const totals = data.totals || { total: data.total || sessions.length };
|
|
429
430
|
state.total = num(totals.total);
|
|
430
431
|
state.shown = sessions.length;
|
|
431
|
-
$('#
|
|
432
|
+
$('#sess-sealed').hidden = true;
|
|
432
433
|
$('#stats-scroll').hidden = false;
|
|
433
|
-
$('#
|
|
434
|
+
$('#sess-ledger-scroll').hidden = false;
|
|
434
435
|
renderStats(totals);
|
|
435
436
|
renderTable(sessions);
|
|
436
437
|
syncTypeOptions(data.session_types);
|
|
@@ -528,7 +529,12 @@
|
|
|
528
529
|
// 1. Who is viewing? (rank gate — the server enforces too.)
|
|
529
530
|
let builder;
|
|
530
531
|
try {
|
|
531
|
-
|
|
532
|
+
// The shell's /me read, shared on window.__hallReads, so opening the tab costs
|
|
533
|
+
// no second round trip; asked here only without it (task 1005049).
|
|
534
|
+
const shared = window.__hallReads && window.__hallReads['/me'];
|
|
535
|
+
const res = shared
|
|
536
|
+
? await shared.then((d) => (d ? { status: 200, ok: true, data: d } : { status: 0, ok: false }))
|
|
537
|
+
: await api.request('GET', `${API}/me`);
|
|
532
538
|
if (res.status === 401) {
|
|
533
539
|
window.location.assign(`${API}/auth/web/start`);
|
|
534
540
|
return;
|
|
@@ -569,5 +575,10 @@
|
|
|
569
575
|
await fetchPage();
|
|
570
576
|
}
|
|
571
577
|
|
|
572
|
-
|
|
578
|
+
// task 1005049: the ledger is Watch's Sessions tab, so it loads when that tab
|
|
579
|
+
// first opens (watch.js calls this), not on every Watch visit: each load is an
|
|
580
|
+
// audited read on the server. Once only.
|
|
581
|
+
let booted = false;
|
|
582
|
+
window.OTBWatchTabs = window.OTBWatchTabs || {};
|
|
583
|
+
window.OTBWatchTabs.sessions = () => { if (booted) return; booted = true; load(); };
|
|
573
584
|
})();
|
|
@@ -393,8 +393,8 @@
|
|
|
393
393
|
chrome exists before the page scripts run. -->
|
|
394
394
|
<script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
|
|
395
395
|
<script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
|
|
396
|
-
<script src="/builders/craft-mode.js?v=2026-11-05-
|
|
397
|
-
<script src="/builders/shell.js?v=2026-11-05-
|
|
396
|
+
<script src="/builders/craft-mode.js?v=2026-11-05-oversight"></script>
|
|
397
|
+
<script src="/builders/shell.js?v=2026-11-05-oversight"></script>
|
|
398
398
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
399
399
|
<script src="/builders/palette.js" defer></script>
|
|
400
400
|
<!-- shared component kit (window.OTBKit) — section rail (task 2166). -->
|
|
@@ -356,8 +356,7 @@
|
|
|
356
356
|
// Metic+ oversight could not honestly hold it. Everything below IS a rank
|
|
357
357
|
// surface, so this group's gate and audience are unchanged.
|
|
358
358
|
{ id: 'watch', label: 'Watch', icon: 'watch', href: '/watch', gate: 'government', position: true },
|
|
359
|
-
|
|
360
|
-
{ id: 'sessions', label: 'Sessions', icon: 'sessions', href: '/sessions', gate: 'government', position: true },
|
|
359
|
+
// task 1005049: Sessions and Autobongos (/gate) are tabs on Watch; both URLs redirect there.
|
|
361
360
|
// The project's own fundamentals (task 1003280, goal 1000072). Mirrors the
|
|
362
361
|
// server's requireGovernmentPage floor; cosmetic only, as every item here is.
|
|
363
362
|
{ id: 'project-settings', label: 'Project settings', icon: 'settings', href: '/project-settings', gate: 'government', position: true },
|
|
@@ -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-11-05-
|
|
234
|
-
<script src="/builders/shell.js?v=2026-11-05-
|
|
233
|
+
<script src="/builders/craft-mode.js?v=2026-11-05-oversight"></script>
|
|
234
|
+
<script src="/builders/shell.js?v=2026-11-05-oversight"></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>
|
|
@@ -224,8 +224,8 @@
|
|
|
224
224
|
|
|
225
225
|
<script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
|
|
226
226
|
<script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
|
|
227
|
-
<script src="/builders/craft-mode.js?v=2026-11-05-
|
|
228
|
-
<script src="/builders/shell.js?v=2026-11-05-
|
|
227
|
+
<script src="/builders/craft-mode.js?v=2026-11-05-oversight"></script>
|
|
228
|
+
<script src="/builders/shell.js?v=2026-11-05-oversight"></script>
|
|
229
229
|
<script src="/builders/palette.js" defer></script>
|
|
230
230
|
<!-- The kit, for its ONE renderer: the full-size ship-time visual figure
|
|
231
231
|
(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-11-05-
|
|
62
|
-
<script src="/builders/shell.js?v=2026-11-05-
|
|
61
|
+
<script src="/builders/craft-mode.js?v=2026-11-05-oversight"></script>
|
|
62
|
+
<script src="/builders/shell.js?v=2026-11-05-oversight"></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-11-05-
|
|
144
|
-
<script src="/builders/shell.js?v=2026-11-05-
|
|
143
|
+
<script src="/builders/craft-mode.js?v=2026-11-05-oversight"></script>
|
|
144
|
+
<script src="/builders/shell.js?v=2026-11-05-oversight"></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-11-05-
|
|
148
|
-
<script src="/builders/shell.js?v=2026-11-05-
|
|
147
|
+
<script src="/builders/craft-mode.js?v=2026-11-05-oversight"></script>
|
|
148
|
+
<script src="/builders/shell.js?v=2026-11-05-oversight"></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>
|
|
@@ -21,12 +21,6 @@
|
|
|
21
21
|
/* Row actions inside a ledger cell: ghost pills in a row. */
|
|
22
22
|
.watch-actions { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
|
|
23
23
|
|
|
24
|
-
/* The reader's own roster row (task 1003720). Nobody may change their own rank
|
|
25
|
-
or standing — the server refuses both outright — so that row states a fact
|
|
26
|
-
where the others carry a control, and a fact is written in the faint ink, not
|
|
27
|
-
painted as a disabled pill. The foot line under the table says why. */
|
|
28
|
-
.watch-self { color: var(--ink-faint); }
|
|
29
|
-
|
|
30
24
|
/* A sealed or failed section says so in place. Three states, three sentences:
|
|
31
25
|
"nothing here" is the kit's empty state, this is "you may not see this" and
|
|
32
26
|
"could not load". */
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>Watch — {{worldName}}</title>
|
|
7
|
-
<meta name="description" content="Archon oversight for {{worldName}} — access requests, security,
|
|
7
|
+
<meta name="description" content="Archon oversight for {{worldName}} — access requests, security, stale claims, reviews, repo health, sessions and the Autobongos runner.">
|
|
8
8
|
<!-- The hub's origin, for the link from a guild's head in the applications queue to
|
|
9
9
|
its page on the hub (task 1002304). Empty on a vanilla instance: no link. -->
|
|
10
10
|
<meta name="hub-origin" content="{{hubOrigin}}">
|
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
shared .stat-row/.stat-tile + hall-kit rules. -->
|
|
19
19
|
<link rel="stylesheet" href="/builders/oversight.css">
|
|
20
20
|
<link rel="stylesheet" href="/builders/watch.css">
|
|
21
|
+
<!-- task 1005049: the Sessions and Autobongos tabs bring their pages' own sheets. -->
|
|
22
|
+
<link rel="stylesheet" href="/builders/sessions.css">
|
|
23
|
+
<link rel="stylesheet" href="/builders/gate.css">
|
|
21
24
|
</head>
|
|
22
25
|
<body data-page="watch" data-page-title="Watch">
|
|
23
26
|
|
|
@@ -48,8 +51,8 @@
|
|
|
48
51
|
<div class="stat-row ov-summary" id="watch-attention" hidden>
|
|
49
52
|
<a class="stat-tile" href="#people"><span class="stat-tile__label">Access requests</span><span class="stat-tile__num" id="att-access">—</span><span class="stat-tile__sub">pending, on the People tab</span></a>
|
|
50
53
|
<a class="stat-tile" href="#security"><span class="stat-tile__label">Security reports</span><span class="stat-tile__num" id="att-security">—</span><span class="stat-tile__sub">open, on the Security tab</span></a>
|
|
51
|
-
<a class="stat-tile" href="
|
|
52
|
-
<a class="stat-tile" href="#
|
|
54
|
+
<a class="stat-tile" href="/roster?claims=stale"><span class="stat-tile__label">Stale claims</span><span class="stat-tile__num" id="att-stale">—</span><span class="stat-tile__sub">builders, on the Builders page</span></a>
|
|
55
|
+
<a class="stat-tile" href="#sessions"><span class="stat-tile__label">Sessions</span><span class="stat-tile__num" id="att-sessions">—</span><span class="stat-tile__sub">recorded, on the Sessions tab</span></a>
|
|
53
56
|
</div>
|
|
54
57
|
|
|
55
58
|
<!-- task 2173: the nine surfaces sit behind three OTBKit tabs, hash-synced
|
|
@@ -66,13 +69,6 @@
|
|
|
66
69
|
<div id="gate-body"><span class="kit-skel"></span></div>
|
|
67
70
|
</section>
|
|
68
71
|
|
|
69
|
-
<!-- 2. ROSTER -->
|
|
70
|
-
<section class="scroll" id="roster-scroll" aria-labelledby="roster-h" hidden>
|
|
71
|
-
<h2 id="roster-h" class="scroll__h">Roster</h2>
|
|
72
|
-
<p class="scroll__lede">Every builder in {{worldName}} — rank, {{currency}}, karma, and active claims.</p>
|
|
73
|
-
<div id="roster-body"><span class="kit-skel"></span></div>
|
|
74
|
-
</section>
|
|
75
|
-
|
|
76
72
|
<!-- 3. REVIEWS BY BUILDER -->
|
|
77
73
|
<section class="scroll" id="marks-scroll" aria-labelledby="marks-h" hidden>
|
|
78
74
|
<h2 id="marks-h" class="scroll__h">Reviews by builder</h2>
|
|
@@ -96,10 +92,10 @@
|
|
|
96
92
|
always fetched and used to throw away (task 1003469). -->
|
|
97
93
|
<section class="scroll" id="sessions-scroll" aria-labelledby="sessions-h" hidden>
|
|
98
94
|
<h2 id="sessions-h" class="scroll__h">Sessions</h2>
|
|
99
|
-
<p class="scroll__lede">A tally of recorded work sessions, and the latest five. The full record
|
|
95
|
+
<p class="scroll__lede">A tally of recorded work sessions, and the latest five. The full record is on the Sessions tab.</p>
|
|
100
96
|
<div id="sessions-stats" class="ov-figs"><span class="kit-skel"></span></div>
|
|
101
97
|
<div id="sessions-recent"></div>
|
|
102
|
-
<p class="ov-foot"><a href="
|
|
98
|
+
<p class="ov-foot"><a href="#sessions">View all sessions</a></p>
|
|
103
99
|
</section>
|
|
104
100
|
|
|
105
101
|
<!-- 7. REPO HEALTH -->
|
|
@@ -154,6 +150,113 @@
|
|
|
154
150
|
<div id="surveyor-body"></div>
|
|
155
151
|
</section>
|
|
156
152
|
</div>
|
|
153
|
+
<!-- ============ tab: Sessions (task 1005049: was the /sessions page) ============ -->
|
|
154
|
+
<div id="pane-sessions" role="tabpanel" aria-labelledby="tab-sessions" hidden>
|
|
155
|
+
<!-- Every section below starts hidden, so the tab says it is loading until one shows. -->
|
|
156
|
+
<p class="hall-loading hall-loading--page hall-loading--wait" role="status">Loading…</p>
|
|
157
|
+
<!-- Access notice — shown by sessions.js when the viewer is below the
|
|
158
|
+
floor. The heading and the line are written from the pack's rank
|
|
159
|
+
label (ADR 0157: metic and up), so the static text never renders. -->
|
|
160
|
+
<section class="scroll" id="sess-sealed" aria-labelledby="sess-sealed-h" hidden>
|
|
161
|
+
<h2 id="sess-sealed-h" class="scroll__h">Higher ranks only</h2>
|
|
162
|
+
<p class="scroll__lede" id="sess-sealed-body">The session log opens from a higher rank.</p>
|
|
163
|
+
</section>
|
|
164
|
+
|
|
165
|
+
<!-- Totals — the shell's glass strip at the top of the page (the way
|
|
166
|
+
Watch and Gate carry theirs), not a strip inside a panel. -->
|
|
167
|
+
<section class="ov-summary" id="stats-scroll" aria-labelledby="stats-h" hidden>
|
|
168
|
+
<h2 id="stats-h" class="sr-only">Totals</h2>
|
|
169
|
+
<div class="stat-row" id="stats-body"></div>
|
|
170
|
+
</section>
|
|
171
|
+
|
|
172
|
+
<!-- Every recorded session — the corpus table. No wrapper: the shell's
|
|
173
|
+
card strip scrolls a wide ledger on a phone, and the old wrapper
|
|
174
|
+
turned the sticky header into a scrollport overlay on desktop. -->
|
|
175
|
+
<section class="scroll" id="sess-ledger-scroll" aria-labelledby="sess-ledger-h" hidden>
|
|
176
|
+
<h2 id="sess-ledger-h" class="scroll__h">Recorded sessions</h2>
|
|
177
|
+
<!-- The count, and a failed load, sit here: the page head that used to carry
|
|
178
|
+
them is hidden (task 1004829). -->
|
|
179
|
+
<p class="scroll__lede" id="ledger-status" role="status" aria-live="polite">Loading sessions…</p>
|
|
180
|
+
<!-- task 1003975: the ledger's first filter. It LOOKS like the pager's
|
|
181
|
+
per-page control on purpose (one control grammar per page) but wears
|
|
182
|
+
its own class — the hall_audit guard reads `class="pager…"` as a
|
|
183
|
+
hand-rolled pager, and it is right to: borrowing that family is how a
|
|
184
|
+
second pager implementation starts. Its options are filled from the
|
|
185
|
+
server's vocabulary in sessions.js, never hardcoded here, where they
|
|
186
|
+
would drift from what the route accepts. -->
|
|
187
|
+
<div class="ledger-filters">
|
|
188
|
+
<label class="ledger-filter">
|
|
189
|
+
<span>Kind</span>
|
|
190
|
+
<select id="session-type-filter" aria-label="Filter sessions by kind"></select>
|
|
191
|
+
</label>
|
|
192
|
+
</div>
|
|
193
|
+
<table class="ledger" id="sessions-table">
|
|
194
|
+
<thead>
|
|
195
|
+
<tr>
|
|
196
|
+
<th>Session</th><th>Builder</th>
|
|
197
|
+
<th title="What the session did, derived from its own transcript: whether it wrote files, used tools, and how many prompts a person actually sent.">Kind</th>
|
|
198
|
+
<th>Tasks</th>
|
|
199
|
+
<th class="ledger__num">Earned</th><th class="ledger__num">Turns</th><th class="ledger__num">Tokens</th><th class="ledger__num">Tool errors</th>
|
|
200
|
+
<th class="ledger__num">Duration</th>
|
|
201
|
+
<th class="ledger__num" title="How the session read to this builder, scored against their own interaction profile. Advisory — it gates nothing.">Plain speech</th>
|
|
202
|
+
<th>Recorded</th>
|
|
203
|
+
</tr>
|
|
204
|
+
</thead>
|
|
205
|
+
<tbody id="sessions-body"></tbody>
|
|
206
|
+
</table>
|
|
207
|
+
<!-- The kit stamps the pager bar here (sessions.js, OTBKit.pagerBarHtml).
|
|
208
|
+
makeLedger is deliberately NOT used: it stamps table + pager into one
|
|
209
|
+
mount, and this page keeps them separate so the bar sits below the
|
|
210
|
+
ledger rather than inside whatever scrollport the table is in. -->
|
|
211
|
+
<div id="sessions-pager-mount"></div>
|
|
212
|
+
<p class="ledger__note" id="ledger-foot"></p>
|
|
213
|
+
</section>
|
|
214
|
+
|
|
215
|
+
<!-- task 1002830: the narrative. Opened from a row's Read door; the SAME page
|
|
216
|
+
and the same gate as the ledger above (ADR 0051 — no second page). The
|
|
217
|
+
server decides who may read a transcript and records an administrator's
|
|
218
|
+
cross-builder read BEFORE serving it; this panel only displays. -->
|
|
219
|
+
<section class="scroll" id="transcript-scroll" aria-labelledby="transcript-h" hidden>
|
|
220
|
+
<h2 id="transcript-h" class="scroll__h">Transcript</h2>
|
|
221
|
+
<p class="scroll__lede" id="transcript-sub"></p>
|
|
222
|
+
<p><button type="button" class="btn" id="transcript-close">Close transcript</button></p>
|
|
223
|
+
<div id="transcript-body" class="transcript"></div>
|
|
224
|
+
<p><button type="button" class="btn" id="transcript-more" hidden>Load more turns</button></p>
|
|
225
|
+
</section>
|
|
226
|
+
|
|
227
|
+
</div>
|
|
228
|
+
|
|
229
|
+
<!-- ============ tab: Autobongos (task 1005049: was the /gate page) ============ -->
|
|
230
|
+
<div id="pane-autobongos" role="tabpanel" aria-labelledby="tab-autobongos" hidden>
|
|
231
|
+
<p class="hall-loading hall-loading--page hall-loading--wait" role="status">Loading…</p>
|
|
232
|
+
<!-- task 1003469: the sealed state is a panel with a heading, not a bare
|
|
233
|
+
line on the ground. gate.js writes the real floor into the lede from
|
|
234
|
+
the pack's rank label, so the copy cannot drift from ADR 0157. -->
|
|
235
|
+
<section class="scroll" id="gate-sealed" aria-labelledby="gate-sealed-h" hidden>
|
|
236
|
+
<h2 class="scroll__h" id="gate-sealed-h">Restricted</h2>
|
|
237
|
+
<p class="scroll__lede" id="gate-sealed-body">This page opens at a higher rank.</p>
|
|
238
|
+
</section>
|
|
239
|
+
|
|
240
|
+
<section id="runner-body" hidden>
|
|
241
|
+
<!-- The Autobongos fence (task 1003908, ADR 0307 §6): what is allowed to
|
|
242
|
+
reach main unattended. This page held the held-PR list too until task
|
|
243
|
+
1004859 removed it; the URL stays /gate so bookmarks keep working.
|
|
244
|
+
Archon-only — a Metic gets the note below instead, and the routes
|
|
245
|
+
behind the panel are system:true/archon server-side regardless. -->
|
|
246
|
+
<p class="scroll__lede" id="gate-archon-only" hidden></p>
|
|
247
|
+
<section class="scroll" id="fence-panel" aria-labelledby="fence-h" hidden>
|
|
248
|
+
<h2 class="scroll__h" id="fence-h">Autobongos runner</h2>
|
|
249
|
+
<!-- One panel (task 1004509): a checklist while anything the runner
|
|
250
|
+
needs is missing, one status line with the switch once it is all
|
|
251
|
+
ticked. Drawn from /autonomy/runners + /autonomy/fence together. -->
|
|
252
|
+
<div id="fence-body"><span class="kit-skel"></span></div>
|
|
253
|
+
<!-- Every builder's runner (task 1004501): what each one picked to
|
|
254
|
+
work, whether you paused it, and whether it is checking in. -->
|
|
255
|
+
<div id="fence-builders"></div>
|
|
256
|
+
</section>
|
|
257
|
+
</section>
|
|
258
|
+
|
|
259
|
+
</div>
|
|
157
260
|
</main>
|
|
158
261
|
|
|
159
262
|
<footer class="app-foot" id="app-foot"></footer>
|
|
@@ -167,14 +270,17 @@
|
|
|
167
270
|
chrome exists before the page script runs. -->
|
|
168
271
|
<script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
|
|
169
272
|
<script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
|
|
170
|
-
<script src="/builders/craft-mode.js?v=2026-11-05-
|
|
171
|
-
<script src="/builders/shell.js?v=2026-11-05-
|
|
273
|
+
<script src="/builders/craft-mode.js?v=2026-11-05-oversight"></script>
|
|
274
|
+
<script src="/builders/shell.js?v=2026-11-05-oversight"></script>
|
|
172
275
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
173
276
|
<script src="/builders/palette.js" defer></script>
|
|
174
277
|
<!-- shared component kit (window.OTBKit) — tabs, ledger, pager (task 2166). -->
|
|
175
278
|
<script src="/builders/hall-kit.js"></script>
|
|
176
279
|
<script src="/builders/task-detail.js?v=2026-07-03-taskdetail"></script>
|
|
177
280
|
<script src="/builders/brand-apply.js?v=2026-06-19-r56"></script>
|
|
178
|
-
|
|
281
|
+
<!-- task 1005049: the two tabs' scripts, booted by watch.js when their tab first opens. -->
|
|
282
|
+
<script src="/builders/sessions.js?v=2026-11-05-tab"></script>
|
|
283
|
+
<script src="/builders/gate.js?v=2026-11-05-tab"></script>
|
|
284
|
+
<script src="/builders/watch.js?v=2026-11-05-tabs"></script>
|
|
179
285
|
</body>
|
|
180
286
|
</html>
|