@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.
Files changed (78) hide show
  1. package/.bongos-core.json +87 -102
  2. package/docs/canonical-permissions.md +2 -1
  3. package/docs/copy-inventory.md +122 -127
  4. package/docs/copy-registry.json +179 -244
  5. package/docs/module-api-changelog.md +4 -0
  6. package/docs/page-inventory.json +11 -69
  7. package/docs/page-readings.json +434 -822
  8. package/modules/hall-ui/public/ai-studio.html +2 -2
  9. package/modules/hall-ui/public/atlas.html +2 -2
  10. package/modules/hall-ui/public/blockers.html +2 -2
  11. package/modules/hall-ui/public/board-room.html +2 -2
  12. package/modules/hall-ui/public/city.html +2 -2
  13. package/modules/hall-ui/public/collab.html +2 -2
  14. package/modules/hall-ui/public/commands.html +2 -2
  15. package/modules/hall-ui/public/copy-desk.html +2 -2
  16. package/modules/hall-ui/public/craft-mode.js +1 -1
  17. package/modules/hall-ui/public/deploy.html +2 -2
  18. package/modules/hall-ui/public/gate.js +12 -5
  19. package/modules/hall-ui/public/goal-map.html +2 -2
  20. package/modules/hall-ui/public/goals.html +2 -2
  21. package/modules/hall-ui/public/government.html +2 -2
  22. package/modules/hall-ui/public/idea.html +2 -2
  23. package/modules/hall-ui/public/ideas.html +2 -2
  24. package/modules/hall-ui/public/index.html +2 -2
  25. package/modules/hall-ui/public/market-watch-item.html +2 -2
  26. package/modules/hall-ui/public/market-watch-rules.html +2 -2
  27. package/modules/hall-ui/public/market-watch.html +2 -2
  28. package/modules/hall-ui/public/modules.html +2 -2
  29. package/modules/hall-ui/public/money.html +2 -2
  30. package/modules/hall-ui/public/my-agents.html +2 -2
  31. package/modules/hall-ui/public/palette.js +10 -0
  32. package/modules/hall-ui/public/primer.html +2 -2
  33. package/modules/hall-ui/public/profile.html +2 -2
  34. package/modules/hall-ui/public/project-settings.html +2 -2
  35. package/modules/hall-ui/public/roadmap.html +2 -2
  36. package/modules/hall-ui/public/roster.css +10 -0
  37. package/modules/hall-ui/public/roster.html +6 -3
  38. package/modules/hall-ui/public/roster.js +168 -9
  39. package/modules/hall-ui/public/sessions.js +21 -10
  40. package/modules/hall-ui/public/settings.html +2 -2
  41. package/modules/hall-ui/public/shell.js +1 -2
  42. package/modules/hall-ui/public/street.html +2 -2
  43. package/modules/hall-ui/public/studio.html +2 -2
  44. package/modules/hall-ui/public/task.html +2 -2
  45. package/modules/hall-ui/public/thinking.html +2 -2
  46. package/modules/hall-ui/public/tweak-editor.html +2 -2
  47. package/modules/hall-ui/public/watch.css +0 -6
  48. package/modules/hall-ui/public/watch.html +121 -15
  49. package/modules/hall-ui/public/watch.js +28 -166
  50. package/modules/hall-ui/public/watch.states.json +49 -1
  51. package/modules/hall-ui/public/work.html +2 -2
  52. package/modules/hall-ui/records/builders-roster.md +2 -0
  53. package/package-lock.json +2 -2
  54. package/package.json +1 -1
  55. package/release-notes.json +12 -0
  56. package/scripts/bongos/gen-hall-sitemap.js +2 -0
  57. package/src/bongos/routes/me.js +3 -1
  58. package/src/bongos/serve-internal.js +15 -22
  59. package/src/module-api.js +1 -1
  60. package/tests/autonomy_builder_scope.mjs +2 -1
  61. package/tests/hall_audit.mjs +1 -1
  62. package/tests/hall_loading.mjs +13 -2
  63. package/tests/hall_nav.mjs +5 -2
  64. package/tests/hall_oversight_tabs.mjs +162 -0
  65. package/tests/hall_oversight_world.mjs +23 -9
  66. package/tests/hall_page_gate_map.mjs +7 -2
  67. package/tests/hall_roster_world.mjs +14 -9
  68. package/tests/hall_sessions_ledger.mjs +2 -1
  69. package/tests/nav_permission_atoms.mjs +3 -2
  70. package/tests/rank_tier_single_source.mjs +3 -1
  71. package/tests/roster_government_columns.mjs +369 -0
  72. package/tests/sessions_kit_pager.mjs +8 -4
  73. package/tests/watch_sealed_floor.mjs +6 -4
  74. package/modules/hall-ui/public/gate.html +0 -76
  75. package/modules/hall-ui/public/gate.states.json +0 -60
  76. package/modules/hall-ui/public/sessions.html +0 -128
  77. package/modules/hall-ui/public/sessions.states.json +0 -59
  78. 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">' + rankBadgeHtml(b.rank) + '</td>'
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
- return ['Place', 'Builder', 'Roles', 'Rank',
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 = stampPlaces(Array.isArray(rows) ? rows : []);
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 (list) {
346
- marks = window.OTBFounding ? window.OTBFounding.byId(list) : {};
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 (/builders/sessions).
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 = $('#gate-scroll');
289
- $('#gate-h').textContent = heading || `${floor} and up`;
290
- if (message) $('#gate-body').textContent = 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
- $('#sessions-scroll').hidden = true;
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 sessions.html; the PAGER is the kit's.
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
- $('#gate-scroll').hidden = true;
432
+ $('#sess-sealed').hidden = true;
432
433
  $('#stats-scroll').hidden = false;
433
- $('#sessions-scroll').hidden = false;
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
- const res = await api.request('GET', `${API}/me`);
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
- load();
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-sections"></script>
397
- <script src="/builders/shell.js?v=2026-11-05-sections"></script>
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
- { id: 'gate', label: 'Autobongos', icon: 'gate', href: '/gate', gate: 'government', position: true }, // task 1004859: the runner panel; URL kept for bookmarks
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-sections"></script>
234
- <script src="/builders/shell.js?v=2026-11-05-sections"></script>
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-sections"></script>
228
- <script src="/builders/shell.js?v=2026-11-05-sections"></script>
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-sections"></script>
62
- <script src="/builders/shell.js?v=2026-11-05-sections"></script>
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-sections"></script>
144
- <script src="/builders/shell.js?v=2026-11-05-sections"></script>
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-sections"></script>
148
- <script src="/builders/shell.js?v=2026-11-05-sections"></script>
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, roster, reviews, and repo health.">
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="#people"><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 People tab</span></a>
52
- <a class="stat-tile" href="#operations"><span class="stat-tile__label">Sessions</span><span class="stat-tile__num" id="att-sessions">—</span><span class="stat-tile__sub">recorded, on the Operations tab</span></a>
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 lives on the Sessions page.</p>
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="/sessions">View all sessions</a></p>
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-sections"></script>
171
- <script src="/builders/shell.js?v=2026-11-05-sections"></script>
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
- <script src="/builders/watch.js?v=2026-09-02-ledgers"></script>
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>