@bongos/core 1.24.34 → 1.24.36
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 +102 -147
- package/.claude/skills/merge-mode/SKILL.md +1 -1
- package/docs/adr/0228-the-reading-room-owes-a-reference-content-rules-not-a-second-archetype.md +1 -1
- package/docs/copy-inventory.md +136 -192
- package/docs/copy-registry.json +186 -752
- package/docs/module-api-changelog.md +4 -0
- package/docs/onboarding/diagrams/README.md +1 -2
- package/docs/onboarding/primer.md +66 -22
- package/docs/page-inventory.json +7 -101
- package/docs/page-readings.json +947 -1413
- package/docs/recipes/ui-look-before-you-ship.md +2 -2
- package/modules/design-styles/skills/style/SKILL.md +2 -2
- 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/builders.js +8 -6
- 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/diagram-viewer.css +1 -1
- package/modules/hall-ui/public/diagram-viewer.js +5 -5
- package/modules/hall-ui/public/fleet.html +2 -2
- package/modules/hall-ui/public/founding-mark.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/hall-render.js +62 -1
- package/modules/hall-ui/public/hall-widgets.js +11 -1
- 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 +4 -4
- 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/my-agents.html +2 -2
- package/modules/hall-ui/public/palette.js +15 -0
- package/modules/hall-ui/public/primer-live.js +188 -0
- package/modules/hall-ui/public/primer.html +11 -6
- package/modules/hall-ui/public/primer.js +101 -22
- package/modules/hall-ui/public/primer.states.json +54 -7
- 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/room.css +23 -121
- 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 +14 -21
- package/modules/hall-ui/public/street.html +2 -2
- package/modules/hall-ui/public/studio.html +2 -2
- package/modules/hall-ui/public/style.css +8 -55
- 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/modules/hall-ui/records/founding-mark.md +4 -4
- package/modules/hall-ui/records/reading-rooms.md +2 -0
- package/modules/hall-ui/records/reference-room.md +2 -0
- package/modules/onboarding/founding-mode.js +6 -2
- package/modules/ui-design/kit/render.js +1 -1
- package/modules/ui-design/styles/README.md +2 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/bongos/gen-hall-sitemap.js +15 -2
- package/scripts/hall-preview/server.js +6 -11
- package/src/bongos/serve-internal.js +28 -37
- package/src/module-api.js +1 -1
- package/tests/craft_mode_home.mjs +6 -2
- package/tests/founding_builders.mjs +6 -5
- package/tests/founding_mode.mjs +7 -4
- package/tests/hall_board_world.mjs +44 -1
- package/tests/hall_fullscreen.mjs +1 -1
- package/tests/hall_nav.mjs +24 -7
- package/tests/hall_page_gate_map.mjs +14 -5
- package/tests/hall_reading_room.mjs +426 -0
- package/tests/hall_reading_world.mjs +36 -30
- package/tests/hall_sitemap.mjs +25 -0
- package/tests/nav_permission_atoms.mjs +3 -2
- package/tests/rank_tier_single_source.mjs +0 -1
- package/tests/ui_design_kit.mjs +1 -1
- package/modules/hall-ui/public/diagrams.css +0 -45
- package/modules/hall-ui/public/diagrams.html +0 -106
- package/modules/hall-ui/public/diagrams.states.json +0 -28
- package/modules/hall-ui/public/drachmae.html +0 -110
- package/modules/hall-ui/public/drachmae.js +0 -431
- package/modules/hall-ui/public/drachmae.states.json +0 -58
- package/modules/hall-ui/public/ranks-founding.states.json +0 -12
- package/modules/hall-ui/public/ranks.html +0 -83
- package/modules/hall-ui/public/ranks.js +0 -229
- package/modules/hall-ui/public/ranks.states.json +0 -15
- package/tests/hall_reference_world.mjs +0 -352
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.36",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
package/release-notes.json
CHANGED
|
@@ -11167,5 +11167,17 @@
|
|
|
11167
11167
|
"id": "1005068",
|
|
11168
11168
|
"text": "The builders hall now has a generated map of every page and link, with a check that flags broken links and pages nobody can reach."
|
|
11169
11169
|
}
|
|
11170
|
+
],
|
|
11171
|
+
"1.24.35": [
|
|
11172
|
+
{
|
|
11173
|
+
"id": "1005069",
|
|
11174
|
+
"text": "The Primer now links to the Repo Atlas and the Commands page, so every mode can find them."
|
|
11175
|
+
}
|
|
11176
|
+
],
|
|
11177
|
+
"1.24.36": [
|
|
11178
|
+
{
|
|
11179
|
+
"id": "1005046",
|
|
11180
|
+
"text": "The Docs section now has one reading room instead of four pages: ranks, credits and karma, and the five diagrams all live in the Primer, each topic explained once with its picture beside it. Old links to Ranks, Economy and Dia"
|
|
11181
|
+
}
|
|
11170
11182
|
]
|
|
11171
11183
|
}
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
// • the crafts: craft-mode.js RUN in a vm, asked tabsFor(mode) — the same answer the hall uses;
|
|
17
17
|
// • the server's page gates: serve-internal.js's *_PAGE_RE literals and their gdsAuth
|
|
18
18
|
// dispatch, the static parse tests/hall_page_gate_map.mjs established;
|
|
19
|
-
// • the links: every in-hall href in a page's HTML and in each script the page loads
|
|
19
|
+
// • the links: every in-hall href in a page's HTML and in each script the page loads, and in
|
|
20
|
+
// the markdown a reading page fetches (CONTENT: the Primer's hall-page links, task 1005069).
|
|
20
21
|
// Scripts nearly every page loads (the shell, the palette, the craft switch) are the CHROME: their
|
|
21
22
|
// links are the nav, reported once, never charged to every page.
|
|
22
23
|
//
|
|
@@ -58,12 +59,23 @@ const ROUTES = {
|
|
|
58
59
|
'/board': { to: '/work', why: 'old name of the Work board, redirects' },
|
|
59
60
|
'/sky': { to: '/thinking', why: 'old name, redirects' },
|
|
60
61
|
'/market-watch-settings': { to: '/market-watch', why: 'now a Settings tab, redirects (task 1005045)' },
|
|
62
|
+
'/ranks': { to: '/primer', why: 'now a Primer chapter, redirects (task 1005046)' },
|
|
63
|
+
'/drachmae': { to: '/primer', why: 'now a Primer chapter, redirects (task 1005046)' },
|
|
64
|
+
'/diagrams': { to: '/primer', why: 'now a Primer chapter, redirects; its images still serve (task 1005046)' },
|
|
61
65
|
};
|
|
62
66
|
// Targets that leave the hall on purpose: sign-in, the hub's /account (platform-server.js;
|
|
63
67
|
// settings.js points the link at the hub), the API, module assets, and any file with an extension.
|
|
64
68
|
const EXTERNAL = [/^\/api\//, /^\/auth\b/, /^\/login\b/, /^\/logout\b/, /^\/signin\b/, /^\/status\b/,
|
|
65
69
|
/^\/account\b/, /^\/modules\//, /^\/docs\b/, /^\/diagrams\//, /^\/healthz\b/, /^\/version\b/, /\.[a-z0-9]{2,5}$/i];
|
|
66
70
|
|
|
71
|
+
// Pages whose words come from a markdown file they fetch. A link in that file to a hall page's
|
|
72
|
+
// own file (../../modules/hall-ui/public/<page>.html) opens the page in the hall (primer.js,
|
|
73
|
+
// task 1005069), so it is the page's link.
|
|
74
|
+
const CONTENT = { '/primer': 'docs/onboarding/primer.md' };
|
|
75
|
+
function mdHallLinks(md) {
|
|
76
|
+
return [...md.matchAll(/\]\((?:\.\.\/)+modules\/hall-ui\/public\/([a-z0-9-]+)\.html\)/g)].map((m) => '/' + m[1]);
|
|
77
|
+
}
|
|
78
|
+
|
|
67
79
|
function read(rel) { return fs.readFileSync(path.join(ROOT, rel), 'utf8'); }
|
|
68
80
|
|
|
69
81
|
// ---- the pages ----
|
|
@@ -210,6 +222,7 @@ function build() {
|
|
|
210
222
|
};
|
|
211
223
|
for (const h of linksIn(p.html)) add(h, p.file);
|
|
212
224
|
for (const s of p.scripts) if (!chrome.includes(s)) for (const h of linksOf(s)) add(h, s);
|
|
225
|
+
if (CONTENT[p.url]) for (const h of mdHallLinks(read(CONTENT[p.url]))) add(h, CONTENT[p.url]);
|
|
213
226
|
}
|
|
214
227
|
// the chrome's own links that are not nav items are checked too (the palette, the shell)
|
|
215
228
|
for (const s of chrome) for (const h of linksOf(s)) resolve(h, '(every page)', s);
|
|
@@ -364,5 +377,5 @@ function main(argv) {
|
|
|
364
377
|
return 0;
|
|
365
378
|
}
|
|
366
379
|
|
|
367
|
-
module.exports = { build, render, normalise, linksIn, classify, ROUTES, OUT };
|
|
380
|
+
module.exports = { build, render, normalise, linksIn, classify, mdHallLinks, ROUTES, CONTENT, OUT };
|
|
368
381
|
if (require.main === module) process.exit(main(process.argv.slice(2)));
|
|
@@ -479,8 +479,8 @@ app.get(/^\/([a-z-]+)$/, (req, res, next) => {
|
|
|
479
479
|
if (!sendPage(res, `${req.params[0]}.html`)) next();
|
|
480
480
|
});
|
|
481
481
|
|
|
482
|
-
// What the READING
|
|
483
|
-
// the generated diagrams.
|
|
482
|
+
// What the READING ROOM reads (task 1003306): the primer document and
|
|
483
|
+
// the generated diagrams. Both live outside modules/hall-ui/public and are
|
|
484
484
|
// served by serve-internal.js from docs/onboarding/ — but the hall's page gate
|
|
485
485
|
// sits in front of them, so in the harness every one of those fetches 302'd to
|
|
486
486
|
// sign-in and the primer could only ever be previewed in its FAILURE state and
|
|
@@ -489,17 +489,12 @@ app.get(/^\/([a-z-]+)$/, (req, res, next) => {
|
|
|
489
489
|
// canonical files prod reads.
|
|
490
490
|
// NEVER hand-edit a diagram: the .svg/.png pair and assertions.json are written
|
|
491
491
|
// in lockstep by scripts/bongos/gen-diagrams.js.
|
|
492
|
-
//
|
|
493
|
-
//
|
|
494
|
-
// /builders/diagrams
|
|
495
|
-
|
|
496
|
-
// /diagrams. Mount one and the populated page renders five broken images.
|
|
497
|
-
for (const prefix of ['/builders/diagrams', '/diagrams']) {
|
|
498
|
-
app.use(prefix, express.static(path.join(ROOT, 'docs/onboarding/diagrams')));
|
|
499
|
-
}
|
|
492
|
+
// The Primer (the one page that shows them since task 1005046 folded the
|
|
493
|
+
// Diagrams page into it) references each figure absolutely, at
|
|
494
|
+
// /builders/diagrams/*, which is the one prefix mounted here.
|
|
495
|
+
app.use('/builders/diagrams', express.static(path.join(ROOT, 'docs/onboarding/diagrams')));
|
|
500
496
|
for (const [route, file] of [
|
|
501
497
|
['/builders/primer.md', 'docs/onboarding/primer.md'],
|
|
502
|
-
['/builders/drachmae-and-karma.md', 'docs/onboarding/drachmae-and-karma.md'],
|
|
503
498
|
]) {
|
|
504
499
|
app.get(route, (req, res, next) => {
|
|
505
500
|
const abs = path.join(ROOT, file);
|
|
@@ -1048,6 +1048,28 @@ function mountInternalSurfaces(app) {
|
|
|
1048
1048
|
return res.redirect(302, `${isBuildersHost(req) ? '' : '/builders'}/market-watch${qs}#settings`);
|
|
1049
1049
|
});
|
|
1050
1050
|
|
|
1051
|
+
// task 1005046 (owner decision 2026-11-03, idea 1001311): ONE reading room. Ranks,
|
|
1052
|
+
// Economy (/drachmae) and Diagrams explained what the Primer already explained, so
|
|
1053
|
+
// they are Primer chapters now, each with its diagram, and each old URL lands on the
|
|
1054
|
+
// chapter that replaced it. 302 for the same reason as the two redirects above, and
|
|
1055
|
+
// no gate here: the redirect discloses nothing, and the Primer's own sign-in gate
|
|
1056
|
+
// applies the moment it is followed. The diagram IMAGES under /builders/diagrams/*
|
|
1057
|
+
// are not matched (the regex is anchored) and still serve below.
|
|
1058
|
+
const RETIRED_DOC_CHAPTER = Object.freeze({
|
|
1059
|
+
ranks: 'the-rank-ladder',
|
|
1060
|
+
drachmae: 'credits-and-karma',
|
|
1061
|
+
diagrams: 'the-claim-ship-lifecycle',
|
|
1062
|
+
});
|
|
1063
|
+
const RETIRED_DOC_PAGE_RE = /^\/builders\/(ranks|drachmae|diagrams)(?:\.html)?\/?$/;
|
|
1064
|
+
app.use((req, res, next) => {
|
|
1065
|
+
if (isModuleSurfaceHost(req)) return next();
|
|
1066
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') return next();
|
|
1067
|
+
const m = RETIRED_DOC_PAGE_RE.exec(path.posix.normalize(req.path));
|
|
1068
|
+
if (!m) return next();
|
|
1069
|
+
const qs = req.url.includes('?') ? req.url.slice(req.url.indexOf('?')) : '';
|
|
1070
|
+
return res.redirect(302, `${isBuildersHost(req) ? '' : '/builders'}/primer${qs}#${RETIRED_DOC_CHAPTER[m[1]]}`);
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1051
1073
|
// /deploy EXISTS ONLY WHERE THE DOOR DOES (task 1004296). The owner, 2026-09-25: /deploy is
|
|
1052
1074
|
// "only for bongos builders hall itself, not for all production ones". Every datum and
|
|
1053
1075
|
// both acts on that page are provisioning-module routes, so a hall that does not run the
|
|
@@ -1252,7 +1274,7 @@ function mountInternalSurfaces(app) {
|
|
|
1252
1274
|
// ONE tier of gate: ANY SIGNED-IN BUILDER — the Work Board, Roadmap, the Goals
|
|
1253
1275
|
// page, the task-detail page, the Repo Atlas, Settings
|
|
1254
1276
|
// (#748, #904, task 1293, task 1757, task 751) and, since task 1002710, the
|
|
1255
|
-
// orientation reading
|
|
1277
|
+
// orientation reading room (the primer; task 1005046 folded the diagrams in). A Xenos IS allowed; an
|
|
1256
1278
|
// unauthenticated visitor is bounced into sign-in rather than served the shell.
|
|
1257
1279
|
// Settings is in here (not gated higher) because a newcomer needs it for
|
|
1258
1280
|
// first-time setup — CLI re-auth and choosing their craft both live there
|
|
@@ -1324,9 +1346,10 @@ function mountInternalSurfaces(app) {
|
|
|
1324
1346
|
// whole gate; the governing reads it adds are gated by their own routes.
|
|
1325
1347
|
// task 1004921 `ai-studio` (the viewer's own routes).
|
|
1326
1348
|
const SIGNEDIN_BUILDERS_PAGE_RE =
|
|
1327
|
-
/^\/builders\/(?:work|settings|atlas|modules|goals|goal-map|roadmap|copy-desk|studio|ai-studio|tweak-editor|ideas|thinking|collab|task|idea|primer(?:\.md)?|
|
|
1349
|
+
/^\/builders\/(?:work|settings|atlas|modules|goals|goal-map|roadmap|copy-desk|studio|ai-studio|tweak-editor|ideas|thinking|collab|task|idea|primer(?:\.md)?|my-agents|commands)(?:\.html)?\/?$/;
|
|
1328
1350
|
// The diagram image assets under /builders/diagrams/ ride the same tier as the
|
|
1329
|
-
// page that embeds them
|
|
1351
|
+
// page that embeds them — the Primer, since task 1005046 retired the Diagrams
|
|
1352
|
+
// page (its URL now 302s to the Primer's first diagram, above).
|
|
1330
1353
|
const SIGNEDIN_BUILDERS_PREFIX_RE = /^\/builders\/diagrams\//;
|
|
1331
1354
|
const MARKET_WATCH_PAGE_RE = /^\/builders\/market-watch(?:-(?:item|rules))?(?:\.html)?\/?$/; // task 1004941: every read behind them is requireBuilder
|
|
1332
1355
|
app.use((req, res, next) => {
|
|
@@ -1348,25 +1371,6 @@ function mountInternalSurfaces(app) {
|
|
|
1348
1371
|
next();
|
|
1349
1372
|
});
|
|
1350
1373
|
|
|
1351
|
-
// V3.R13-followup-2 (#411): /builders/diagrams/ (with trailing slash) used to
|
|
1352
|
-
// fall through to the game's catch-all because express.static can't resolve a
|
|
1353
|
-
// directory request without an index. Redirect to /builders/diagrams (no slash)
|
|
1354
|
-
// so the diagrams.html page actually renders. The no-slash form already works
|
|
1355
|
-
// via the extensions: ['html'] resolution on buildersStatic.
|
|
1356
|
-
//
|
|
1357
|
-
// Implementation notes:
|
|
1358
|
-
// - REGEX route, not a string. With Express's default non-strict routing,
|
|
1359
|
-
// the string form '/builders/diagrams/' matches both '/.../diagrams' AND
|
|
1360
|
-
// '/.../diagrams/', which would cause a redirect loop. Anchoring with
|
|
1361
|
-
// ^…$ + a literal trailing slash gates the match to the with-slash form.
|
|
1362
|
-
// - Registered BEFORE the diagramsStatic wrapper below — that wrapper
|
|
1363
|
-
// mutates req.url for /builders/diagrams/* paths, which would prevent
|
|
1364
|
-
// this exact-path redirect from matching if it ran first.
|
|
1365
|
-
app.get(/^\/builders\/diagrams\/$/, (req, res, next) => {
|
|
1366
|
-
if (isModuleSurfaceHost(req)) return next();
|
|
1367
|
-
res.redirect(301, '/builders/diagrams');
|
|
1368
|
-
});
|
|
1369
|
-
|
|
1370
1374
|
// task 1965: the read-only Task Board (/board, task 751) was consolidated into
|
|
1371
1375
|
// the Work page (/work) — the hall keeps one task-listing surface. 301 old
|
|
1372
1376
|
// bookmarks + deep-links (the apex /builders/board and the bare subdomain
|
|
@@ -1396,7 +1400,7 @@ function mountInternalSurfaces(app) {
|
|
|
1396
1400
|
|
|
1397
1401
|
// Onboarding diagram PNGs (V3.R76 #295) live in docs/onboarding/diagrams as the
|
|
1398
1402
|
// single source of truth alongside their Mermaid sources. Serve them here so the
|
|
1399
|
-
//
|
|
1403
|
+
// Primer can inline each in its chapter without duplicating the binaries.
|
|
1400
1404
|
// Mounted before buildersStatic so /builders/diagrams/*.png resolves to docs/.
|
|
1401
1405
|
const diagramsStatic = express.static(path.join(ROOT, 'docs/onboarding/diagrams'), {
|
|
1402
1406
|
setHeaders: (res) => res.setHeader('Cache-Control', 'public, max-age=300'),
|
|
@@ -1423,19 +1427,6 @@ function mountInternalSurfaces(app) {
|
|
|
1423
1427
|
res.sendFile(path.join(ROOT, 'docs/onboarding/primer.md'));
|
|
1424
1428
|
});
|
|
1425
1429
|
|
|
1426
|
-
// Drachmae & karma reader (task 1472) — the builder-facing "how you get paid"
|
|
1427
|
-
// doc. The canonical markdown lives at docs/onboarding/drachmae-and-karma.md
|
|
1428
|
-
// (single source of truth, also linked from the primer + the repo); the
|
|
1429
|
-
// /builders/drachmae page (drachmae.js + md-reader.js) fetches + renders it
|
|
1430
|
-
// client-side. Same static-file mount pattern as primer.md above; public
|
|
1431
|
-
// like the /ranks page — economy transparency, no sign-in gate.
|
|
1432
|
-
app.get('/builders/drachmae-and-karma.md', (req, res, next) => {
|
|
1433
|
-
if (isModuleSurfaceHost(req)) return next();
|
|
1434
|
-
res.set('Cache-Control', 'public, max-age=300');
|
|
1435
|
-
res.set('Content-Type', 'text/markdown; charset=utf-8');
|
|
1436
|
-
res.sendFile(path.join(ROOT, 'docs/onboarding/drachmae-and-karma.md'));
|
|
1437
|
-
});
|
|
1438
|
-
|
|
1439
1430
|
// Rendered API docs (task 1918, BONGOS-V1). A self-describing OpenAPI 3.1 site
|
|
1440
1431
|
// at /docs — cloudbongos.com/docs is the advertised canonical URL, and it works
|
|
1441
1432
|
// on the apex of every instance (mounted in the SHARED surface, so a self-hosted
|
|
@@ -1474,7 +1465,7 @@ function mountInternalSurfaces(app) {
|
|
|
1474
1465
|
if (isModuleSurfaceHost(req)) return next();
|
|
1475
1466
|
if (req.path === '/builders' || req.path.startsWith('/builders/')) {
|
|
1476
1467
|
// Serve HTML pages from the stamped in-memory copy (asset ?v= = content
|
|
1477
|
-
// hash). Gated pages (primer
|
|
1468
|
+
// hash). Gated pages (the primer and the rest) have already passed the sign-in
|
|
1478
1469
|
// gate above before reaching here. Everything else (js/css/png/json,
|
|
1479
1470
|
// unknown paths) falls through to the on-disk static handler.
|
|
1480
1471
|
if (req.method === 'GET' || req.method === 'HEAD') {
|
package/src/module-api.js
CHANGED
|
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
|
|
|
81
81
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
82
82
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
83
83
|
|
|
84
|
-
const CORE_VERSION = '1.24.
|
|
84
|
+
const CORE_VERSION = '1.24.36'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
85
85
|
|
|
86
86
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
87
87
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|
|
@@ -107,8 +107,12 @@ test('Home redraws itself for the craft, so it never hops from here', () => {
|
|
|
107
107
|
assert.deepEqual(hops, []);
|
|
108
108
|
});
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
// The Economy page (drachmae.html, nav item 'economy') was this case's real example
|
|
111
|
+
// until task 1005046 folded it into the Primer; the rule it pins is unchanged, so a
|
|
112
|
+
// made-up pairing keeps it: the marked nav item (Tasks, Building's alone) is read
|
|
113
|
+
// before the page name (Studio, which Artistry also shows).
|
|
114
|
+
test('the nav item wins over the page name', () => {
|
|
115
|
+
const { CM } = boot({ page: 'studio', active: 'work' });
|
|
112
116
|
assert.equal(CM.strandedBy('artistry'), true);
|
|
113
117
|
assert.equal(CM.strandedBy('building'), false);
|
|
114
118
|
});
|
|
@@ -249,14 +249,15 @@ test('a project that was never founded keeps the wall hidden', async () => {
|
|
|
249
249
|
assert.equal(host.hidden, true);
|
|
250
250
|
});
|
|
251
251
|
|
|
252
|
-
|
|
252
|
+
// task 1005046: the Ranks page, which carried a third founding ledger, folded
|
|
253
|
+
// into the Primer; the founders are listed on Home's founding wall and on the
|
|
254
|
+
// roster, both pinned here, so the ledger went with the page.
|
|
255
|
+
test('the profile reads the same list, and every page loads the script first', () => {
|
|
253
256
|
const profile = read('profile.js');
|
|
254
|
-
const ranks = read('ranks.js');
|
|
255
257
|
// renderHead takes the role headline since task 1004434; the mark still follows it.
|
|
256
258
|
assert.match(profile, /renderHead\(builder(?:, view)?\);\s*\n\s*renderFoundingMark\(builder\);/);
|
|
257
|
-
assert.
|
|
258
|
-
|
|
259
|
-
for (const [html, page] of [['roster.html', 'roster.js'], ['profile.html', 'profile.js'], ['ranks.html', 'ranks.js'], ['index.html', 'hall-widgets.js']]) {
|
|
259
|
+
assert.ok(!fs.existsSync(path.join(PUB, 'ranks.html')), 'the Ranks page and its founding ledger are gone');
|
|
260
|
+
for (const [html, page] of [['roster.html', 'roster.js'], ['profile.html', 'profile.js'], ['index.html', 'hall-widgets.js']]) {
|
|
260
261
|
const src = read(html);
|
|
261
262
|
const a = src.indexOf('/builders/founding-mark.js');
|
|
262
263
|
const b = src.indexOf(`/builders/${page}`);
|
package/tests/founding_mode.mjs
CHANGED
|
@@ -43,7 +43,7 @@ test('active with no stage yet is the decide pre-stage, and every room sleeps',
|
|
|
43
43
|
assert.equal(f.stage, 'decide');
|
|
44
44
|
assert.equal(f.stage_number, 0);
|
|
45
45
|
assert.equal(f.stage_count, 5);
|
|
46
|
-
for (const id of ['roadmap', 'goals', 'goal-map', 'work', 'board-room', '
|
|
46
|
+
for (const id of ['roadmap', 'goals', 'goal-map', 'work', 'board-room', 'collab', 'roster']) {
|
|
47
47
|
assert.ok(id in f.asleep, `${id} sleeps while deciding`);
|
|
48
48
|
}
|
|
49
49
|
});
|
|
@@ -52,8 +52,11 @@ test('a room wakes once the stage that wakes it is behind', () => {
|
|
|
52
52
|
const f = fm.shapeFoundingMode({ active: 'true', stage: 'charter' });
|
|
53
53
|
for (const id of ['roadmap', 'goals', 'goal-map', 'work']) assert.ok(!(id in f.asleep), `${id} woke`);
|
|
54
54
|
assert.equal(f.asleep['board-room'], 'charter');
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
// task 1005046: Economy and Ranks were the rooms legalize and legislation woke;
|
|
56
|
+
// they are Primer chapters now, and the Primer never sleeps.
|
|
57
|
+
assert.ok(!('economy' in f.asleep) && !('ranks' in f.asleep), 'no retired Docs page is put to sleep');
|
|
58
|
+
assert.ok(!('primer' in f.asleep), 'the Primer stays awake through founding');
|
|
59
|
+
assert.deepEqual([...fm.WAKES.legalize, ...fm.WAKES.legislation], [], 'the two late stages wake no sidebar room');
|
|
57
60
|
assert.equal(f.asleep.collab, 'alive', 'community wakes only when the planet is alive');
|
|
58
61
|
});
|
|
59
62
|
|
|
@@ -118,7 +121,7 @@ class El {
|
|
|
118
121
|
}
|
|
119
122
|
|
|
120
123
|
function makeHall() {
|
|
121
|
-
const navIds = ['home', 'roadmap', 'goals', 'goal-map', 'work', 'board-room', '
|
|
124
|
+
const navIds = ['home', 'roadmap', 'goals', 'goal-map', 'work', 'board-room', 'collab', 'roster', 'profile'];
|
|
122
125
|
const nav = navIds.map((id) => { const a = new El('a'); a.id = 'nav-' + id; a.className = 'nav-item'; return a; });
|
|
123
126
|
const appBody = new El('div');
|
|
124
127
|
const topbar = new El('header'); topbar.id = 'app-topbar';
|
|
@@ -315,7 +315,7 @@ test('the greeting reads /me only; the version tab and its progress read left Ho
|
|
|
315
315
|
|
|
316
316
|
// Boot hall-render.js against a stub DOM and a ctx of getters (the way
|
|
317
317
|
// builders.js builds it) and read what the greeting and the card actually say.
|
|
318
|
-
function bootHallRender({ me, progress = null, leaderboard = [] }) {
|
|
318
|
+
function bootHallRender({ me, progress = null, leaderboard = [], ctxExtra = {} }) {
|
|
319
319
|
const els = new Map();
|
|
320
320
|
const makeEl = () => ({
|
|
321
321
|
dataset: {}, style: {}, textContent: '', hidden: false, _html: '', innerHTML: '',
|
|
@@ -354,6 +354,7 @@ function bootHallRender({ me, progress = null, leaderboard = [] }) {
|
|
|
354
354
|
get lastMeState() { return me; }, get lastCostState() { return me.cost || null; }, get lastKarmaState() { return me.karma || null; },
|
|
355
355
|
get lastNeedsState() { return me.needs || null; }, get lastRebaseState() { return me.needs_rebase_tasks || null; },
|
|
356
356
|
get lastProgressState() { return progress; },
|
|
357
|
+
...ctxExtra,
|
|
357
358
|
};
|
|
358
359
|
const api = windowObj.OTBHallRender(ctx);
|
|
359
360
|
api.lbState.rows = leaderboard;
|
|
@@ -396,6 +397,48 @@ test('RUN: the Standing card carries the three facts, the place as "2nd of 4", a
|
|
|
396
397
|
assert.match(withPrefs.el('home-standing-body').innerHTML, /<dt>Leaderboard place<\/dt><dd>—<\/dd>/, 'no roll yet: a dash, not a wrong place');
|
|
397
398
|
});
|
|
398
399
|
|
|
400
|
+
// task 1005046: "Where you stand" left the retired Ranks page for Accomplishments.
|
|
401
|
+
// builders.js hands the factory its real tier MAP and its coercion; the card names
|
|
402
|
+
// the viewer's rank, the rank above it read off that map, and how to reach it.
|
|
403
|
+
const TIER_CTX = {
|
|
404
|
+
RANK_ORDER: { archon: 4, metic: 3, thetes: 2, xenos: 1 },
|
|
405
|
+
normalizeRank: (r) => ({ archon: 4, metic: 3, thetes: 2, xenos: 1 }[r] ? r : 'xenos'),
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
test('RUN: Where you stand names the viewer\'s rank, the one above it, and how to reach it', () => {
|
|
409
|
+
const { api, el } = bootHallRender({ me: ME, ctxExtra: TIER_CTX });
|
|
410
|
+
api.renderProfile({ ...ME.builder, rank: 'thetes' });
|
|
411
|
+
const html = el('rank-standing-body').innerHTML;
|
|
412
|
+
assert.match(html, /<div class="ranks-standing__badge"><span class="rank-badge rank-badge--thetes"/);
|
|
413
|
+
assert.match(html, /<p class="ranks-standing__title">The proven builder<\/p>/);
|
|
414
|
+
assert.match(html, /The rank above yours is <span class="rank-badge rank-badge--metic"/, 'the next rung is read off the tier map');
|
|
415
|
+
assert.match(html, /Promotion to <strong>Metic<\/strong>/, 'and the climb is the one from THIS rank');
|
|
416
|
+
assert.match(html, /<a href="\/primer#the-rank-ladder">/, 'the full ladder is one chapter of the Primer');
|
|
417
|
+
assert.doesNotMatch(html, /Loading…/, 'the skeleton is replaced');
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
test('RUN: Where you stand says so at the top, and coerces a stray rank to the entry rung', () => {
|
|
421
|
+
const top = bootHallRender({ me: ME, ctxExtra: TIER_CTX });
|
|
422
|
+
top.api.renderProfile({ ...ME.builder, rank: 'archon' });
|
|
423
|
+
const atTop = top.el('rank-standing-body').innerHTML;
|
|
424
|
+
assert.equal((atTop.match(/You hold the highest rank/g) || []).length, 1, 'said once, not twice');
|
|
425
|
+
assert.doesNotMatch(atTop, /ranks-standing__next/, 'no next rung to name');
|
|
426
|
+
const stray = bootHallRender({ me: ME, ctxExtra: TIER_CTX });
|
|
427
|
+
stray.api.renderProfile({ ...ME.builder, rank: 'strategos' });
|
|
428
|
+
const html = stray.el('rank-standing-body').innerHTML;
|
|
429
|
+
assert.match(html, /<p class="ranks-standing__title">The newcomer<\/p>/, 'a reserved rank reads as xenos, as the Standing band does');
|
|
430
|
+
assert.match(html, /The rank above yours is <span class="rank-badge rank-badge--thetes"/);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
test('the Where you stand card is a Progress section of Accomplishments, with a Loading… line', () => {
|
|
434
|
+
const widgets = read('hall-widgets.js');
|
|
435
|
+
const at = widgets.indexOf("w('rank-scroll', 'Where you stand'");
|
|
436
|
+
assert.ok(at > -1, 'registered as a hall widget');
|
|
437
|
+
const reg = widgets.slice(at, widgets.indexOf('});', at));
|
|
438
|
+
assert.match(reg, /<div id="rank-standing-body"><div class="hall-loading" role="status">Loading…<\/div><\/div>/);
|
|
439
|
+
assert.match(reg, /view: 'standing', group: 'progress'/);
|
|
440
|
+
});
|
|
441
|
+
|
|
399
442
|
test('RUN: the three notices are inbox lines with a kind, a line and — where one exists — a door', () => {
|
|
400
443
|
const { api, el } = bootHallRender({ me: ME });
|
|
401
444
|
api.renderProfile(ME.builder);
|
|
@@ -276,7 +276,7 @@ test('no hall page draws its own back link; each carries the shell\'s hooks inst
|
|
|
276
276
|
assert.match(read('tweak-editor.css'), /html:not\(\[data-fullscreen="on"\]\) \.tw-stage \.tw-back \{ display: none; \}/,
|
|
277
277
|
'and shows it only in full screen, so the page never offers two');
|
|
278
278
|
assert.match(read('idea.html'), /<body [^>]*data-back-fallback="\/thinking"/);
|
|
279
|
-
for (const f of ['primer.html'
|
|
279
|
+
for (const f of ['primer.html']) {
|
|
280
280
|
assert.match(read(f), /<a class="btn btn--ghost" data-shell-exit href="\/builders">/, `${f}: its error exit is the shell's`);
|
|
281
281
|
}
|
|
282
282
|
for (const f of fs.readdirSync(HALL).filter((n) => n.endsWith('.html'))) {
|
package/tests/hall_nav.mjs
CHANGED
|
@@ -308,8 +308,11 @@ test('sidebar groups read by audience, in order', () => {
|
|
|
308
308
|
assert.ok(!nav.flatMap((g) => g.items).some((i) => /^\/people\b/.test(i.href)), 'no nav item points at the deleted /people');
|
|
309
309
|
// task 1004473: 'commands' — every skill and CLI verb, read live — is reference
|
|
310
310
|
// material that reads the same for every builder, so it joins Docs.
|
|
311
|
+
// task 1005046 (owner decision 2026-11-03, idea 1001311): ONE reading room. Ranks,
|
|
312
|
+
// Economy and Diagrams explained what the Primer explains; they are its chapters
|
|
313
|
+
// now, so Docs is the Primer and the four references that are not a document.
|
|
311
314
|
assert.deepEqual(nav[3].items.map((i) => i.id),
|
|
312
|
-
['primer', '
|
|
315
|
+
['primer', 'commands', 'atlas', 'modules', 'apidocs'], 'Docs = reference material');
|
|
313
316
|
// 'government' (the Permissions tab, BV1.R113) is the group's one archon-gated item.
|
|
314
317
|
// 'project-settings' joins the Government group at the metic floor (task 1003280,
|
|
315
318
|
// goal 1000072) — it sits before 'government', which is the one archon-only item.
|
|
@@ -333,12 +336,26 @@ test('sidebar groups read by audience, in order', () => {
|
|
|
333
336
|
assert.ok(!nav.flatMap((g) => g.items).some((i) => i.href === '/market-watch-settings'), 'no item points at the retired settings page');
|
|
334
337
|
});
|
|
335
338
|
|
|
336
|
-
|
|
339
|
+
// task 1005046: the three pages that left Docs left the hall. Their nav items are
|
|
340
|
+
// gone, their files are gone, and each old URL 302s to the Primer chapter that
|
|
341
|
+
// replaced it (tests/hall_page_gate_map.mjs pins the redirect; tests/
|
|
342
|
+
// hall_reading_world.mjs pins that the chapter exists and carries its diagram).
|
|
343
|
+
test('Ranks, Economy and Diagrams are Primer chapters, not pages', () => {
|
|
337
344
|
const nav = parseNav(boot().sidebarHtml);
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
345
|
+
const items = nav.flatMap((g) => g.items);
|
|
346
|
+
for (const id of ['ranks', 'economy', 'diagrams']) {
|
|
347
|
+
assert.equal(items.some((i) => i.id === id), false, `the nav renders no '${id}' item`);
|
|
348
|
+
assert.doesNotMatch(SRC, new RegExp(`id: '${id}'`), `shell.js NAV declares no '${id}' item`);
|
|
349
|
+
assert.doesNotMatch(SRC, new RegExp(`\\n\\s*${id}: '<`), `and ICONS keeps no '${id}' glyph for it`);
|
|
350
|
+
}
|
|
351
|
+
for (const href of ['/ranks', '/drachmae', '/diagrams']) {
|
|
352
|
+
assert.equal(items.some((i) => i.href === href), false, `no item points at ${href}`);
|
|
353
|
+
}
|
|
354
|
+
for (const f of ['ranks.html', 'drachmae.html', 'diagrams.html']) {
|
|
355
|
+
assert.ok(!fs.existsSync(path.join(HALL, f)), `${f} left the hall`);
|
|
356
|
+
}
|
|
357
|
+
const primer = nav.find((g) => g.label === 'Docs').items.find((i) => i.id === 'primer');
|
|
358
|
+
assert.equal(primer.href, '/primer', 'the Primer is the reading room the three became');
|
|
342
359
|
});
|
|
343
360
|
|
|
344
361
|
test('every nav link lands on a page that exists', () => {
|
|
@@ -370,7 +387,7 @@ test('THE ARCHIVE: Ideas and Copy desk are off the nav, and both pages still exi
|
|
|
370
387
|
|
|
371
388
|
test('href check actually CATCHES a dead link', () => {
|
|
372
389
|
assert.equal(hrefResolves({ href: '/no-such-page', external: false }), false);
|
|
373
|
-
assert.equal(hrefResolves({ href: '/
|
|
390
|
+
assert.equal(hrefResolves({ href: '/primer', external: false }), true);
|
|
374
391
|
});
|
|
375
392
|
|
|
376
393
|
// ── Community carries no rank flag (task 1003442, goal 1000075) ─────────────
|
|
@@ -112,7 +112,10 @@ const ORACLE = {
|
|
|
112
112
|
// gate those two rows left behind; see GONE_GATES below.)
|
|
113
113
|
'/builders/primer': 'requireBuilderPage',
|
|
114
114
|
'/builders/primer.md': 'requireBuilderPage',
|
|
115
|
-
|
|
115
|
+
// Gone (task 1005046, owner decision 2026-11-03): the Diagrams page is the Primer's
|
|
116
|
+
// chapters now, and /builders/diagrams 302s to the first of them before any gate
|
|
117
|
+
// runs. Its IMAGES are still served, on the Primer's tier, by the prefix gate.
|
|
118
|
+
'/builders/diagrams': null,
|
|
116
119
|
'/builders/diagrams/02-b.png': 'requireBuilderPage',
|
|
117
120
|
|
|
118
121
|
// Metic+ operator surfaces (the Governance nav group).
|
|
@@ -167,8 +170,9 @@ const ORACLE = {
|
|
|
167
170
|
// same reason (its governed read is the same docket).
|
|
168
171
|
'/builders/street': 'requireBoardVotePage',
|
|
169
172
|
|
|
170
|
-
// Deliberately UNGATED — the landing card
|
|
171
|
-
//
|
|
173
|
+
// Deliberately UNGATED — the landing card and a builder's public portfolio
|
|
174
|
+
// render for a stranger. (/ranks and /drachmae sat here too; since task 1005046
|
|
175
|
+
// they are Primer chapters, the old URLs 302 there, and GONE_PAGES holds them.)
|
|
172
176
|
//
|
|
173
177
|
// /roster joins them (task 1003441): it is this project’s own ledger, and its
|
|
174
178
|
// one data source — GET /leaderboard — is deliberately unauthenticated,
|
|
@@ -186,8 +190,8 @@ const ORACLE = {
|
|
|
186
190
|
// status dashboard is meant to read it too — "anyone landing on /builders or
|
|
187
191
|
// the status dashboard can see what's stuck on the owner", the route's own
|
|
188
192
|
// handler comment), so the page must not be gated tighter than the API it
|
|
189
|
-
// fronts — the same rule that puts /roster
|
|
190
|
-
//
|
|
193
|
+
// fronts — the same rule that puts /roster here. The nav link is still
|
|
194
|
+
// cosmetically 'signed', same as that one.
|
|
191
195
|
'/builders/blockers': null,
|
|
192
196
|
|
|
193
197
|
// The bare detail slugs, gated by task 1003629 — the decision idea 1000907
|
|
@@ -350,6 +354,11 @@ const GONE_PAGES = {
|
|
|
350
354
|
'/builders/people': 'task 1003444 — the hall has no cross-project directory; /people redirects to /roster',
|
|
351
355
|
// Task 1005045 — one sidebar item for Market Watch: its settings are a tab on the page.
|
|
352
356
|
'/builders/market-watch-settings': 'task 1005045 — the settings are the Settings tab on /market-watch; the old URL redirects there',
|
|
357
|
+
// Task 1005046 — one reading room: each is a Primer chapter, and the old URL 302s to it
|
|
358
|
+
// (tests/hall_reading_room.mjs runs the redirect).
|
|
359
|
+
'/builders/ranks': 'task 1005046 — the rank ladder is a Primer chapter; /ranks redirects there',
|
|
360
|
+
'/builders/drachmae': 'task 1005046 — credits and karma is a Primer chapter; /drachmae redirects there',
|
|
361
|
+
'/builders/diagrams': 'task 1005046 — each diagram sits in its Primer chapter; /diagrams redirects there',
|
|
353
362
|
};
|
|
354
363
|
|
|
355
364
|
test('a row that is null because the page left the hall serves no page', () => {
|