@bongos/core 1.24.36 → 1.24.38
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 +92 -87
- package/docs/copy-inventory.md +65 -64
- package/docs/copy-registry.json +170 -173
- package/docs/module-api-changelog.md +4 -0
- package/docs/onboarding/primer.md +2 -0
- package/docs/page-inventory.json +34 -34
- package/docs/page-readings.json +687 -684
- 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 +6 -4
- package/modules/hall-ui/public/deploy.html +2 -2
- package/modules/hall-ui/public/fleet-costs.js +3 -2
- package/modules/hall-ui/public/gate.html +2 -2
- 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 +92 -0
- package/modules/hall-ui/public/money.js +302 -0
- package/modules/hall-ui/public/money.states.json +15 -0
- package/modules/hall-ui/public/my-agents.html +2 -2
- package/modules/hall-ui/public/palette.js +9 -2
- package/modules/hall-ui/public/primer-live.js +18 -105
- package/modules/hall-ui/public/primer.html +7 -8
- package/modules/hall-ui/public/primer.js +16 -18
- package/modules/hall-ui/public/profile.html +2 -2
- package/modules/hall-ui/public/project-settings.css +2 -1
- package/modules/hall-ui/public/project-settings.html +6 -16
- package/modules/hall-ui/public/project-settings.js +2 -142
- package/modules/hall-ui/public/roadmap.html +2 -2
- package/modules/hall-ui/public/roster.html +2 -2
- package/modules/hall-ui/public/sessions.html +2 -2
- package/modules/hall-ui/public/settings.html +2 -2
- package/modules/hall-ui/public/shell.js +113 -94
- 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.html +2 -2
- package/modules/hall-ui/public/work.html +2 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/src/bongos/auth.js +0 -8
- package/src/bongos/serve-internal.js +15 -6
- package/src/module-api.js +1 -1
- package/tests/auth_resolve_failure.mjs +5 -4
- package/tests/craft_mode_nav.mjs +12 -10
- package/tests/fleet_cost_view.mjs +2 -18
- package/tests/goal_map_page.mjs +5 -2
- package/tests/hall_loading.mjs +1 -1
- package/tests/hall_money_page.mjs +272 -0
- package/tests/hall_nav.mjs +23 -15
- package/tests/hall_oversight_world.mjs +1 -1
- package/tests/hall_page_gate_map.mjs +3 -2
- package/tests/hall_reading_room.mjs +21 -62
- package/tests/nav_permission_atoms.mjs +1 -1
- package/modules/hall-ui/public/fleet.html +0 -69
- package/modules/hall-ui/public/fleet.js +0 -41
- package/modules/hall-ui/public/fleet.states.json +0 -14
package/tests/hall_nav.mjs
CHANGED
|
@@ -243,7 +243,10 @@ test('sidebar groups read by audience, in order', () => {
|
|
|
243
243
|
// 'Archon' → 'Governance' in ADR 0157 (the group's pages opened to Metic, so a header
|
|
244
244
|
// naming the top rank was wrong), then → 'Government' by ADR 0174: a header
|
|
245
245
|
// reading "Archon" over items a Metic can open would misdescribe the ladder.
|
|
246
|
-
|
|
246
|
+
// task 1005071 (owner decision 2026-11-04, from the sitemap audit): every group is
|
|
247
|
+
// labelled, and the old unlabeled project group split into Work, Make and Govern.
|
|
248
|
+
// Docs became Help; Government became Run the project.
|
|
249
|
+
assert.deepEqual(nav.map((g) => g.label), ['Work', 'Make', 'Community', 'You', 'Govern', 'Run the project', 'Help']);
|
|
247
250
|
// task 1003113 (R02 of goal 1000074): 'copy-desk' joins the unlabeled group,
|
|
248
251
|
// after Tasks. It belongs with the project's own work and not in Docs — it is a
|
|
249
252
|
// QUEUE somebody acts on, not reference material — and not in You or Community,
|
|
@@ -285,12 +288,15 @@ test('sidebar groups read by audience, in order', () => {
|
|
|
285
288
|
// task 1004939 (goal 1000137): 'market-watch' joins last — the project's scouts on its
|
|
286
289
|
// market, so its subject is the project. Module-gated: it is dropped wherever the
|
|
287
290
|
// default-off market-watch module is not enabled (this boot reports no module state).
|
|
288
|
-
|
|
289
|
-
|
|
291
|
+
// task 1005071: the project group split three ways. Work is the project's state;
|
|
292
|
+
// Make is where things get made; Govern is the board's rooms.
|
|
293
|
+
assert.deepEqual(nav[0].items.map((i) => i.id), ['home', 'work', 'goals', 'goal-map', 'roadmap', 'blockers', 'thinking'],
|
|
294
|
+
'Work = the project itself (task 1005071 order)');
|
|
295
|
+
assert.deepEqual(nav[1].items.map((i) => i.id), ['studio', 'market-watch'], 'Make = where things get made');
|
|
290
296
|
// task 1004685 (the living-city prototype, round-3 board 6): 'my-agents' joins You —
|
|
291
297
|
// how your own Claude works for you, the personal half of "the city steers
|
|
292
298
|
// everyone, My agents steers yours". Its subject is the viewer's own agents.
|
|
293
|
-
assert.deepEqual(nav[
|
|
299
|
+
assert.deepEqual(nav[3].items.map((i) => i.id), ['standing', 'profile', 'my-agents'], 'You = your own numbers and your own agents');
|
|
294
300
|
// task 1003444: 'people' LEFT the group — the owner deleted the hall's
|
|
295
301
|
// cross-project directory (ADR 0337 D5); its home is the hub, and /people
|
|
296
302
|
// redirects to /roster. Builders is the hall's directory now.
|
|
@@ -305,29 +311,31 @@ test('sidebar groups read by audience, in order', () => {
|
|
|
305
311
|
// 'leaderboard') and is now a real page at /roster labelled Builders. That is
|
|
306
312
|
// what made this group read as one real page plus a link into the home page.
|
|
307
313
|
assert.deepEqual(nav[2].items.map((i) => i.id), ['collab', 'roster'], 'Community = everyone else, in this project');
|
|
314
|
+
assert.deepEqual(nav[4].items.map((i) => i.id), ['city', 'board-room'], 'Govern = the board\'s rooms, the city first');
|
|
308
315
|
assert.ok(!nav.flatMap((g) => g.items).some((i) => /^\/people\b/.test(i.href)), 'no nav item points at the deleted /people');
|
|
309
316
|
// task 1004473: 'commands' — every skill and CLI verb, read live — is reference
|
|
310
317
|
// material that reads the same for every builder, so it joins Docs.
|
|
311
318
|
// task 1005046 (owner decision 2026-11-03, idea 1001311): ONE reading room. Ranks,
|
|
312
319
|
// Economy and Diagrams explained what the Primer explains; they are its chapters
|
|
313
320
|
// now, so Docs is the Primer and the four references that are not a document.
|
|
314
|
-
assert.deepEqual(nav[
|
|
321
|
+
assert.deepEqual(nav[6].items.map((i) => i.id),
|
|
315
322
|
['primer', 'commands', 'atlas', 'modules', 'apidocs'], 'Docs = reference material');
|
|
316
323
|
// 'government' (the Permissions tab, BV1.R113) is the group's one archon-gated item.
|
|
317
324
|
// 'project-settings' joins the Government group at the metic floor (task 1003280,
|
|
318
325
|
// goal 1000072) — it sits before 'government', which is the one archon-only item.
|
|
319
326
|
// 'board-room' LED this group from the ADR 0266 carve until task 1003744 moved
|
|
320
|
-
// it to the project group — see the note on nav[
|
|
327
|
+
// it to the project group — see the note on Govern (nav[4]). What is left is rank surface
|
|
321
328
|
// throughout, which is what lets the group keep its metic head honestly.
|
|
322
329
|
// 'deploy' (task 1003159, ADR 0293) is the SECOND archon item, beside 'government':
|
|
323
330
|
// its gate is the `core.pin.move` atom, archon-floor and system:true. It sits with
|
|
324
331
|
// the steering surfaces because that is what it is — the owner's third release gate.
|
|
325
|
-
// '
|
|
326
|
-
//
|
|
332
|
+
// 'money' (task 1005047, owner decision 2026-11-03) follows 'project-settings' at the
|
|
333
|
+
// same government floor: the one money page — spend and payouts, the treasury and the
|
|
334
|
+
// hosting bills. It replaced 'fleet' (task 1001949), now its #hosting section.
|
|
327
335
|
// 'market-watch-settings' (task 1004938) LEFT the group in task 1005045: the owner saw
|
|
328
336
|
// "Market Watch" twice and had to guess which was which (owner decision 2026-11-03,
|
|
329
337
|
// idea 1001310). The settings are an owner-only Settings tab on /market-watch now.
|
|
330
|
-
assert.deepEqual(nav[
|
|
338
|
+
assert.deepEqual(nav[5].items.map((i) => i.id), ['watch', 'gate', 'sessions', 'project-settings', 'money', 'deploy', 'government']);
|
|
331
339
|
// task 1005045: and no label is left twice anywhere in the sidebar, so the next pair
|
|
332
340
|
// fails here instead of in front of the owner.
|
|
333
341
|
const labels = [...boot().sidebarHtml.matchAll(/<a class="nav-item" [^>]*>[\s\S]*?<span class="nav-item__label">([^<]*)<\/span>/g)].map((m) => m[1]);
|
|
@@ -354,7 +362,7 @@ test('Ranks, Economy and Diagrams are Primer chapters, not pages', () => {
|
|
|
354
362
|
for (const f of ['ranks.html', 'drachmae.html', 'diagrams.html']) {
|
|
355
363
|
assert.ok(!fs.existsSync(path.join(HALL, f)), `${f} left the hall`);
|
|
356
364
|
}
|
|
357
|
-
const primer = nav.find((g) => g.label === '
|
|
365
|
+
const primer = nav.find((g) => g.label === 'Help').items.find((i) => i.id === 'primer');
|
|
358
366
|
assert.equal(primer.href, '/primer', 'the Primer is the reading room the three became');
|
|
359
367
|
});
|
|
360
368
|
|
|
@@ -563,11 +571,11 @@ test('collapsible groups carry a stable storage key (collapse persistence)', ()
|
|
|
563
571
|
// hall-nav-collapsed storage key, so any builder who had collapsed the old "archon"
|
|
564
572
|
// group gets the new one expanded once — cosmetic, and preferable to keeping a stale
|
|
565
573
|
// slug that no longer matches the visible label.
|
|
566
|
-
|
|
574
|
+
// task 1005071: a label with spaces hyphenates, so the key stays a valid id.
|
|
575
|
+
for (const g of ['work', 'make', 'community', 'you', 'govern', 'run-the-project', 'help']) {
|
|
567
576
|
assert.match(html, new RegExp(`data-group="${g}"`), `${g} is collapsible (hall-nav-collapsed key)`);
|
|
568
577
|
}
|
|
569
|
-
assert.ok(!/
|
|
570
|
-
'the unlabeled project group stays always-expanded');
|
|
578
|
+
assert.ok(!/aria-controls="[^"]* /.test(html), 'no aria-controls id carries a space');
|
|
571
579
|
});
|
|
572
580
|
|
|
573
581
|
// ── a nav item can carry a COUNT badge (task 1003737) ───────────────────────
|
|
@@ -779,7 +787,7 @@ test('no glyph path data reads as an IP address to the release leak scan', () =>
|
|
|
779
787
|
// block (task 1004713). These drive the real shell and the real gov-positions.js.
|
|
780
788
|
|
|
781
789
|
const tick = () => new Promise((resolve) => setImmediate(resolve));
|
|
782
|
-
const GOVERNING = ['
|
|
790
|
+
const GOVERNING = ['city', 'board-room', 'watch', 'gate', 'sessions', 'project-settings', 'money', 'deploy', 'government'];
|
|
783
791
|
const lead = (building, title) => ({ kind: 'lead', seat: 'lead:' + building, building, title });
|
|
784
792
|
const block = (held) => ({ held, title: held.length ? held[0].title : null, has_position: held.length > 0 });
|
|
785
793
|
|
|
@@ -798,7 +806,7 @@ test('POSITION: exactly the governing surfaces carry the flag — the City, the
|
|
|
798
806
|
const html = boot().sidebarHtml;
|
|
799
807
|
const flagged = [...html.matchAll(/id="nav-([^"]+)"[^>]*data-position="1"/g)].map((m) => m[1]);
|
|
800
808
|
assert.deepEqual(flagged, GOVERNING);
|
|
801
|
-
assert.match(html, /class="nav-group__head" data-gate="government" data-position="1" data-group="
|
|
809
|
+
assert.match(html, /class="nav-group__head" data-gate="government" data-position="1" data-group="run-the-project"/,
|
|
802
810
|
'the Government head carries it too, so no label is left over an emptied group');
|
|
803
811
|
const nav = parseNav(html);
|
|
804
812
|
assert.ok(!nav.find((g) => g.label === 'You').items.some((i) => flagged.includes(i.id)), 'your own pages never need a seat');
|
|
@@ -278,7 +278,7 @@ test('RUN: a metric its snapshots never carried is skipped, not drawn as a cliff
|
|
|
278
278
|
|
|
279
279
|
// ---- task 1004829: the government pages read like the rebuilt Home ----------
|
|
280
280
|
|
|
281
|
-
const GOV_PAGES = ['watch', 'gate', 'sessions', 'project-settings', '
|
|
281
|
+
const GOV_PAGES = ['watch', 'gate', 'sessions', 'project-settings', 'money', 'deploy', 'government'];
|
|
282
282
|
|
|
283
283
|
test('every government page head holds only its h1, and no script writes into it', () => {
|
|
284
284
|
// No page shows its head (task 1004836). A sub-line there is a line nobody
|
|
@@ -126,6 +126,9 @@ const ORACLE = {
|
|
|
126
126
|
// its neighbours: a steering surface, metic+ per ADR 0157. Its two WRITES gate
|
|
127
127
|
// independently on `project.settings.manage`, so this row is only about the shell.
|
|
128
128
|
'/builders/project-settings': 'requireGovernmentPage',
|
|
129
|
+
// The one money page (task 1005047): the same floor as project-settings, whose money
|
|
130
|
+
// section it took. Its hosting section's data keeps its own atom on the route.
|
|
131
|
+
'/builders/money': 'requireGovernmentPage',
|
|
129
132
|
// The deploy door (task 1003159, ADR 0293) — the NARROWEST gate in this table, on
|
|
130
133
|
// the `core.pin.move` atom: archon-floor and system:true, the same key its preview
|
|
131
134
|
// and its move both check. Correct for the one page whose button restarts the live
|
|
@@ -133,8 +136,6 @@ const ORACLE = {
|
|
|
133
136
|
// its write on purpose: the preview discloses the exact core version the box serves
|
|
134
137
|
// and how far behind it is, which is a vulnerability window.
|
|
135
138
|
'/builders/deploy': 'requireCorePinPage',
|
|
136
|
-
// The fleet cost page (task 1001949), on the atom its one read checks.
|
|
137
|
-
'/builders/fleet': 'requireFleetPage',
|
|
138
139
|
|
|
139
140
|
// Gone (task 1003444, owner call 2026-09-25 — ADR 0337 D5): the cross-project
|
|
140
141
|
// directory left the hall, and /people now redirects to /roster before this
|
|
@@ -15,10 +15,11 @@
|
|
|
15
15
|
// real h2/h3 slug of docs/onboarding/primer.md, computed by the SAME slugify
|
|
16
16
|
// the page runs — so retitling a chapter fails here, not silently in a
|
|
17
17
|
// browser (the credits diagram was lost that way once).
|
|
18
|
-
// * THE ECONOMY PAGE'S LIVE
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
18
|
+
// * THE ECONOMY PAGE'S LIVE SECTION MOVED WHOLE. The bounty rate card
|
|
19
|
+
// (primer-live.js, from drachmae.js, task 2174) is RUN in a vm against the
|
|
20
|
+
// checked-in fixture: a Loading… line first, the real numbers after, and
|
|
21
|
+
// NOTHING — not an empty panel — when the feed fails. (The treasury beside
|
|
22
|
+
// it went to the money page in task 1005047: tests/hall_money_page.mjs.)
|
|
22
23
|
// * THE SHARED READER the economy doc leaned on (h4, linked headings, nested
|
|
23
24
|
// emphasis) stays pinned: drachmae-and-karma.md is still the full reference.
|
|
24
25
|
//
|
|
@@ -188,7 +189,6 @@ function liftInsertLive({ hash = '', withObserver = true }) {
|
|
|
188
189
|
location: { hash },
|
|
189
190
|
OTBPrimerLive: {
|
|
190
191
|
placeholder: (kind) => ({ kind }),
|
|
191
|
-
fillTreasury: async (s) => { fills.push(s.kind); return true; },
|
|
192
192
|
fillBounty: async (s) => { fills.push(s.kind); return true; },
|
|
193
193
|
},
|
|
194
194
|
};
|
|
@@ -216,7 +216,7 @@ function liftInsertLive({ hash = '', withObserver = true }) {
|
|
|
216
216
|
test('RUN: the live reads wait until the reader comes near, so an ordinary Primer visit costs nothing', async () => {
|
|
217
217
|
const t = liftInsertLive({ hash: '#the-rank-ladder' });
|
|
218
218
|
await t.run();
|
|
219
|
-
assert.deepEqual(t.placed, [['
|
|
219
|
+
assert.deepEqual(t.placed, [['bounty', true]], 'placed at the end of the credits chapter, ahead of the next one (the treasury is on /money since task 1005047)');
|
|
220
220
|
assert.deepEqual(t.fills, [], 'no fetch on load');
|
|
221
221
|
assert.equal(t.observers.length, 1);
|
|
222
222
|
t.observers[0].cb([{ isIntersecting: false }]);
|
|
@@ -224,20 +224,20 @@ test('RUN: the live reads wait until the reader comes near, so an ordinary Prime
|
|
|
224
224
|
t.observers[0].cb([{ isIntersecting: true }]);
|
|
225
225
|
t.observers[0].cb([{ isIntersecting: true }]);
|
|
226
226
|
await Promise.resolve();
|
|
227
|
-
assert.deepEqual(t.fills, ['
|
|
227
|
+
assert.deepEqual(t.fills, ['bounty'], 'one read, once, when the reader approaches');
|
|
228
228
|
assert.equal(t.observers[0].gone, true, 'and the watch is dropped');
|
|
229
229
|
});
|
|
230
230
|
|
|
231
231
|
test('RUN: an old /drachmae link (or a section address) fills at once, so the landing is on the final layout', async () => {
|
|
232
|
-
for (const hash of ['#credits-and-karma', '#
|
|
232
|
+
for (const hash of ['#credits-and-karma', '#security-bounty-rates']) {
|
|
233
233
|
const t = liftInsertLive({ hash });
|
|
234
234
|
await t.run();
|
|
235
|
-
assert.deepEqual(t.fills, ['
|
|
235
|
+
assert.deepEqual(t.fills, ['bounty'], `${hash} reads it before landing`);
|
|
236
236
|
assert.equal(t.observers.length, 0);
|
|
237
237
|
}
|
|
238
238
|
const old = liftInsertLive({ withObserver: false });
|
|
239
239
|
await old.run();
|
|
240
|
-
assert.deepEqual(old.fills, ['
|
|
240
|
+
assert.deepEqual(old.fills, ['bounty'], 'a browser with no observer still gets the section');
|
|
241
241
|
});
|
|
242
242
|
|
|
243
243
|
test('a deep link lands after the article, its live sections and the fonts', () => {
|
|
@@ -251,11 +251,8 @@ test('the states file shoots a redirected deep link, not just the top of the pag
|
|
|
251
251
|
assert.match(states.states.jump.url, /#credits-and-karma$/);
|
|
252
252
|
});
|
|
253
253
|
|
|
254
|
-
// ---- 3. the live
|
|
254
|
+
// ---- 3. the live section, RUN ---------------------------------------------
|
|
255
255
|
|
|
256
|
-
const costSummaryFixture = JSON.parse(
|
|
257
|
-
fs.readFileSync(path.join(ROOT, 'modules', 'ui-design', 'kit', 'fixtures', 'cost-summary.json'), 'utf8'),
|
|
258
|
-
);
|
|
259
256
|
const bountyFixture = JSON.parse(
|
|
260
257
|
fs.readFileSync(path.join(ROOT, 'scripts', 'hall-preview', 'fixtures', 'public__bounty-table.json'), 'utf8'),
|
|
261
258
|
);
|
|
@@ -271,7 +268,7 @@ function fakeSection() {
|
|
|
271
268
|
};
|
|
272
269
|
}
|
|
273
270
|
|
|
274
|
-
async function loadLive({ responses, branding = { currency: { label: 'drachmae' } }
|
|
271
|
+
async function loadLive({ responses, branding = { currency: { label: 'drachmae' } } }) {
|
|
275
272
|
const warnings = [];
|
|
276
273
|
const sandbox = {
|
|
277
274
|
window: { OTB: realOTB(), __BRANDING__: branding },
|
|
@@ -284,21 +281,16 @@ async function loadLive({ responses, branding = { currency: { label: 'drachmae'
|
|
|
284
281
|
return { ok: true, status: 200, json: async () => r };
|
|
285
282
|
},
|
|
286
283
|
};
|
|
287
|
-
if (kit) {
|
|
288
|
-
const kitPath = path.join(HALL, 'hall-kit.js');
|
|
289
|
-
sandbox.window.OTBKit = await import(`file://${kitPath.replace(/\\/g, '/')}`).then((m) => m.default);
|
|
290
|
-
}
|
|
291
284
|
vm.createContext(sandbox);
|
|
292
285
|
vm.runInContext(liveJs, sandbox, { filename: 'primer-live.js' });
|
|
293
286
|
return { live: sandbox.window.OTBPrimerLive, warnings };
|
|
294
287
|
}
|
|
295
288
|
|
|
296
|
-
const TREASURY = '/api/bongos/public/cost-summary';
|
|
297
289
|
const BOUNTY = '/api/bongos/public/bounty-table';
|
|
298
290
|
|
|
299
291
|
test('RUN: a section says Loading… the moment it is placed, under the heading a deep link needs', async () => {
|
|
300
292
|
const { live } = await loadLive({ responses: {} });
|
|
301
|
-
for (const [kind, id] of [['
|
|
293
|
+
for (const [kind, id] of [['bounty', 'security-bounty-rates']]) {
|
|
302
294
|
const s = live.placeholder(kind);
|
|
303
295
|
assert.match(s.innerHTML, new RegExp(`^<h3 id="${id}">`), `${kind} carries its address from the start`);
|
|
304
296
|
assert.match(s.innerHTML, /<p class="hall-loading" role="status">Loading…<\/p>/);
|
|
@@ -306,38 +298,6 @@ test('RUN: a section says Loading… the moment it is placed, under the heading
|
|
|
306
298
|
}
|
|
307
299
|
});
|
|
308
300
|
|
|
309
|
-
test('RUN: the treasury fills from the payload, and its line ends on the total the prose states', async () => {
|
|
310
|
-
const { live } = await loadLive({ responses: { [TREASURY]: costSummaryFixture } });
|
|
311
|
-
const s = live.placeholder('treasury');
|
|
312
|
-
assert.equal(await live.fillTreasury(s), true);
|
|
313
|
-
assert.equal(s.removed, false);
|
|
314
|
-
assert.equal(s.attrs['aria-busy'], undefined, 'no longer busy');
|
|
315
|
-
assert.doesNotMatch(s.innerHTML, /Loading…/);
|
|
316
|
-
assert.match(s.innerHTML, /^<h3 id="the-treasury">The treasury, so far<\/h3>/);
|
|
317
|
-
const usd = `$${Number(costSummaryFixture.total_usd).toLocaleString(undefined, { maximumFractionDigits: 0 })}`;
|
|
318
|
-
assert.ok(s.innerHTML.includes(`<strong>${usd}</strong>`), 'the spend total is stated');
|
|
319
|
-
assert.match(s.innerHTML, /id="treasury-spend"/);
|
|
320
|
-
assert.match(s.innerHTML, /id="treasury-earned"/);
|
|
321
|
-
assert.match(s.innerHTML, /Drachmae awarded, cumulative/, 'the currency word is the branding pack\'s');
|
|
322
|
-
assert.doesNotMatch(s.innerHTML, /NaN|Infinity/, 'a NaN in a path renders as an invisible chart with no error anywhere');
|
|
323
|
-
assert.doesNotMatch(s.innerHTML, /tabindex="0"/, 'no focus stop: nothing here wires a readout');
|
|
324
|
-
assert.doesNotMatch(s.innerHTML, /&amp;|&#/, 'nothing is escaped twice');
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
test('RUN: a treasury that cannot be read, or holds nothing yet, leaves NO section behind', async () => {
|
|
328
|
-
for (const responses of [{}, { [TREASURY]: new Error('down') }, { [TREASURY]: { daily: [], daily_credits: [] } }]) {
|
|
329
|
-
const { live } = await loadLive({ responses });
|
|
330
|
-
const s = live.placeholder('treasury');
|
|
331
|
-
assert.equal(await live.fillTreasury(s), false);
|
|
332
|
-
assert.equal(s.removed, true, 'the Loading… line goes with it — never an empty panel');
|
|
333
|
-
}
|
|
334
|
-
const noKit = await loadLive({ responses: { [TREASURY]: costSummaryFixture }, kit: false });
|
|
335
|
-
const s = noKit.live.placeholder('treasury');
|
|
336
|
-
assert.equal(await noKit.live.fillTreasury(s), false, 'without the chart kit there is nothing honest to draw');
|
|
337
|
-
assert.equal(s.removed, true);
|
|
338
|
-
assert.match(noKit.warnings.join('\n'), /OTBKit missing/);
|
|
339
|
-
});
|
|
340
|
-
|
|
341
301
|
test('RUN: the bounty rate card renders the live table, tuned tiers marked', async () => {
|
|
342
302
|
const tuned = { ...bountyFixture, table: bountyFixture.table.map((r, i) => (i === 0 ? { ...r, overridden: true } : r)) };
|
|
343
303
|
const { live } = await loadLive({ responses: { [BOUNTY]: tuned }, branding: {} });
|
|
@@ -358,20 +318,19 @@ test('RUN: a bounty feed that fails leaves no section', async () => {
|
|
|
358
318
|
}
|
|
359
319
|
});
|
|
360
320
|
|
|
361
|
-
test('the live
|
|
362
|
-
assert.match(liveJs, /TREASURY_URL = '\/api\/bongos\/public\/cost-summary'/);
|
|
321
|
+
test('the live section reads an endpoint that already existed, and the treasury is gone to /money', () => {
|
|
363
322
|
assert.match(liveJs, /BOUNTY_URL = '\/api\/bongos\/public\/bounty-table'/);
|
|
364
|
-
|
|
365
|
-
assert.match(liveJs, new RegExp(`kit\\.${fn}\\(`), `the treasury draws through ${fn}`);
|
|
366
|
-
}
|
|
323
|
+
assert.doesNotMatch(liveJs, /cost-summary|fillTreasury/, 'task 1005047: a money figure is drawn on the money page alone');
|
|
367
324
|
assert.doesNotMatch(liveJs, /<(?:path|polyline|rect)\b[^>]*\b(?:d|points)="\$\{/, 'no hand-built SVG geometry');
|
|
368
325
|
});
|
|
369
326
|
|
|
370
|
-
test('the page loads the
|
|
327
|
+
test('the page loads the live module, then the page script', () => {
|
|
371
328
|
const loaded = [...primerHtml.matchAll(/<script[^>]+src="([^"?]+)/g)].map((m) => m[1].split('/').pop());
|
|
372
329
|
const at = (f) => loaded.indexOf(f);
|
|
373
|
-
assert.ok(at('
|
|
374
|
-
assert.ok(at('
|
|
330
|
+
assert.ok(at('primer-live.js') > -1 && at('primer.js') > -1);
|
|
331
|
+
assert.ok(at('primer-live.js') < at('primer.js'));
|
|
332
|
+
// task 1005047: the chart kit drew the treasury, which is on the money page now.
|
|
333
|
+
assert.equal(at('hall-kit.js'), -1, 'nothing on the Primer draws a chart');
|
|
375
334
|
// The figure chrome is shared (style.css), so the Primer needs no profile sheet.
|
|
376
335
|
assert.match(primerHtml, /style\.css/);
|
|
377
336
|
assert.doesNotMatch(primerHtml, /profile\.css/);
|
|
@@ -411,7 +370,7 @@ test('emphasis inside a bold run renders — the API list-price line showed its
|
|
|
411
370
|
|
|
412
371
|
test('the room gives the fourth level a rule, the measure and the anchor offset', () => {
|
|
413
372
|
const h4 = rule(roomCss, '.primer-article h4');
|
|
414
|
-
assert.ok(h4, 'room.css styles h4 (the
|
|
373
|
+
assert.ok(h4, 'room.css styles h4 (the credits reference doc uses them)');
|
|
415
374
|
assert.match(h4, /scroll-margin-top/, 'a deep link to an h4 clears the top bar');
|
|
416
375
|
const measured = decomment(roomCss).match(/\.primer-article\s*>\s*p,[\s\S]*?\{([^}]*)\}/);
|
|
417
376
|
assert.ok(measured, 'the measured-children list exists');
|
|
@@ -96,7 +96,7 @@ test('the hall-preview fixture carries every atom /me resolves', () => {
|
|
|
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
98
|
// task 1005046: Ranks, Economy and Diagrams left Docs (one reading room, the Primer).
|
|
99
|
-
const SHELL_SHA = '
|
|
99
|
+
const SHELL_SHA = 'baf8a15f025eba68';
|
|
100
100
|
|
|
101
101
|
test('shell.js has not changed without its cache-bust being bumped', () => {
|
|
102
102
|
const actual = crypto.createHash('sha256').update(SHELL).digest('hex').slice(0, 16);
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<title>Fleet costs — {{worldName}}</title>
|
|
7
|
-
<meta name="description" content="What each hosted project costs this platform, and an estimate of what its owner pays for their own hosting.">
|
|
8
|
-
<link rel="icon" href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text x='16' y='24' font-size='24' text-anchor='middle' font-family='sans-serif'>⌂</text></svg>">
|
|
9
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
-
<link rel="stylesheet" href="/builders/style.css?v=2026-09-23-changelist1">
|
|
12
|
-
<link rel="stylesheet" href="/builders/oversight.css">
|
|
13
|
-
<link rel="stylesheet" href="/builders/project-settings.css">
|
|
14
|
-
</head>
|
|
15
|
-
<body data-page="fleet" data-page-title="Fleet costs">
|
|
16
|
-
|
|
17
|
-
<div class="app">
|
|
18
|
-
<aside class="app-sidebar" id="app-sidebar" aria-label="Primary"></aside>
|
|
19
|
-
<div class="app-scrim" id="app-scrim" hidden></div>
|
|
20
|
-
<div class="app-body">
|
|
21
|
-
<header class="app-topbar" id="app-topbar"></header>
|
|
22
|
-
<div class="app-content">
|
|
23
|
-
<!-- The page's one h1, for screen readers: no page shows its head (task
|
|
24
|
-
1004836), so its sub-line went in task 1004829. -->
|
|
25
|
-
<div class="page-head page-head--board">
|
|
26
|
-
<h1 class="page-head__h">Fleet costs</h1>
|
|
27
|
-
</div>
|
|
28
|
-
|
|
29
|
-
<main id="hall-main">
|
|
30
|
-
<!-- task 1005043: the page loader, shown until any section below is revealed. -->
|
|
31
|
-
<p class="hall-loading hall-loading--page hall-loading--wait" role="status">Loading…</p>
|
|
32
|
-
<section class="scroll" id="fl-sealed" aria-labelledby="fl-sealed-h" hidden>
|
|
33
|
-
<h2 class="scroll__h" id="fl-sealed-h">Restricted</h2>
|
|
34
|
-
<p class="scroll__lede">This page opens at a higher rank.</p>
|
|
35
|
-
</section>
|
|
36
|
-
|
|
37
|
-
<section class="scroll" id="fl-body" aria-labelledby="fl-body-h" hidden>
|
|
38
|
-
<h2 class="scroll__h" id="fl-body-h">What each project costs</h2>
|
|
39
|
-
<!-- Two different bills, one column each (ADR 0327): a cloud host runs on its
|
|
40
|
-
OWNER's account, so it costs this platform nothing and its owner pays their
|
|
41
|
-
hosting provider directly. Nothing on this page is charged to anyone. -->
|
|
42
|
-
<p class="scroll__lede">
|
|
43
|
-
Two different bills, one column each. The first is what a project costs this
|
|
44
|
-
platform. The second is an estimate of what its owner pays their own hosting
|
|
45
|
-
provider, which bills them directly. A project with no recorded plan reads
|
|
46
|
-
"not priced yet", never $0, and is left out of the total. A self-hosted
|
|
47
|
-
project runs on its owner's own machine, so its bill is not visible to us.
|
|
48
|
-
</p>
|
|
49
|
-
<div id="fl-panel"><span class="kit-skel"></span></div>
|
|
50
|
-
</section>
|
|
51
|
-
</main>
|
|
52
|
-
|
|
53
|
-
<footer class="app-foot" id="app-foot"></footer>
|
|
54
|
-
</div>
|
|
55
|
-
</div>
|
|
56
|
-
</div>
|
|
57
|
-
|
|
58
|
-
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
59
|
-
|
|
60
|
-
<script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
|
|
61
|
-
<script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
|
|
62
|
-
<script src="/builders/craft-mode.js?v=2026-11-04-reading-room"></script>
|
|
63
|
-
<script src="/builders/shell.js?v=2026-11-04-reading-room"></script>
|
|
64
|
-
<script src="/builders/palette.js" defer></script>
|
|
65
|
-
<script src="/builders/hall-kit.js"></script>
|
|
66
|
-
<script src="/builders/fleet-costs.js"></script>
|
|
67
|
-
<script src="/builders/fleet.js"></script>
|
|
68
|
-
</body>
|
|
69
|
-
</html>
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
// fleet.js — the fleet cost page (task 1001949, ADR 0327): the chrome around
|
|
2
|
-
// fleet-costs.js, which does all the drawing.
|
|
3
|
-
//
|
|
4
|
-
// One read, GET /provisioning/fleet/cost-ledger, gated on `provisioning.fleet.manage`
|
|
5
|
-
// exactly as this page is. A 403 shows the sealed state rather than an empty table,
|
|
6
|
-
// so a refusal is never mistaken for a fleet with nothing in it.
|
|
7
|
-
(() => {
|
|
8
|
-
'use strict';
|
|
9
|
-
|
|
10
|
-
const API = '/api/bongos';
|
|
11
|
-
const api = window.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false });
|
|
12
|
-
const { escapeHtml } = window.OTB;
|
|
13
|
-
const $ = (id) => document.getElementById(id);
|
|
14
|
-
|
|
15
|
-
function fail(message) {
|
|
16
|
-
$('fl-body').hidden = false;
|
|
17
|
-
$('fl-panel').innerHTML = `<p class="ps-invalid">${escapeHtml(message)}</p>`;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async function load() {
|
|
21
|
-
let res;
|
|
22
|
-
try {
|
|
23
|
-
res = await api.request('GET', `${API}/provisioning/fleet/cost-ledger`);
|
|
24
|
-
} catch (e) {
|
|
25
|
-
return fail('Could not reach the server.');
|
|
26
|
-
}
|
|
27
|
-
if (res.status === 401) { window.location.assign(`${API}/auth/web/start`); return; }
|
|
28
|
-
if (res.status === 403) {
|
|
29
|
-
$('fl-sealed').hidden = false;
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
if (!res.ok) {
|
|
33
|
-
const err = (res.data && res.data.error) || {};
|
|
34
|
-
return fail(err.message && err.message !== err.code ? err.message : 'Could not read the fleet costs.');
|
|
35
|
-
}
|
|
36
|
-
$('fl-body').hidden = false;
|
|
37
|
-
$('fl-panel').innerHTML = window.OTBFleetCosts.ledgerHtml(res.data);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
document.addEventListener('DOMContentLoaded', load);
|
|
41
|
-
})();
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"_": "FLEET COSTS (task 1001949, ADR 0327) through modules/ui-design/kit/render.js. The page reads ONE authenticated route, GET /provisioning/fleet/cost-ledger, gated on provisioning.fleet.manage, so against the kit's own stub it renders its failed-read state — a state the floors must hold in. To look at the populated ledger run it against the hall-preview harness, whose provisioning__fleet__cost-ledger.json fixture carries two cloud hosts with no recorded plan (every owner cost unpriced, the state live is in today):\n\n node scripts/hall-preview/server.js --port 4620 --fixture-me\n node modules/ui-design/kit/render.js --states modules/hall-ui/public/fleet.states.json --base-in http://localhost:4620 --base-out http://localhost:4620",
|
|
3
|
-
"page": "/builders/fleet",
|
|
4
|
-
"surface": "hall-ui",
|
|
5
|
-
"stub": { "prefix": "/builders/" },
|
|
6
|
-
"modeQuery": false,
|
|
7
|
-
"ignoreRequests": ["/api/(gds|bongos)/"],
|
|
8
|
-
"ignoreConsole": ["/api/(gds|bongos)/", "\\b401\\b", "\\b403\\b", "\\b404\\b", "Unauthorized", "Forbidden", "Not Found", "load failed"],
|
|
9
|
-
"states": {
|
|
10
|
-
"ledger": { "auth": true, "actions": [["wait", 800]], "expect": { "visible": ["#fl-body"] } },
|
|
11
|
-
"out": { "auth": false, "actions": [["wait", 600]] }
|
|
12
|
-
},
|
|
13
|
-
"reducedMotion": { "auth": true, "actions": [["wait", 800]] }
|
|
14
|
-
}
|