@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
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// tests/hall_oversight_tabs.mjs — one oversight page (task 1005049, owner decision
|
|
2
|
+
// 2026-11-03, idea 1001315 item 1).
|
|
3
|
+
//
|
|
4
|
+
// Watch, Sessions and Autobongos (/gate) were three oversight pages in the same
|
|
5
|
+
// sidebar group. Sessions and Autobongos are tabs on Watch now:
|
|
6
|
+
//
|
|
7
|
+
// * OLD URLS LAND ON THE RIGHT TAB. /sessions and /gate 302 to /watch#sessions
|
|
8
|
+
// and /watch#autobongos (serve-internal.js RETIRED_TO_SECTION), with any query
|
|
9
|
+
// string kept; the real middleware runs here, on a bare app.
|
|
10
|
+
// * EVERYTHING THE TWO PAGES SHOWED IS ON WATCH. The tabs exist, their panes hold
|
|
11
|
+
// the ledger, the transcript reader and the runner panel, and the scripts that
|
|
12
|
+
// drew the pages still draw them.
|
|
13
|
+
// * A TAB LOADS WHEN IT OPENS. Each session-ledger load is an audited read on the
|
|
14
|
+
// server, so sessions.js and gate.js ask for nothing until Watch first opens
|
|
15
|
+
// their tab, and only once.
|
|
16
|
+
// * THE PALETTE STILL FINDS THEM: its Watch jumps are real tab ids.
|
|
17
|
+
//
|
|
18
|
+
// Run: node tests/hall_oversight_tabs.mjs
|
|
19
|
+
|
|
20
|
+
import { test } from 'node:test';
|
|
21
|
+
import assert from 'node:assert/strict';
|
|
22
|
+
import fs from 'node:fs';
|
|
23
|
+
import http from 'node:http';
|
|
24
|
+
import path from 'node:path';
|
|
25
|
+
import vm from 'node:vm';
|
|
26
|
+
import { createRequire } from 'node:module';
|
|
27
|
+
import { fileURLToPath } from 'node:url';
|
|
28
|
+
|
|
29
|
+
const require = createRequire(import.meta.url);
|
|
30
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
31
|
+
const HALL = path.join(ROOT, 'modules', 'hall-ui', 'public');
|
|
32
|
+
const read = (f) => fs.readFileSync(path.join(HALL, f), 'utf8');
|
|
33
|
+
const WATCH_HTML = read('watch.html');
|
|
34
|
+
const WATCH_JS = read('watch.js');
|
|
35
|
+
|
|
36
|
+
async function withHall(fn) {
|
|
37
|
+
const express = require('express');
|
|
38
|
+
const { mountInternalSurfaces } = require('../src/bongos/serve-internal.js');
|
|
39
|
+
const app = express();
|
|
40
|
+
mountInternalSurfaces(app);
|
|
41
|
+
const server = app.listen(0, '127.0.0.1');
|
|
42
|
+
await new Promise((r) => server.once('listening', r));
|
|
43
|
+
const { port } = server.address();
|
|
44
|
+
const get = (p, host) => new Promise((resolve, reject) => {
|
|
45
|
+
http.get({ host: '127.0.0.1', port, path: p, headers: { host } }, (res) => {
|
|
46
|
+
res.resume();
|
|
47
|
+
res.on('end', () => resolve({ status: res.statusCode, location: res.headers.location || '' }));
|
|
48
|
+
}).on('error', reject);
|
|
49
|
+
});
|
|
50
|
+
try { await fn(get); } finally { server.close(); }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const TAB_OF = { sessions: 'sessions', gate: 'autobongos' };
|
|
54
|
+
|
|
55
|
+
test('RUN: /sessions and /gate each 302 to their tab on Watch', async () => {
|
|
56
|
+
await withHall(async (get) => {
|
|
57
|
+
for (const [page, tab] of Object.entries(TAB_OF)) {
|
|
58
|
+
for (const form of [`/${page}`, `/${page}/`, `/${page}.html`]) {
|
|
59
|
+
const r = await get(form, 'builders.127.0.0.1');
|
|
60
|
+
assert.equal(r.status, 302, `${form} is a temporary redirect`);
|
|
61
|
+
assert.equal(r.location, `/watch#${tab}`, `${form} lands on #${tab}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const q = await get('/sessions?from=mail', 'builders.127.0.0.1');
|
|
65
|
+
assert.equal(q.location, '/watch?from=mail#sessions', 'a query string rides along; the tab is the fragment');
|
|
66
|
+
// The map the two joined still carries the page it was built for (task 1005047).
|
|
67
|
+
assert.equal((await get('/fleet', 'builders.127.0.0.1')).location, '/money#hosting');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('Watch has a Sessions and an Autobongos tab, each with its pane', () => {
|
|
72
|
+
assert.match(WATCH_JS, /\{ id: 'sessions', label: 'Sessions', panel: 'pane-sessions' \}/);
|
|
73
|
+
assert.match(WATCH_JS, /\{ id: 'autobongos', label: 'Autobongos', panel: 'pane-autobongos' \}/);
|
|
74
|
+
for (const pane of ['pane-sessions', 'pane-autobongos']) {
|
|
75
|
+
assert.match(WATCH_HTML, new RegExp(`<div id="${pane}" role="tabpanel" aria-labelledby="tab-${pane.slice(5)}" hidden>`));
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('everything the two pages showed is in those panes', () => {
|
|
80
|
+
const sessions = WATCH_HTML.slice(WATCH_HTML.indexOf('<div id="pane-sessions"'), WATCH_HTML.indexOf('<div id="pane-autobongos"'));
|
|
81
|
+
for (const id of ['sess-sealed', 'stats-scroll', 'sess-ledger-scroll', 'session-type-filter', 'sessions-table', 'sessions-pager-mount', 'transcript-scroll']) {
|
|
82
|
+
assert.match(sessions, new RegExp(`id="${id}"`), `the Sessions tab holds #${id}`);
|
|
83
|
+
}
|
|
84
|
+
const runner = WATCH_HTML.slice(WATCH_HTML.indexOf('<div id="pane-autobongos"'));
|
|
85
|
+
for (const id of ['gate-sealed', 'runner-body', 'gate-archon-only', 'fence-panel', 'fence-body', 'fence-builders']) {
|
|
86
|
+
assert.match(runner, new RegExp(`id="${id}"`), `the Autobongos tab holds #${id}`);
|
|
87
|
+
}
|
|
88
|
+
// Their scripts load before watch.js, which boots them.
|
|
89
|
+
const i = (s) => WATCH_HTML.indexOf(s);
|
|
90
|
+
assert.ok(i('/builders/sessions.js') > 0 && i('/builders/sessions.js') < i('/builders/watch.js'));
|
|
91
|
+
assert.ok(i('/builders/gate.js') > 0 && i('/builders/gate.js') < i('/builders/watch.js'));
|
|
92
|
+
// No element id on the page is used twice: three pages' markup now share one document.
|
|
93
|
+
const ids = [...WATCH_HTML.replace(/<!--[\s\S]*?-->/g, '').matchAll(/\sid="([^"]+)"/g)].map((m) => m[1]);
|
|
94
|
+
assert.deepEqual(ids.filter((x, k) => ids.indexOf(x) !== k), [], 'every id on Watch is said once');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('the pages are gone, and nothing in the sidebar points at them', () => {
|
|
98
|
+
assert.ok(!fs.existsSync(path.join(HALL, 'sessions.html')));
|
|
99
|
+
assert.ok(!fs.existsSync(path.join(HALL, 'gate.html')));
|
|
100
|
+
assert.ok(!/href: '\/(sessions|gate)'/.test(read('shell.js')));
|
|
101
|
+
assert.ok(!/'gate', 'sessions'/.test(read('craft-mode.js')), 'Governing mode no longer lists them');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
function bootTab(file, { sharedMe = false } = {}) {
|
|
105
|
+
const requests = [];
|
|
106
|
+
const el = () => ({ hidden: true, textContent: '', innerHTML: '', value: '', dataset: {}, style: {}, addEventListener() {}, querySelector: () => null, querySelectorAll: () => [], appendChild() {} });
|
|
107
|
+
const els = {};
|
|
108
|
+
const getEl = (id) => (els[id] = els[id] || el());
|
|
109
|
+
const document = { getElementById: getEl, querySelector: (s) => getEl(String(s).replace(/^#/, '')), querySelectorAll: () => [], createElement: el, addEventListener() {} };
|
|
110
|
+
const window = {
|
|
111
|
+
BongosClient: { createClient: () => ({ request: async (m, p) => { requests.push(p); return { status: 200, ok: true, data: { builder: { id: 1, rank: 'xenos' } } }; } }) },
|
|
112
|
+
OTB: { escapeHtml: (s) => String(s), $: document.querySelector, num: Number, fmtInt: String, fmtCompact: String, fmtDate: String, rankLabel: (r) => r, toast() {} },
|
|
113
|
+
OTBKit: { emptyStateHtml: (m) => m, makePager: () => ({ page: 1, perPage: 25 }) },
|
|
114
|
+
location: { assign() {} },
|
|
115
|
+
localStorage: { getItem: () => null, setItem() {} },
|
|
116
|
+
addEventListener() {},
|
|
117
|
+
};
|
|
118
|
+
// The shell's own /me read (shell.js probeMe shares it on window.__hallReads).
|
|
119
|
+
// A viewer below the floor, so each tab stops at its own sealed notice after /me.
|
|
120
|
+
if (sharedMe) window.__hallReads = { '/me': Promise.resolve({ builder: { id: 1, rank: 'xenos' } }) };
|
|
121
|
+
const sandbox = { window, document, console: { log() {}, warn() {}, error() {} }, setTimeout, clearTimeout, Promise, URLSearchParams };
|
|
122
|
+
vm.createContext(sandbox);
|
|
123
|
+
vm.runInContext(read(file), sandbox, { filename: file });
|
|
124
|
+
return { window, requests, getEl };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (const [file, tab] of [['sessions.js', 'sessions'], ['gate.js', 'autobongos']]) {
|
|
128
|
+
test(`${file} asks for nothing until Watch opens its tab, then boots once`, async () => {
|
|
129
|
+
const t = bootTab(file);
|
|
130
|
+
await new Promise((r) => setImmediate(r));
|
|
131
|
+
assert.equal(t.requests.length, 0, `${file} read the server before its tab opened`);
|
|
132
|
+
assert.equal(typeof t.window.OTBWatchTabs[tab], 'function', `${file} hands Watch a boot for #${tab}`);
|
|
133
|
+
t.window.OTBWatchTabs[tab]();
|
|
134
|
+
await new Promise((r) => setImmediate(r));
|
|
135
|
+
const first = t.requests.length;
|
|
136
|
+
assert.ok(first >= 1, 'opening the tab loads it');
|
|
137
|
+
t.window.OTBWatchTabs[tab]();
|
|
138
|
+
await new Promise((r) => setImmediate(r));
|
|
139
|
+
assert.equal(t.requests.length, first, 'opening it again does not load it twice');
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
for (const [file, tab] of [['sessions.js', 'sessions'], ['gate.js', 'autobongos']]) {
|
|
144
|
+
test(`${file} adopts the shell's /me read rather than asking again`, async () => {
|
|
145
|
+
const t = bootTab(file, { sharedMe: true });
|
|
146
|
+
t.window.OTBWatchTabs[tab]();
|
|
147
|
+
for (let i = 0; i < 5; i++) await new Promise((r) => setImmediate(r));
|
|
148
|
+
assert.ok(!t.requests.some((p) => /\/me$/.test(p)), `${file} asked /me although the shell had`);
|
|
149
|
+
assert.equal(t.getEl(file === 'gate.js' ? 'gate-sealed' : 'sess-sealed').hidden, false, 'and still acts on the answer');
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
test('watch.js boots a tab from its own change hook', () => {
|
|
154
|
+
assert.match(WATCH_JS, /onChange: \(id\) => \{\s*const boot = window\.OTBWatchTabs && window\.OTBWatchTabs\[id\];\s*if \(boot\) boot\(\);/);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('the palette\'s Watch jumps are real watch.js tab ids', () => {
|
|
158
|
+
const ids = [...WATCH_JS.matchAll(/\{ id: '([a-z-]+)', label: '[^']*', panel:/g)].map((m) => m[1]);
|
|
159
|
+
const declared = [...read('palette.js').matchAll(/href:\s*'\/watch#([a-z-]+)'/g)].map((m) => m[1]);
|
|
160
|
+
assert.deepEqual(declared, ids, 'one jump per Watch tab, in order');
|
|
161
|
+
assert.ok(declared.includes('sessions') && declared.includes('autobongos'));
|
|
162
|
+
});
|
|
@@ -17,7 +17,8 @@ import { fileURLToPath } from 'node:url';
|
|
|
17
17
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
18
18
|
const PUB = path.join(ROOT, 'modules', 'hall-ui', 'public');
|
|
19
19
|
const read = (f) => fs.readFileSync(path.join(PUB, f), 'utf8').replace(/\r\n/g, '\n');
|
|
20
|
-
|
|
20
|
+
// task 1005049: the /gate page is Watch's Autobongos tab; its script and sheet stay.
|
|
21
|
+
const PAGES = ['government', 'watch'];
|
|
21
22
|
|
|
22
23
|
test('every oversight page links the family sheet, before its own', () => {
|
|
23
24
|
for (const p of PAGES) {
|
|
@@ -27,6 +28,11 @@ test('every oversight page links the family sheet, before its own', () => {
|
|
|
27
28
|
assert.ok(fam > 0, `${p}.html links oversight.css`);
|
|
28
29
|
assert.ok(own > fam, `${p}.html links its own sheet after the family's`);
|
|
29
30
|
}
|
|
31
|
+
// Watch's two folded-in tabs bring their sheets, after the family's too.
|
|
32
|
+
const watch = read('watch.html');
|
|
33
|
+
for (const sheet of ['sessions', 'gate']) {
|
|
34
|
+
assert.ok(watch.indexOf(`/builders/${sheet}.css`) > watch.indexOf('/builders/oversight.css'), `watch.html links ${sheet}.css after the family's`);
|
|
35
|
+
}
|
|
30
36
|
});
|
|
31
37
|
|
|
32
38
|
test('the family sheet spends only the fifteen: no literal colour', () => {
|
|
@@ -110,6 +116,10 @@ test('the sealed state is a panel with the floor written from the pack, on all f
|
|
|
110
116
|
assert.match(read(`${p}.html`), /class="scroll" id="[a-z-]*sealed(-scroll)?" /, `${p}.html seals with a .scroll panel`);
|
|
111
117
|
assert.match(read(`${p}.js`), /rankLabel\('metic'\)/, `${p}.js writes the floor from the pack's rank label`);
|
|
112
118
|
}
|
|
119
|
+
// The Autobongos and Sessions tabs keep their own sealed panels inside Watch.
|
|
120
|
+
assert.match(read('watch.html'), /class="scroll" id="gate-sealed" /);
|
|
121
|
+
assert.match(read('watch.html'), /class="scroll" id="sess-sealed" /);
|
|
122
|
+
assert.match(read('gate.js'), /rankLabel\('metic'\)/);
|
|
113
123
|
});
|
|
114
124
|
|
|
115
125
|
test('the vocabulary wall holds: the family is named oversight on the government surface', () => {
|
|
@@ -278,7 +288,9 @@ test('RUN: a metric its snapshots never carried is skipped, not drawn as a cliff
|
|
|
278
288
|
|
|
279
289
|
// ---- task 1004829: the government pages read like the rebuilt Home ----------
|
|
280
290
|
|
|
281
|
-
const GOV_PAGES = ['watch', '
|
|
291
|
+
const GOV_PAGES = ['watch', 'project-settings', 'money', 'deploy', 'government'];
|
|
292
|
+
// task 1005049: gate.js and sessions.js draw Watch's tabs now; their scripts still must not.
|
|
293
|
+
const GOV_SCRIPTS = [...GOV_PAGES, 'gate', 'sessions'];
|
|
282
294
|
|
|
283
295
|
test('every government page head holds only its h1, and no script writes into it', () => {
|
|
284
296
|
// No page shows its head (task 1004836). A sub-line there is a line nobody
|
|
@@ -291,7 +303,7 @@ test('every government page head holds only its h1, and no script writes into it
|
|
|
291
303
|
assert.doesNotMatch(head[1], /page-head__eyebrow|page-head__sub/, `${page}.html's hidden head holds only the h1`);
|
|
292
304
|
}
|
|
293
305
|
const subIds = ['watch-sub', 'gate-sub', 'ledger-sub', 'ps-sub', 'dp-sub', 'fl-sub', 'gov-sub'];
|
|
294
|
-
for (const page of
|
|
306
|
+
for (const page of GOV_SCRIPTS) {
|
|
295
307
|
const js = read(`${page}.js`);
|
|
296
308
|
for (const id of subIds) assert.ok(!js.includes(id), `${page}.js no longer writes into #${id}`);
|
|
297
309
|
}
|
|
@@ -300,7 +312,7 @@ test('every government page head holds only its h1, and no script writes into it
|
|
|
300
312
|
test('a failed load on Watch and Sessions is said in the page body', () => {
|
|
301
313
|
assert.match(read('watch.js'), /showSealed\('Could not check your access\. Reload to try again\.', 'Could not load'\)/);
|
|
302
314
|
assert.match(read('sessions.js'), /showSealed\('Could not check your access\. Reload to try again\.', 'Could not load'\)/);
|
|
303
|
-
assert.match(read('
|
|
315
|
+
assert.match(read('watch.html'), /id="ledger-status" role="status"/, 'the count and a failed page load sit on the ledger itself');
|
|
304
316
|
});
|
|
305
317
|
|
|
306
318
|
test('the page at /gate is the Autobongos panel: no held-PR list, and a Metic is told the controls are the Archon\'s', () => {
|
|
@@ -312,10 +324,12 @@ test('the page at /gate is the Autobongos panel: no held-PR list, and a Metic is
|
|
|
312
324
|
assert.ok(!/renderRow\(pr|OTBAsk/.test(js), 'no held-PR row or ask card is drawn');
|
|
313
325
|
assert.match(js, /if \(rank !== 'archon'\) \{ showArchonOnly\(\); return; \}/, 'a Metic gets the note, not an empty page');
|
|
314
326
|
assert.match(js, /The Autobongos controls are for the \$\{rankLabel\('archon'\)\}/, 'the rank is named from the pack');
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
assert.match(
|
|
327
|
+
// task 1005049: the panel is Watch's Autobongos tab, and /gate lands there.
|
|
328
|
+
const html = read('watch.html');
|
|
329
|
+
const pane = html.slice(html.indexOf('<div id="pane-autobongos"'));
|
|
330
|
+
assert.match(pane, /id="gate-archon-only"/);
|
|
319
331
|
assert.ok(!/gate-list|hall-ask\.js/.test(html), 'the list mount and the ask-card script are gone');
|
|
320
|
-
assert.match(read('
|
|
332
|
+
assert.match(read('watch.js'), /\{ id: 'autobongos', label: 'Autobongos', panel: 'pane-autobongos' \}/, 'the tab says what it is');
|
|
333
|
+
assert.ok(!/href: '\/gate'/.test(read('shell.js')), 'the sidebar no longer lists it');
|
|
334
|
+
assert.match(fs.readFileSync(path.join(ROOT, 'src/bongos/serve-internal.js'), 'utf8'), /gate: 'watch#autobongos'/, 'and the old URL lands on the tab');
|
|
321
335
|
});
|
|
@@ -120,8 +120,10 @@ const ORACLE = {
|
|
|
120
120
|
|
|
121
121
|
// Metic+ operator surfaces (the Governance nav group).
|
|
122
122
|
'/builders/watch': 'requireGovernmentPage',
|
|
123
|
-
|
|
124
|
-
'
|
|
123
|
+
// Gone (task 1005049, owner decision 2026-11-03): Sessions and Autobongos are tabs
|
|
124
|
+
// on Watch, behind Watch's gate, and both URLs 302 to their tab before any gate runs.
|
|
125
|
+
'/builders/gate': null,
|
|
126
|
+
'/builders/sessions': null,
|
|
125
127
|
// The five fundamentals of the project (task 1003280, goal 1000072). Same tier as
|
|
126
128
|
// its neighbours: a steering surface, metic+ per ADR 0157. Its two WRITES gate
|
|
127
129
|
// independently on `project.settings.manage`, so this row is only about the shell.
|
|
@@ -360,6 +362,9 @@ const GONE_PAGES = {
|
|
|
360
362
|
'/builders/ranks': 'task 1005046 — the rank ladder is a Primer chapter; /ranks redirects there',
|
|
361
363
|
'/builders/drachmae': 'task 1005046 — credits and karma is a Primer chapter; /drachmae redirects there',
|
|
362
364
|
'/builders/diagrams': 'task 1005046 — each diagram sits in its Primer chapter; /diagrams redirects there',
|
|
365
|
+
// Task 1005049 — one oversight page: each is a tab on Watch, and the old URL 302s to it.
|
|
366
|
+
'/builders/sessions': 'task 1005049 — the session log is Watch\'s Sessions tab; /sessions redirects there',
|
|
367
|
+
'/builders/gate': 'task 1005049 — the runner panel is Watch\'s Autobongos tab; /gate redirects there',
|
|
363
368
|
};
|
|
364
369
|
|
|
365
370
|
test('a row that is null because the page left the hall serves no page', () => {
|
|
@@ -230,12 +230,15 @@ test('sessions: the nowrap + wrapper pair is gone, and with it the sticky-header
|
|
|
230
230
|
const css = decomment(read('sessions.css'));
|
|
231
231
|
assert.doesNotMatch(css, /white-space:\s*nowrap/, 'no blanket nowrap on every cell');
|
|
232
232
|
assert.doesNotMatch(css, /ledger-table-wrap/, 'no wrapper — the shell\'s card strip handles a phone');
|
|
233
|
-
assert.doesNotMatch(
|
|
233
|
+
assert.doesNotMatch(sessionsPane(), /ledger-table-wrap/);
|
|
234
234
|
assert.doesNotMatch(css, /var\(--info\)|ledger-sid/, 'the session id is no longer painted in the info colour');
|
|
235
235
|
noAlias(css, 'sessions.css');
|
|
236
236
|
tokensDefined(css, 'sessions.css');
|
|
237
237
|
});
|
|
238
238
|
|
|
239
|
+
// task 1005049: the session ledger is Watch's Sessions tab; its markup is that pane.
|
|
240
|
+
const sessionsPane = () => { const w = read('watch.html'); return w.slice(w.indexOf('<div id="pane-sessions"'), w.indexOf('<div id="pane-autobongos"')); };
|
|
241
|
+
|
|
239
242
|
test('sessions: every id on a row is a door, the earned figure is shown, the totals ride the top strip', () => {
|
|
240
243
|
const src = read('sessions.js');
|
|
241
244
|
assert.match(src, /<a class="fact-chip" href="\/builders\/task\/\$\{encodeURIComponent\(String\(t\)\)\}">task \$\{escapeHtml\(String\(t\)\)\}<\/a>/,
|
|
@@ -244,8 +247,8 @@ test('sessions: every id on a row is a door, the earned figure is shown, the tot
|
|
|
244
247
|
assert.match(src, /<span class="ov-mono" title="\$\{escapeHtml\(s\.session_id\)\}">/, 'the session id is a mono fact with the full id on hover');
|
|
245
248
|
assert.match(src, /fmtInt\(s\.drachmae_earned\)/, 'what the session earned is on the row');
|
|
246
249
|
assert.doesNotMatch(src, /`#\$\{escapeHtml\(t\)\}`/, 'no bare hash-prefixed task text');
|
|
247
|
-
const html =
|
|
248
|
-
assert.match(html, /<section class="ov-summary" id="stats-scroll"/, 'the totals are the top-of-
|
|
250
|
+
const html = sessionsPane();
|
|
251
|
+
assert.match(html, /<section class="ov-summary" id="stats-scroll"/, 'the totals are the top-of-tab strip');
|
|
249
252
|
assert.match(html, /<div class="stat-row" id="stats-body"><\/div>/);
|
|
250
253
|
const ths = (html.match(/<th[ >]/g) || []).length;
|
|
251
254
|
// task 1002516: a tenth column, "Plain speech" — the transcript accessibility
|
|
@@ -259,8 +262,9 @@ test('sessions: every id on a row is a door, the earned figure is shown, the tot
|
|
|
259
262
|
// empty state short by a column, which no screenshot pass would catch.
|
|
260
263
|
assert.equal(ths, 11, 'eleven columns: session, builder, kind, tasks, earned, turns, tokens, tool errors, duration, plain speech, recorded');
|
|
261
264
|
assert.match(src, new RegExp(`colspan="${ths}"`), 'the empty row spans them all');
|
|
262
|
-
const
|
|
263
|
-
|
|
265
|
+
const page = read('watch.html');
|
|
266
|
+
const fam = page.indexOf('/builders/oversight.css');
|
|
267
|
+
assert.ok(fam > 0 && page.indexOf('/builders/sessions.css') > fam, 'watch.html links the family sheet before the sessions sheet');
|
|
264
268
|
});
|
|
265
269
|
|
|
266
270
|
test('sessions: the floor is written from the pack\'s rank label, never a hardcoded rank', () => {
|
|
@@ -268,9 +272,9 @@ test('sessions: the floor is written from the pack\'s rank label, never a hardco
|
|
|
268
272
|
const sealed = src.slice(src.indexOf('function showSealed'), src.indexOf('// ---- paging state'));
|
|
269
273
|
assert.match(sealed, /const floor = rankLabel\('metic'\)/);
|
|
270
274
|
// a failed load names its own heading; the rank floor is the default (task 1004829)
|
|
271
|
-
assert.match(sealed, /\$\('#
|
|
275
|
+
assert.match(sealed, /\$\('#sess-sealed-h'\)\.textContent = heading \|\| `\$\{floor\} and up`/);
|
|
272
276
|
assert.doesNotMatch(decomment(src), /Archons only|Metic rank up|Archon-only/);
|
|
273
|
-
assert.doesNotMatch(
|
|
277
|
+
assert.doesNotMatch(sessionsPane(), /Archon/);
|
|
274
278
|
});
|
|
275
279
|
|
|
276
280
|
// =========================================================================
|
|
@@ -299,8 +303,9 @@ test('the states files exercise the redesign\'s new states', () => {
|
|
|
299
303
|
const goals = JSON.parse(read('goals.states.json'));
|
|
300
304
|
assert.ok(goals.states.filters && goals.states.cards, 'goals: the Filters disclosure and the cards view are measured');
|
|
301
305
|
assert.deepEqual(goals.states.cards.expect.value, [['#f-goal-view', 'cards']]);
|
|
302
|
-
|
|
303
|
-
|
|
306
|
+
// task 1005049: the ledger is Watch's Sessions tab, measured as that page's state.
|
|
307
|
+
const watch = JSON.parse(read('watch.states.json'));
|
|
308
|
+
assert.deepEqual(watch.states.sessions.expect.visible, ['#pane-sessions', '#stats-scroll', '#sess-ledger-scroll']);
|
|
304
309
|
});
|
|
305
310
|
|
|
306
311
|
test('the roster draws the circuit divider in CSS, on the first row\'s cell and not the row', () => {
|
|
@@ -19,7 +19,8 @@ const PUB = path.join(ROOT, 'modules', 'hall-ui', 'public');
|
|
|
19
19
|
const read = (f) => fs.readFileSync(path.join(PUB, f), 'utf8').replace(/\r\n/g, '\n');
|
|
20
20
|
|
|
21
21
|
const JS = read('sessions.js');
|
|
22
|
-
|
|
22
|
+
// task 1005049: the ledger is Watch's Sessions tab; its markup lives in watch.html.
|
|
23
|
+
const HTML = (() => { const w = read('watch.html'); return w.slice(w.indexOf('<div id="pane-sessions"'), w.indexOf('<div id="pane-autobongos"')); })();
|
|
23
24
|
|
|
24
25
|
// Slice `const ACCESSIBILITY_TITLE = {...}` through the end of accessibilityCell
|
|
25
26
|
// and run it with the one helper it closes over.
|
|
@@ -95,8 +95,9 @@ test('the hall-preview fixture carries every atom /me resolves', () => {
|
|
|
95
95
|
// task 1004846: the new logo (its bump landed late, in task 1004852); task 1004938: the
|
|
96
96
|
// Market Watch settings nav item and its fuel glyph; task 1005041: the Update pill replaced the
|
|
97
97
|
// core-update banner; task 1005045: the Market Watch settings item left (one sidebar item);
|
|
98
|
-
// task 1005046: Ranks, Economy and Diagrams left Docs (one reading room, the Primer)
|
|
99
|
-
|
|
98
|
+
// task 1005046: Ranks, Economy and Diagrams left Docs (one reading room, the Primer);
|
|
99
|
+
// task 1005049: Sessions and Autobongos left the sidebar (tabs on Watch).
|
|
100
|
+
const SHELL_SHA = 'bb0abbadab2508ca';
|
|
100
101
|
|
|
101
102
|
test('shell.js has not changed without its cache-bust being bumped', () => {
|
|
102
103
|
const actual = crypto.createHash('sha256').update(SHELL).digest('hex').slice(0, 16);
|
|
@@ -93,7 +93,9 @@ const STATIC_LADDER_FILES = [
|
|
|
93
93
|
'modules/discord/discord-channels.js',
|
|
94
94
|
'modules/memory/search.js',
|
|
95
95
|
'modules/hall-ui/public/profile.js',
|
|
96
|
-
|
|
96
|
+
// task 1005048: the rank <select> on each roster row moved from watch.js to the
|
|
97
|
+
// Builders page with the roster itself; the copy moved with it.
|
|
98
|
+
'modules/hall-ui/public/roster.js',
|
|
97
99
|
// task 1003094: the Constitution panel and the Board Room's sitting card both
|
|
98
100
|
// expand the `rank:<key>+` membership form ("that rank and above") to render
|
|
99
101
|
// who governs in plain language, so both need the ladder to expand against.
|