@bongos/core 1.24.29 → 1.24.31
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 +198 -108
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +12 -0
- package/clients/bongos-client/index.cjs +12 -0
- package/clients/bongos-client/index.d.ts +12 -0
- package/clients/bongos-client/index.mjs +12 -0
- package/docs/api/openapi.json +174 -1
- package/docs/api-reference.md +12 -2
- package/docs/copy-inventory.md +226 -224
- package/docs/copy-registry.json +583 -549
- package/docs/module-api-changelog.md +2 -0
- package/docs/page-inventory.json +18 -37
- package/docs/page-readings.json +306 -361
- package/modules/copy-desk/module.json +1 -1
- package/modules/copy-desk/routes/copy-desk-studio.js +103 -0
- package/modules/copy-desk/tests/copy_no_cms.mjs +2 -2
- package/modules/government/db.js +29 -0
- package/modules/government/docket.js +61 -4
- package/modules/government/routes/government.js +2 -0
- package/modules/hall-ui/public/ai-studio.html +1 -1
- package/modules/hall-ui/public/atlas.html +1 -1
- package/modules/hall-ui/public/blockers.html +1 -1
- package/modules/hall-ui/public/board-room.html +1 -1
- package/modules/hall-ui/public/builders.js +3 -79
- package/modules/hall-ui/public/city.html +1 -1
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/commands.html +1 -1
- package/modules/hall-ui/public/copy-desk.html +1 -1
- package/modules/hall-ui/public/craft-mode.js +1 -1
- package/modules/hall-ui/public/deploy.html +1 -1
- package/modules/hall-ui/public/diagrams.html +1 -1
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/fleet.html +1 -1
- package/modules/hall-ui/public/gate.html +1 -1
- package/modules/hall-ui/public/goal-map.html +1 -1
- package/modules/hall-ui/public/goals.html +1 -1
- package/modules/hall-ui/public/government.html +1 -1
- package/modules/hall-ui/public/hall-render.js +68 -167
- package/modules/hall-ui/public/hall-widgets.js +4 -45
- package/modules/hall-ui/public/home-city-draw.js +583 -0
- package/modules/hall-ui/public/home-city.css +102 -0
- package/modules/hall-ui/public/home-city.js +339 -0
- package/modules/hall-ui/public/home-gallery.css +129 -0
- package/modules/hall-ui/public/home-gallery.js +349 -0
- package/modules/hall-ui/public/home-studio.css +72 -0
- package/modules/hall-ui/public/home-studio.js +203 -0
- package/modules/hall-ui/public/idea.html +1 -1
- package/modules/hall-ui/public/ideas.html +1 -1
- package/modules/hall-ui/public/index.html +9 -7
- package/modules/hall-ui/public/index.states.json +73 -8
- package/modules/hall-ui/public/market-watch-item.html +1 -1
- package/modules/hall-ui/public/market-watch-rules.html +1 -1
- package/modules/hall-ui/public/market-watch-settings.js +6 -1
- package/modules/hall-ui/public/market-watch.html +141 -54
- package/modules/hall-ui/public/market-watch.js +54 -0
- package/modules/hall-ui/public/market-watch.states.json +2 -1
- package/modules/hall-ui/public/modules.html +1 -1
- package/modules/hall-ui/public/my-agents.html +1 -1
- package/modules/hall-ui/public/primer.html +1 -1
- package/modules/hall-ui/public/profile.html +1 -1
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/ranks.html +1 -1
- package/modules/hall-ui/public/roadmap.html +1 -1
- package/modules/hall-ui/public/roster.html +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings.html +1 -1
- package/modules/hall-ui/public/shell.js +2 -3
- package/modules/hall-ui/public/street.html +1 -1
- package/modules/hall-ui/public/studio.html +9 -1
- package/modules/hall-ui/public/style.css +74 -27
- package/modules/hall-ui/public/task.html +1 -1
- package/modules/hall-ui/public/thinking.html +1 -1
- package/modules/hall-ui/public/tweak-editor.html +1 -1
- package/modules/hall-ui/public/watch.html +1 -1
- package/modules/hall-ui/public/work.html +1 -1
- package/modules/hall-ui/records/home-city.md +189 -0
- package/modules/hall-ui/records/loader.md +1 -1
- package/modules/hall-ui/records/market-watch-settings.md +16 -3
- package/modules/hall-ui/records/market-watch.md +3 -1
- package/modules/lifecycle/home-city.js +388 -0
- package/modules/lifecycle/home-gallery.js +320 -0
- package/modules/lifecycle/migrations/lifecycle_020_work_appreciations.sql +36 -0
- package/modules/lifecycle/module.json +2 -0
- package/modules/lifecycle/routes/home-city.js +29 -0
- package/modules/lifecycle/routes/home-gallery.js +37 -0
- package/modules/market-watch/routes/settings.js +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +38 -0
- package/scripts/bongos/leak-scan-allowlist.js +8 -0
- package/scripts/hall-preview/README.md +1 -0
- package/scripts/hall-preview/server.js +10 -0
- package/src/bongos/serve-internal.js +17 -3
- package/src/module-api.js +1 -1
- package/tests/claim_from_task_and_home.mjs +8 -43
- package/tests/hall_board_world.mjs +10 -12
- package/tests/hall_builders_page_pass.mjs +18 -27
- package/tests/hall_home_ships_bounded.mjs +4 -3
- package/tests/hall_loading.mjs +1 -1
- package/tests/hall_nav.mjs +11 -4
- package/tests/hall_page_gate_map.mjs +7 -4
- package/tests/home_bounded_reads.mjs +16 -31
- package/tests/home_city.mjs +580 -0
- package/tests/home_city_widget.mjs +260 -0
- package/tests/home_gallery.mjs +255 -0
- package/tests/home_gallery_widget.mjs +220 -0
- package/tests/home_studio.mjs +169 -0
- package/tests/home_studio_widget.mjs +157 -0
- package/tests/leaderboard_roles.mjs +3 -2
- package/tests/market_watch_pages.mjs +162 -0
- package/tests/nav_permission_atoms.mjs +2 -2
- package/modules/hall-ui/public/market-watch-settings.html +0 -115
- package/modules/hall-ui/public/market-watch-settings.states.json +0 -14
|
@@ -86,16 +86,10 @@ await test('task.html\'s cache-bust moved for task.js', () => {
|
|
|
86
86
|
assert.match(taskHtml, /task\.js\?v=2026-09-28-taskclaim/);
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
-
// == Home
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
// 1,500-line fitness ratchet (hall-ui/CLAUDE.md — growth goes in the
|
|
94
|
-
// lib/renderer, not the page).
|
|
95
|
-
assert.match(hallRenderJs, /function wireNextUpClaim\(\)/);
|
|
96
|
-
assert.match(hallRenderJs, /makeClaimAction\(\{/);
|
|
97
|
-
assert.match(hallRenderJs, /window\.OTBClaimAction/);
|
|
98
|
-
});
|
|
89
|
+
// == Home ====================================================================
|
|
90
|
+
// task 1005054: Home's "Next up" rows left with the "Your work" card (the project
|
|
91
|
+
// city took its place), so Home carries no claim button and no claim action.
|
|
92
|
+
// Claiming lives on the task page above and on the Work board.
|
|
99
93
|
|
|
100
94
|
await test('the shared postJSON carries { status, data } — the contract claim-action.js reads', () => {
|
|
101
95
|
const m = buildersJs.match(/async function postJSON\(path, body\) \{[\s\S]*?\n {2}\}/);
|
|
@@ -103,46 +97,17 @@ await test('the shared postJSON carries { status, data } — the contract claim-
|
|
|
103
97
|
assert.match(m[0], /err\.data\s*=/, 'postJSON must set err.data or every refusal here falls through to the generic line');
|
|
104
98
|
});
|
|
105
99
|
|
|
106
|
-
await test('
|
|
107
|
-
const m = buildersJs.match(/list\.innerHTML = rows\.map\(\(t\) => \{[\s\S]*?\n {6}\}\)\.join\(''\);/);
|
|
108
|
-
assert.ok(m, 'loadNextUp row template not found');
|
|
109
|
-
assert.match(m[0], /class="[^"]*next-up__claim[^"]*"/);
|
|
110
|
-
assert.match(m[0], /data-task-id="\$\{escapeHtml\(String\(t\.id\)\)\}"/);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
await test('the Next-up Claim button routes through claimAction.claim, not a raw POST', () => {
|
|
114
|
-
const m = hallRenderJs.match(/function wireNextUpClaim\(\) \{[\s\S]*?\n {2}\}/);
|
|
115
|
-
assert.ok(m, 'wireNextUpClaim not found');
|
|
116
|
-
assert.match(m[0], /\$\('#home-next-list'\)\?\.addEventListener\('click'/);
|
|
117
|
-
assert.match(m[0], /claimAction\.claim\(taskId\)/);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
await test('builders.js wires the Next-up claim action rather than hand-rolling it', () => {
|
|
100
|
+
await test('Home hand-rolls no claim: no POST /claims, no leftover Next-up wiring', () => {
|
|
121
101
|
assert.doesNotMatch(buildersJs, /postJSON\(\s*`\$\{API\}\/claims/);
|
|
122
|
-
assert.
|
|
102
|
+
assert.doesNotMatch(buildersJs + hallRenderJs, /wireNextUpClaim|home-next-list|next-up__claim/);
|
|
103
|
+
assert.doesNotMatch(indexHtml, /claim-action\.js/, 'Home has no claim button, so it loads no claim action');
|
|
123
104
|
});
|
|
124
105
|
|
|
125
|
-
await test('the
|
|
126
|
-
// Not a second copy of fitness.js's own gate (scripts/bongos/fitness-ratchets.js)
|
|
127
|
-
// — this is the reason wireNextUpClaim moved to hall-render.js in the first
|
|
128
|
-
// place (see its own comment): a regression here explains WHY that file grew,
|
|
129
|
-
// not just THAT some file grew.
|
|
106
|
+
await test('the page halves stay under the 1,500-line ratchet', () => {
|
|
130
107
|
for (const [name, src] of [['builders.js', buildersJs], ['hall-render.js', hallRenderJs]]) {
|
|
131
108
|
const lines = src.split('\n').length;
|
|
132
109
|
assert.ok(lines <= 1500, `${name} is ${lines} lines — over the fitness ratchet`);
|
|
133
110
|
}
|
|
134
111
|
});
|
|
135
112
|
|
|
136
|
-
await test('index.html loads claim-action.js before builders.js', () => {
|
|
137
|
-
const shared = indexHtml.indexOf('claim-action.js');
|
|
138
|
-
const after = indexHtml.indexOf('builders.js?v=');
|
|
139
|
-
assert.ok(shared > -1, 'index.html must load claim-action.js');
|
|
140
|
-
assert.ok(after > shared, 'builders.js must load AFTER claim-action.js');
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
await test('index.html\'s cache-bust moved for builders.js', () => {
|
|
144
|
-
assert.doesNotMatch(indexHtml, /builders\.js\?v=2026-06-27-r75/, 'the old bust is still there');
|
|
145
|
-
assert.match(indexHtml, /builders\.js\?v=2026-09-28-nextupclaim/);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
113
|
summary();
|
|
@@ -303,13 +303,14 @@ test('the system notices are inbox LINES, first in the card, and counted by its
|
|
|
303
303
|
}
|
|
304
304
|
});
|
|
305
305
|
|
|
306
|
-
test('the greeting reads /me
|
|
307
|
-
|
|
306
|
+
test('the greeting reads /me only; the version tab and its progress read left Home', () => {
|
|
307
|
+
// task 1005054: the version tab left with the stats strip, so Home no longer
|
|
308
|
+
// fetches /public/progress; the project city (home-city.js) draws how far it is.
|
|
309
|
+
assert.doesNotMatch(buildersJs, /\/public\/progress/, 'Home no longer fetches the public progress');
|
|
308
310
|
assert.doesNotMatch(buildersJs, /getJSON\(`\$\{API\}\/tasks\/claimable`/,
|
|
309
311
|
'no unbounded claimable fetch on Home');
|
|
310
|
-
assert.
|
|
311
|
-
assert.
|
|
312
|
-
assert.equal((buildersJs.match(/renderGreeting\(\)/g) || []).length, 2, 'rendered after /me and after progress — each as it lands');
|
|
312
|
+
assert.doesNotMatch(buildersJs, /lastProgressState|lastClaimableCount/, 'the greeting needs neither the progress nor the claimable count');
|
|
313
|
+
assert.equal((buildersJs.match(/renderGreeting\(\)/g) || []).length, 1, 'rendered once, as soon as /me answers');
|
|
313
314
|
});
|
|
314
315
|
|
|
315
316
|
// Boot hall-render.js against a stub DOM and a ctx of getters (the way
|
|
@@ -368,16 +369,13 @@ const ME = {
|
|
|
368
369
|
const LEADERBOARD = [{ github_login: 'a', total_credits: 20000 }, { github_login: 'percy', total_credits: 14441 }, { github_login: 'b', total_credits: 3 }, { github_login: 'c', total_credits: 1 }];
|
|
369
370
|
const PROGRESS = { progress: [{ version_id: 'BONGOS-V1', status: 'building', total_count: 605, percent_complete: 75 }, { version_id: 'V2', status: 'planning', total_count: 9000, percent_complete: 0 }] };
|
|
370
371
|
|
|
371
|
-
test('RUN: the greeting names the builder, and
|
|
372
|
+
test('RUN: the greeting names the builder, and writes nothing else', () => {
|
|
373
|
+
// task 1005054: the version tab left Home with the stats strip; how far the
|
|
374
|
+
// project has come is the city under the greeting (home-city.js).
|
|
372
375
|
const { api, el } = bootHallRender({ me: ME, progress: PROGRESS });
|
|
373
376
|
api.renderGreeting();
|
|
374
377
|
assert.match(el('hall-h1').textContent, /^Good (morning|afternoon|evening), Percy Jackson$/);
|
|
375
|
-
assert.equal(el('home-version
|
|
376
|
-
assert.equal(el('home-version-pct').textContent, '75% complete');
|
|
377
|
-
assert.equal(el('home-version').hidden, false);
|
|
378
|
-
const none = bootHallRender({ me: ME });
|
|
379
|
-
none.api.renderGreeting();
|
|
380
|
-
assert.equal(none.el('home-version').hidden, true, 'no progress yet: no tab');
|
|
378
|
+
assert.equal(el('home-version').textContent || '', '', 'no version tab is drawn');
|
|
381
379
|
});
|
|
382
380
|
|
|
383
381
|
test('RUN: the Standing card carries the three facts, the place as "2nd of 4", and no banner', () => {
|
|
@@ -117,42 +117,33 @@ function renderLandingHtml(joinability) {
|
|
|
117
117
|
return html();
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
|
|
120
|
+
// The sign-in redesign made the sign-in button the card's one primary control
|
|
121
|
+
// in EVERY mode; only the door beneath it depends on joinability.
|
|
122
|
+
const SIGNIN = /class="landing__btn landing__btn--block" id="landing-signin" href="\/api\/bongos\/auth\/web\/start"/;
|
|
123
|
+
const signInCount = (html) => (html.match(/href="\/api\/bongos\/auth\/web\/start"/g) || []).length;
|
|
124
|
+
|
|
125
|
+
test('A: the apply door leads with sign-in and folds the request form under it', () => {
|
|
121
126
|
const html = renderLandingHtml('apply');
|
|
122
|
-
assert.match(html,
|
|
123
|
-
assert.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
);
|
|
128
|
-
// The whole point of the change: the footnote link is GONE from this door, so
|
|
129
|
-
// there is exactly one sign-in path and it is the button.
|
|
130
|
-
assert.ok(
|
|
131
|
-
!/Already a member\? <a /.test(html),
|
|
132
|
-
'the apply door no longer repeats sign-in as a prose link under the form',
|
|
133
|
-
);
|
|
134
|
-
assert.match(html, /We store only your GitHub login/, 'the privacy sentence survives the note');
|
|
135
|
-
// Two controls total: request access, and sign in. Not three.
|
|
136
|
-
assert.equal(countButtons(html), 2, 'the apply door carries the request button and the sign-in button');
|
|
127
|
+
assert.match(html, SIGNIN, 'sign-in is a real primary button');
|
|
128
|
+
assert.equal(signInCount(html), 1, 'exactly one sign-in path — never a second prose link');
|
|
129
|
+
assert.match(html, /<details class="landing__disclosure">[\s\S]*id="access-request-form"/, 'the request form sits behind a disclosure');
|
|
130
|
+
assert.ok(html.indexOf('landing-signin') < html.indexOf('access-request-form'), 'sign-in comes first');
|
|
131
|
+
assert.equal(countButtons(html), 2, 'two controls: sign in, and request access');
|
|
137
132
|
});
|
|
138
133
|
|
|
139
|
-
test('A: the
|
|
134
|
+
test('A: the open and invite-only doors carry one sign-in control and no form', () => {
|
|
140
135
|
for (const mode of ['open', 'invite_only']) {
|
|
141
136
|
const html = renderLandingHtml(mode);
|
|
142
|
-
assert.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
);
|
|
146
|
-
assert.equal(countButtons(html), 1, `the ${mode} door carries exactly one sign-in control`);
|
|
147
|
-
assert.match(html, /Already a member\? <a /, `the ${mode} door keeps its prose note for returning builders`);
|
|
137
|
+
assert.match(html, SIGNIN, `the ${mode} door signs in with the same button`);
|
|
138
|
+
assert.equal(signInCount(html), 1, `the ${mode} door has exactly one sign-in path`);
|
|
139
|
+
assert.ok(!/access-request-form/.test(html), `the ${mode} door has nothing to apply with`);
|
|
140
|
+
assert.equal(countButtons(html), 1, `the ${mode} door carries exactly one control`);
|
|
148
141
|
}
|
|
149
142
|
});
|
|
150
143
|
|
|
151
144
|
test('A: an older branding pack with no joinability falls back to the apply door', () => {
|
|
152
|
-
// joinabilityMode() treats anything but open/invite_only as 'apply'
|
|
153
|
-
|
|
154
|
-
// never sent.
|
|
155
|
-
assert.match(renderLandingHtml(null), /landing__returning/);
|
|
145
|
+
// joinabilityMode() treats anything but open/invite_only as 'apply'.
|
|
146
|
+
assert.match(renderLandingHtml(null), /id="access-request-form"/);
|
|
156
147
|
});
|
|
157
148
|
|
|
158
149
|
test('A: the quiet fill is a defined variant, not an inline colour', () => {
|
|
@@ -122,14 +122,15 @@ function loadRender({ view = 'home' } = {}) {
|
|
|
122
122
|
|
|
123
123
|
const homePage = (total, firstId = 3000) => ({ ships: [ship(firstId), ship(firstId - 1)], total });
|
|
124
124
|
|
|
125
|
-
test('Home: the
|
|
125
|
+
test('Home: the Standing chip\'s count comes from the small page, with no ledger fetch', async () => {
|
|
126
|
+
// task 1005054: the Shipped tile and the five-row table left Home with the stats
|
|
127
|
+
// strip and "Your work"; the small page still feeds the Standing chip's count.
|
|
126
128
|
const { r, el, fetched } = loadRender();
|
|
127
129
|
assert.equal(r.HOME_SHIPS_LIMIT, 5);
|
|
128
130
|
r.setRecentShips(homePage(3000));
|
|
129
131
|
await new Promise((done) => setTimeout(done, 0));
|
|
130
132
|
assert.deepEqual(fetched, [], 'Home never asks for the full history');
|
|
131
|
-
assert.equal(el('
|
|
132
|
-
assert.match(el('home-recent-table tbody').innerHTML, /#3000[\s\S]*#2999/);
|
|
133
|
+
assert.equal(el('standing-shipped').textContent, '3000', 'the chip shows the server total, not rows held');
|
|
133
134
|
assert.equal(el('recent-ships tbody').innerHTML, '', 'the ledger is untouched until its view opens');
|
|
134
135
|
});
|
|
135
136
|
|
package/tests/hall_loading.mjs
CHANGED
|
@@ -83,7 +83,7 @@ test('the page loaders step aside by themselves, and never show without :has()',
|
|
|
83
83
|
assert.match(STYLE, /\.hall-loading--wait, \.hall-loading--zones \{ display: none; \}/);
|
|
84
84
|
assert.match(STYLE, /@supports selector\(:has\(a\)\) \{\s*\.hall-loading--wait:not\(:has\(~ :not\(\[hidden\], script, template, style\)\)\) \{ display: flex; \}\s*\.hall-loading--zones:not\(:has\(~ :not\(\[hidden\]\) > :not\(\[hidden\], script, template, style\)\)\) \{ display: flex; \}\s*\}/);
|
|
85
85
|
// A page whose sections all start [hidden] carries the --wait loader FIRST in its main.
|
|
86
|
-
for (const page of ['board-room', 'deploy', 'fleet', 'gate', 'government', '
|
|
86
|
+
for (const page of ['board-room', 'deploy', 'fleet', 'gate', 'government', 'project-settings', 'sessions']) {
|
|
87
87
|
const html = read(`${page}.html`);
|
|
88
88
|
const main = html.slice(html.indexOf('<main id="hall-main">'));
|
|
89
89
|
const first = main.replace(/<main id="hall-main">\s*(<!--[\s\S]*?-->\s*)*/, '');
|
package/tests/hall_nav.mjs
CHANGED
|
@@ -321,9 +321,16 @@ test('sidebar groups read by audience, in order', () => {
|
|
|
321
321
|
// the steering surfaces because that is what it is — the owner's third release gate.
|
|
322
322
|
// 'fleet' (task 1001949) follows it at the metic floor of `provisioning.fleet.manage`:
|
|
323
323
|
// what each hosted project costs is an operator surface, like the rest of this group.
|
|
324
|
-
// 'market-watch-settings' (task 1004938)
|
|
325
|
-
//
|
|
326
|
-
|
|
324
|
+
// 'market-watch-settings' (task 1004938) LEFT the group in task 1005045: the owner saw
|
|
325
|
+
// "Market Watch" twice and had to guess which was which (owner decision 2026-11-03,
|
|
326
|
+
// idea 1001310). The settings are an owner-only Settings tab on /market-watch now.
|
|
327
|
+
assert.deepEqual(nav[4].items.map((i) => i.id), ['watch', 'gate', 'sessions', 'project-settings', 'deploy', 'fleet', 'government']);
|
|
328
|
+
// task 1005045: and no label is left twice anywhere in the sidebar, so the next pair
|
|
329
|
+
// fails here instead of in front of the owner.
|
|
330
|
+
const labels = [...boot().sidebarHtml.matchAll(/<a class="nav-item" [^>]*>[\s\S]*?<span class="nav-item__label">([^<]*)<\/span>/g)].map((m) => m[1]);
|
|
331
|
+
assert.ok(labels.includes('Market Watch'), 'the labels were actually read');
|
|
332
|
+
assert.deepEqual(labels.filter((l, i) => labels.indexOf(l) !== i), [], 'every sidebar label is said once');
|
|
333
|
+
assert.ok(!nav.flatMap((g) => g.items).some((i) => i.href === '/market-watch-settings'), 'no item points at the retired settings page');
|
|
327
334
|
});
|
|
328
335
|
|
|
329
336
|
test('Economy lives in Docs and points at the reward-schedule page', () => {
|
|
@@ -755,7 +762,7 @@ test('no glyph path data reads as an IP address to the release leak scan', () =>
|
|
|
755
762
|
// block (task 1004713). These drive the real shell and the real gov-positions.js.
|
|
756
763
|
|
|
757
764
|
const tick = () => new Promise((resolve) => setImmediate(resolve));
|
|
758
|
-
const GOVERNING = ['board-room', 'city', 'watch', 'gate', 'sessions', 'project-settings', 'deploy', 'fleet', '
|
|
765
|
+
const GOVERNING = ['board-room', 'city', 'watch', 'gate', 'sessions', 'project-settings', 'deploy', 'fleet', 'government'];
|
|
759
766
|
const lead = (building, title) => ({ kind: 'lead', seat: 'lead:' + building, building, title });
|
|
760
767
|
const block = (held) => ({ held, title: held.length ? held[0].title : null, has_position: held.length > 0 });
|
|
761
768
|
|
|
@@ -56,10 +56,11 @@ const ORACLE = {
|
|
|
56
56
|
// settings. Every read behind it is the viewer's own (GET /me, /me/* prefs,
|
|
57
57
|
// /specialities) and requireBuilder, so sign-in is the whole gate.
|
|
58
58
|
'/builders/my-agents': 'requireBuilderPage',
|
|
59
|
-
// task
|
|
60
|
-
// owner-only
|
|
61
|
-
//
|
|
62
|
-
|
|
59
|
+
// Gone (task 1005045, owner decision 2026-11-03): the owner's Market Watch settings
|
|
60
|
+
// (task 1004938) are an owner-only Settings tab on /market-watch now, and the old URL
|
|
61
|
+
// 302s to /market-watch#settings before any gate runs. A redirect is not a page; the
|
|
62
|
+
// four settings routes keep their own owner-only wall (tests/market_watch_settings.mjs).
|
|
63
|
+
'/builders/market-watch-settings': null,
|
|
63
64
|
// task 1004939: the Market Watch home and tracked-item pages. Every member of the
|
|
64
65
|
// project reads them; both reads are requireBuilder, so sign-in is the whole gate.
|
|
65
66
|
'/builders/market-watch': 'requireBuilderPage',
|
|
@@ -347,6 +348,8 @@ const GONE_PAGES = {
|
|
|
347
348
|
// Task 1003444 — the owner deleted the hall's cross-project directory; the hub
|
|
348
349
|
// community page (task 1003363) is its home, and /people redirects to /roster.
|
|
349
350
|
'/builders/people': 'task 1003444 — the hall has no cross-project directory; /people redirects to /roster',
|
|
351
|
+
// Task 1005045 — one sidebar item for Market Watch: its settings are a tab on the page.
|
|
352
|
+
'/builders/market-watch-settings': 'task 1005045 — the settings are the Settings tab on /market-watch; the old URL redirects there',
|
|
350
353
|
};
|
|
351
354
|
|
|
352
355
|
test('a row that is null because the page left the hall serves no page', () => {
|
|
@@ -28,7 +28,6 @@ const require = createRequire(import.meta.url);
|
|
|
28
28
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
29
29
|
const read = (rel) => fs.readFileSync(path.join(ROOT, rel), 'utf8');
|
|
30
30
|
const BUILDERS_JS = read('modules/hall-ui/public/builders.js');
|
|
31
|
-
const WIDGETS_JS = read('modules/hall-ui/public/hall-widgets.js');
|
|
32
31
|
const ROUTE = read('modules/lifecycle/routes/tasks.js');
|
|
33
32
|
const DB_TASKS = read('modules/lifecycle/db-tasks.js');
|
|
34
33
|
|
|
@@ -48,26 +47,24 @@ await test('no Home read downloads a feed and slices it in the browser', () => {
|
|
|
48
47
|
// DISPLAYED is fine; narrowing what was FETCHED is the bug, and the two look
|
|
49
48
|
// identical at the call site — which is why this is a test and not a code review.
|
|
50
49
|
const src = noComments(BUILDERS_JS);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
assert.ok(url, `${name} must issue a request`);
|
|
58
|
-
assert.match(url[0], /[?&]limit=\d+/, `${name} must ask the server for its page size`);
|
|
59
|
-
}
|
|
50
|
+
const from = src.indexOf('function loadTriageInbox(');
|
|
51
|
+
assert.ok(from > 0, 'loadTriageInbox must exist');
|
|
52
|
+
const body = src.slice(from, src.indexOf('\n }\n', from));
|
|
53
|
+
const url = body.match(/\$\{API\}\/[^`'"]+/);
|
|
54
|
+
assert.ok(url, 'loadTriageInbox must issue a request');
|
|
55
|
+
assert.match(url[0], /[?&]limit=\d+/, 'loadTriageInbox must ask the server for its page size');
|
|
60
56
|
});
|
|
61
57
|
|
|
62
|
-
await test('the
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
//
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
assert.
|
|
70
|
-
assert.doesNotMatch(
|
|
58
|
+
await test('the project city asks for ONE weekly summary, and the Next-up read left with its card', () => {
|
|
59
|
+
// task 1005054: Home's body is the project city. It draws from one bounded read
|
|
60
|
+
// the server counts per week (modules/lifecycle/home-city.js), never a task feed;
|
|
61
|
+
// the Next-up card and its GET /tasks/claimable read left Home with "Your work".
|
|
62
|
+
const CITY_JS = noComments(read('modules/hall-ui/public/home-city.js'));
|
|
63
|
+
const reads = [...CITY_JS.matchAll(/api\.request\('GET',\s*`([^`]+)`/g)].map((m) => m[1]);
|
|
64
|
+
assert.deepEqual(reads, ['${API}/home/city?weeks=${WEEKS}'], 'one read, asked for its window');
|
|
65
|
+
assert.doesNotMatch(CITY_JS, /\/tasks|claimable|recent-ships/, 'the city never downloads a feed');
|
|
66
|
+
assert.doesNotMatch(noComments(BUILDERS_JS), /function loadNextUp\(|tasks\/claimable/, 'Home no longer reads the claimable feed');
|
|
67
|
+
assert.match(BUILDERS_JS, /\$\{API\}\/inbox\?limit=5/, 'the triage card still draws five');
|
|
71
68
|
});
|
|
72
69
|
|
|
73
70
|
// ---------------------------------------------------------------------------
|
|
@@ -153,16 +150,4 @@ await test('the limit is applied AFTER the JS post-filters, not as a SQL LIMIT',
|
|
|
153
150
|
assert.match(routeBody, /feed\.slice\(0, claimLimit\)/, 'the slice happens on the finished feed');
|
|
154
151
|
});
|
|
155
152
|
|
|
156
|
-
// ---------------------------------------------------------------------------
|
|
157
|
-
// 4. what the reader sees
|
|
158
|
-
// ---------------------------------------------------------------------------
|
|
159
|
-
|
|
160
|
-
await test('the foot names the queue the two rows came off', () => {
|
|
161
|
-
assert.match(WIDGETS_JS, /id="home-next-foot"/, 'the link has an id to write into');
|
|
162
|
-
assert.match(BUILDERS_JS, /See all \$\{fmtNum\(total\)\} ready work/);
|
|
163
|
-
// Silent rather than wrong when the count is missing or not larger than what is
|
|
164
|
-
// shown: "See all 2" under two rows is noise, and a guessed number is worse.
|
|
165
|
-
assert.match(BUILDERS_JS, /Number\.isFinite\(total\) && total > rows\.length/);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
153
|
summary();
|