@bongos/core 1.24.35 → 1.24.37
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 +128 -168
- 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 +164 -219
- package/docs/copy-registry.json +323 -892
- 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 +39 -133
- package/docs/page-readings.json +882 -1346
- 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 +2 -2
- 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-costs.js +3 -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/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 +22 -0
- package/modules/hall-ui/public/primer-live.js +101 -0
- package/modules/hall-ui/public/primer.html +10 -6
- package/modules/hall-ui/public/primer.js +93 -23
- 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.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/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 +21 -26
- 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 +3 -0
- package/scripts/hall-preview/server.js +6 -11
- package/src/bongos/auth.js +0 -8
- package/src/bongos/serve-internal.js +43 -43
- package/src/module-api.js +1 -1
- package/tests/auth_resolve_failure.mjs +5 -4
- package/tests/craft_mode_home.mjs +6 -2
- package/tests/fleet_cost_view.mjs +2 -18
- 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_loading.mjs +1 -1
- package/tests/hall_money_page.mjs +272 -0
- package/tests/hall_nav.mjs +29 -11
- package/tests/hall_oversight_world.mjs +1 -1
- package/tests/hall_page_gate_map.mjs +17 -7
- package/tests/hall_reading_room.mjs +385 -0
- package/tests/hall_reading_world.mjs +36 -30
- 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/fleet.html +0 -69
- package/modules/hall-ui/public/fleet.js +0 -41
- package/modules/hall-ui/public/fleet.states.json +0 -14
- 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
|
@@ -36,7 +36,7 @@ modules/ui-design/styles/<name>/
|
|
|
36
36
|
```
|
|
37
37
|
STYLE=grove node modules/ui-design/kit/serve.js # the front door under the grove
|
|
38
38
|
node modules/ui-design/kit/render.js --page / --style expedition # every state × width × mode, audited
|
|
39
|
-
node modules/ui-design/kit/render.js --page /builders/
|
|
39
|
+
node modules/ui-design/kit/render.js --page /builders/primer --surface hall-ui --style grove
|
|
40
40
|
node modules/ui-design/kit/probe.js --page /projects --style grove
|
|
41
41
|
node modules/ui-design/kit/contrast.js --pack expedition # the pack floor, no browser
|
|
42
42
|
node modules/ui-design/kit/check-mock.js --file modules/ui-design/styles/grove/mock.html --pack grove
|
|
@@ -56,5 +56,5 @@ The steps below are the manual path. The **`/style` session** ([task 1003475](ht
|
|
|
56
56
|
1. Make the dir with the three files; start from the alternate nearest to it.
|
|
57
57
|
2. `node modules/ui-design/kit/contrast.js --pack <name>` until the floor is clean; then `node --test tests/ui_design_styles.mjs`, which runs the world / cosmos / hall suites under the pack and tells you the exact pair that fails. Tune the thirteen, never the sheets.
|
|
58
58
|
3. `node modules/ui-design/kit/check-mock.js --file modules/ui-design/styles/<name>/mock.html --pack <name>` until ALL RULES PASS.
|
|
59
|
-
4. `node modules/ui-design/kit/render.js --page / --style <name>` and look at the shots; the hall's `/builders/
|
|
59
|
+
4. `node modules/ui-design/kit/render.js --page / --style <name>` and look at the shots; the hall's `/builders/primer` shows the light tier.
|
|
60
60
|
5. Add the row above, the line in the module's `CLAUDE.md` and in `/design`. Ship under a claimed task.
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.37",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.24.
|
|
9
|
+
"version": "1.24.37",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.37",
|
|
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
|
@@ -11173,5 +11173,17 @@
|
|
|
11173
11173
|
"id": "1005069",
|
|
11174
11174
|
"text": "The Primer now links to the Repo Atlas and the Commands page, so every mode can find them."
|
|
11175
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
|
+
}
|
|
11182
|
+
],
|
|
11183
|
+
"1.24.37": [
|
|
11184
|
+
{
|
|
11185
|
+
"id": "1005047",
|
|
11186
|
+
"text": "Owners now have one Money page in the Government group: what the project spent and paid out, its treasury, and what each hosted project costs, all in one place. Project settings and the Primer link to it instead of repeating t"
|
|
11187
|
+
}
|
|
11176
11188
|
]
|
|
11177
11189
|
}
|
|
@@ -59,6 +59,9 @@ const ROUTES = {
|
|
|
59
59
|
'/board': { to: '/work', why: 'old name of the Work board, redirects' },
|
|
60
60
|
'/sky': { to: '/thinking', why: 'old name, redirects' },
|
|
61
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)' },
|
|
62
65
|
};
|
|
63
66
|
// Targets that leave the hall on purpose: sign-in, the hub's /account (platform-server.js;
|
|
64
67
|
// settings.js points the link at the hub), the API, module assets, and any file with an extension.
|
|
@@ -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);
|
package/src/bongos/auth.js
CHANGED
|
@@ -406,10 +406,6 @@ const PAGE_GATES = Object.freeze({
|
|
|
406
406
|
// live project; which project they may read or move is still the routes' question,
|
|
407
407
|
// asked per request against that project's row.
|
|
408
408
|
requireCorePinPage: Object.freeze({ permission: 'core.pin.move', deniedTo: '/builders', alsoAdmits: 'isProjectOwner' }),
|
|
409
|
-
// The /fleet cost page (task 1001949) — on `provisioning.fleet.manage`, the SAME atom
|
|
410
|
-
// its one read (GET /provisioning/fleet/cost-ledger) checks: every owner's projects
|
|
411
|
-
// and what they cost, so the page reaches exactly as far as the data does.
|
|
412
|
-
requireFleetPage: Object.freeze({ permission: 'provisioning.fleet.manage', deniedTo: '/builders' }),
|
|
413
409
|
});
|
|
414
410
|
|
|
415
411
|
// The `alsoAdmits` door — "is this visitor the page's OTHER audience?", asked of
|
|
@@ -568,9 +564,6 @@ async function requireBoardVotePage(req, res, next) {
|
|
|
568
564
|
async function requireCorePinPage(req, res, next) {
|
|
569
565
|
return runPageGate('requireCorePinPage', req, res, next);
|
|
570
566
|
}
|
|
571
|
-
async function requireFleetPage(req, res, next) {
|
|
572
|
-
return runPageGate('requireFleetPage', req, res, next);
|
|
573
|
-
}
|
|
574
567
|
|
|
575
568
|
// ---------------------------------------------------------------------------
|
|
576
569
|
// Route-gate inventory (V3.R06 #231) — the canonical "who can call what" list.
|
|
@@ -862,7 +855,6 @@ module.exports = {
|
|
|
862
855
|
requireGovernmentPage,
|
|
863
856
|
requireBoardVotePage,
|
|
864
857
|
requireCorePinPage,
|
|
865
|
-
requireFleetPage,
|
|
866
858
|
requireRank,
|
|
867
859
|
rankMeetsThreshold,
|
|
868
860
|
requirePermission,
|
|
@@ -1010,8 +1010,9 @@ function mountInternalSurfaces(app) {
|
|
|
1010
1010
|
// statically, and a comment in that gap makes the page test as UNGATED while
|
|
1011
1011
|
// looking gated here. Prose goes above the const, as here.
|
|
1012
1012
|
const DEPLOY_PAGE_RE = /^\/builders\/deploy(?:\.html)?\/?$/;
|
|
1013
|
-
//
|
|
1014
|
-
|
|
1013
|
+
// /money — the owner's one money page (task 1005047): the government floor; each
|
|
1014
|
+
// section's read keeps its own wall. NB as at PROJECT_SETTINGS_PAGE_RE.
|
|
1015
|
+
const MONEY_PAGE_RE = /^\/builders\/money(?:\.html)?\/?$/;
|
|
1015
1016
|
// /people and its legacy twin /scouting were the hall's cross-project builder
|
|
1016
1017
|
// directory. Task 1003440 deleted the /scouting door; task 1003444 deleted
|
|
1017
1018
|
// /people itself (owner call, 2026-09-25 — ADR 0337 D5): cross-project
|
|
@@ -1048,6 +1049,38 @@ function mountInternalSurfaces(app) {
|
|
|
1048
1049
|
return res.redirect(302, `${isBuildersHost(req) ? '' : '/builders'}/market-watch${qs}#settings`);
|
|
1049
1050
|
});
|
|
1050
1051
|
|
|
1052
|
+
// task 1005047: Fleet costs is /money's #hosting section now; 302 and no gate, as above.
|
|
1053
|
+
const RETIRED_FLEET_RE = /^\/builders\/fleet(?:\.html)?\/?$/;
|
|
1054
|
+
app.use((req, res, next) => {
|
|
1055
|
+
if (isModuleSurfaceHost(req)) return next();
|
|
1056
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') return next();
|
|
1057
|
+
if (!RETIRED_FLEET_RE.test(path.posix.normalize(req.path))) return next();
|
|
1058
|
+
const qs = req.url.includes('?') ? req.url.slice(req.url.indexOf('?')) : '';
|
|
1059
|
+
return res.redirect(302, `${isBuildersHost(req) ? '' : '/builders'}/money${qs}#hosting`);
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
// task 1005046 (owner decision 2026-11-03, idea 1001311): ONE reading room. Ranks,
|
|
1063
|
+
// Economy (/drachmae) and Diagrams explained what the Primer already explained, so
|
|
1064
|
+
// they are Primer chapters now, each with its diagram, and each old URL lands on the
|
|
1065
|
+
// chapter that replaced it. 302 for the same reason as the two redirects above, and
|
|
1066
|
+
// no gate here: the redirect discloses nothing, and the Primer's own sign-in gate
|
|
1067
|
+
// applies the moment it is followed. The diagram IMAGES under /builders/diagrams/*
|
|
1068
|
+
// are not matched (the regex is anchored) and still serve below.
|
|
1069
|
+
const RETIRED_DOC_CHAPTER = Object.freeze({
|
|
1070
|
+
ranks: 'the-rank-ladder',
|
|
1071
|
+
drachmae: 'credits-and-karma',
|
|
1072
|
+
diagrams: 'the-claim-ship-lifecycle',
|
|
1073
|
+
});
|
|
1074
|
+
const RETIRED_DOC_PAGE_RE = /^\/builders\/(ranks|drachmae|diagrams)(?:\.html)?\/?$/;
|
|
1075
|
+
app.use((req, res, next) => {
|
|
1076
|
+
if (isModuleSurfaceHost(req)) return next();
|
|
1077
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') return next();
|
|
1078
|
+
const m = RETIRED_DOC_PAGE_RE.exec(path.posix.normalize(req.path));
|
|
1079
|
+
if (!m) return next();
|
|
1080
|
+
const qs = req.url.includes('?') ? req.url.slice(req.url.indexOf('?')) : '';
|
|
1081
|
+
return res.redirect(302, `${isBuildersHost(req) ? '' : '/builders'}/primer${qs}#${RETIRED_DOC_CHAPTER[m[1]]}`);
|
|
1082
|
+
});
|
|
1083
|
+
|
|
1051
1084
|
// /deploy EXISTS ONLY WHERE THE DOOR DOES (task 1004296). The owner, 2026-09-25: /deploy is
|
|
1052
1085
|
// "only for bongos builders hall itself, not for all production ones". Every datum and
|
|
1053
1086
|
// both acts on that page are provisioning-module routes, so a hall that does not run the
|
|
@@ -1072,7 +1105,8 @@ function mountInternalSurfaces(app) {
|
|
|
1072
1105
|
if (!req.path.startsWith('/builders')) return next();
|
|
1073
1106
|
const norm = path.posix.normalize(req.path);
|
|
1074
1107
|
if (WATCH_PAGE_RE.test(norm) || GATE_PAGE_RE.test(norm)
|
|
1075
|
-
|| SESSIONS_PAGE_RE.test(norm) || PROJECT_SETTINGS_PAGE_RE.test(norm)
|
|
1108
|
+
|| SESSIONS_PAGE_RE.test(norm) || PROJECT_SETTINGS_PAGE_RE.test(norm)
|
|
1109
|
+
|| MONEY_PAGE_RE.test(norm)) {
|
|
1076
1110
|
return gdsAuth.requireGovernmentPage(req, res, next);
|
|
1077
1111
|
}
|
|
1078
1112
|
// R16 (goal 1000069) widened the government shell from the manage atom
|
|
@@ -1103,9 +1137,6 @@ function mountInternalSurfaces(app) {
|
|
|
1103
1137
|
if (DEPLOY_PAGE_RE.test(norm)) {
|
|
1104
1138
|
return gdsAuth.requireCorePinPage(req, res, next);
|
|
1105
1139
|
}
|
|
1106
|
-
if (FLEET_PAGE_RE.test(norm)) {
|
|
1107
|
-
return gdsAuth.requireFleetPage(req, res, next);
|
|
1108
|
-
}
|
|
1109
1140
|
next();
|
|
1110
1141
|
});
|
|
1111
1142
|
|
|
@@ -1252,7 +1283,7 @@ function mountInternalSurfaces(app) {
|
|
|
1252
1283
|
// ONE tier of gate: ANY SIGNED-IN BUILDER — the Work Board, Roadmap, the Goals
|
|
1253
1284
|
// page, the task-detail page, the Repo Atlas, Settings
|
|
1254
1285
|
// (#748, #904, task 1293, task 1757, task 751) and, since task 1002710, the
|
|
1255
|
-
// orientation reading
|
|
1286
|
+
// orientation reading room (the primer; task 1005046 folded the diagrams in). A Xenos IS allowed; an
|
|
1256
1287
|
// unauthenticated visitor is bounced into sign-in rather than served the shell.
|
|
1257
1288
|
// Settings is in here (not gated higher) because a newcomer needs it for
|
|
1258
1289
|
// first-time setup — CLI re-auth and choosing their craft both live there
|
|
@@ -1324,9 +1355,10 @@ function mountInternalSurfaces(app) {
|
|
|
1324
1355
|
// whole gate; the governing reads it adds are gated by their own routes.
|
|
1325
1356
|
// task 1004921 `ai-studio` (the viewer's own routes).
|
|
1326
1357
|
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)?|
|
|
1358
|
+
/^\/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
1359
|
// The diagram image assets under /builders/diagrams/ ride the same tier as the
|
|
1329
|
-
// page that embeds them
|
|
1360
|
+
// page that embeds them — the Primer, since task 1005046 retired the Diagrams
|
|
1361
|
+
// page (its URL now 302s to the Primer's first diagram, above).
|
|
1330
1362
|
const SIGNEDIN_BUILDERS_PREFIX_RE = /^\/builders\/diagrams\//;
|
|
1331
1363
|
const MARKET_WATCH_PAGE_RE = /^\/builders\/market-watch(?:-(?:item|rules))?(?:\.html)?\/?$/; // task 1004941: every read behind them is requireBuilder
|
|
1332
1364
|
app.use((req, res, next) => {
|
|
@@ -1348,25 +1380,6 @@ function mountInternalSurfaces(app) {
|
|
|
1348
1380
|
next();
|
|
1349
1381
|
});
|
|
1350
1382
|
|
|
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
1383
|
// task 1965: the read-only Task Board (/board, task 751) was consolidated into
|
|
1371
1384
|
// the Work page (/work) — the hall keeps one task-listing surface. 301 old
|
|
1372
1385
|
// bookmarks + deep-links (the apex /builders/board and the bare subdomain
|
|
@@ -1396,7 +1409,7 @@ function mountInternalSurfaces(app) {
|
|
|
1396
1409
|
|
|
1397
1410
|
// Onboarding diagram PNGs (V3.R76 #295) live in docs/onboarding/diagrams as the
|
|
1398
1411
|
// single source of truth alongside their Mermaid sources. Serve them here so the
|
|
1399
|
-
//
|
|
1412
|
+
// Primer can inline each in its chapter without duplicating the binaries.
|
|
1400
1413
|
// Mounted before buildersStatic so /builders/diagrams/*.png resolves to docs/.
|
|
1401
1414
|
const diagramsStatic = express.static(path.join(ROOT, 'docs/onboarding/diagrams'), {
|
|
1402
1415
|
setHeaders: (res) => res.setHeader('Cache-Control', 'public, max-age=300'),
|
|
@@ -1423,19 +1436,6 @@ function mountInternalSurfaces(app) {
|
|
|
1423
1436
|
res.sendFile(path.join(ROOT, 'docs/onboarding/primer.md'));
|
|
1424
1437
|
});
|
|
1425
1438
|
|
|
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
1439
|
// Rendered API docs (task 1918, BONGOS-V1). A self-describing OpenAPI 3.1 site
|
|
1440
1440
|
// at /docs — cloudbongos.com/docs is the advertised canonical URL, and it works
|
|
1441
1441
|
// on the apex of every instance (mounted in the SHARED surface, so a self-hosted
|
|
@@ -1474,7 +1474,7 @@ function mountInternalSurfaces(app) {
|
|
|
1474
1474
|
if (isModuleSurfaceHost(req)) return next();
|
|
1475
1475
|
if (req.path === '/builders' || req.path.startsWith('/builders/')) {
|
|
1476
1476
|
// Serve HTML pages from the stamped in-memory copy (asset ?v= = content
|
|
1477
|
-
// hash). Gated pages (primer
|
|
1477
|
+
// hash). Gated pages (the primer and the rest) have already passed the sign-in
|
|
1478
1478
|
// gate above before reaching here. Everything else (js/css/png/json,
|
|
1479
1479
|
// unknown paths) falls through to the on-disk static handler.
|
|
1480
1480
|
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.37'; // 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');
|
|
@@ -97,18 +97,19 @@ test('AUDIT: every async middleware mounted directly on a router owns its reject
|
|
|
97
97
|
// requireBoardVotePage / BV1.R107 (and the ADR 0266 /board-room vote gate)
|
|
98
98
|
// requireCorePinPage / ADR 0293's /deploy door — the narrowest of them,
|
|
99
99
|
// on the archon-floor `core.pin.move` atom.
|
|
100
|
-
//
|
|
101
|
-
//
|
|
100
|
+
// Task 1005047 removed requireFleetPage with the /fleet page it fronted (its
|
|
101
|
+
// ledger is a section of /money now, behind the government page floor and the
|
|
102
|
+
// route's own atom). Task 1003450 removed two more — requireNonXenosPage and the R113
|
|
102
103
|
// requireGovernmentManagePage — which had both ended up dispatched from no
|
|
103
104
|
// route at all; tests/hall_page_gate_map.mjs now fails if that recurs. Task
|
|
104
105
|
// 1003444 removed requireProjectCuratePage with the /people page it fronted.
|
|
105
106
|
// Anything else async and router-mounted must be added with its own try/catch.
|
|
106
107
|
const src = fs.readFileSync(path.join(ROOT, 'src', 'bongos', 'auth.js'), 'utf8');
|
|
107
108
|
const asyncMw = [...src.matchAll(/^async function (require\w+|allow\w+)\s*\(req, res, next\)/gm)].map((m) => m[1]);
|
|
108
|
-
assert.deepEqual(asyncMw.sort(), ['requireBoardVotePage', 'requireBuilder', 'requireBuilderPage', 'requireCorePinPage', '
|
|
109
|
+
assert.deepEqual(asyncMw.sort(), ['requireBoardVotePage', 'requireBuilder', 'requireBuilderPage', 'requireCorePinPage', 'requireGovernmentPage'],
|
|
109
110
|
'a new async router middleware appeared — give it a try/catch and add it here');
|
|
110
111
|
// The page gates must actually delegate (that is WHY they are safe).
|
|
111
|
-
for (const name of ['requireBuilderPage', 'requireGovernmentPage', 'requireBoardVotePage', 'requireCorePinPage'
|
|
112
|
+
for (const name of ['requireBuilderPage', 'requireGovernmentPage', 'requireBoardVotePage', 'requireCorePinPage']) {
|
|
112
113
|
const fn = src.slice(src.indexOf(`async function ${name}`));
|
|
113
114
|
assert.match(sliceFn(fn), /runPageGate\(/, `${name} must delegate to the guarded runPageGate`);
|
|
114
115
|
}
|
|
@@ -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
|
});
|
|
@@ -103,24 +103,8 @@ test('no projects renders a sentence, not an empty table', () => {
|
|
|
103
103
|
assert.match(costs.ledgerHtml(null), /No projects are hosted here yet/);
|
|
104
104
|
});
|
|
105
105
|
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
const html = read(path.join(HALL, 'fleet.html'));
|
|
109
|
-
assert.ok(html.indexOf('fleet-costs.js') !== -1 && html.indexOf('fleet-costs.js') < html.indexOf('/fleet.js'), 'fleet-costs.js loads first');
|
|
110
|
-
const js = read(path.join(HALL, 'fleet.js'));
|
|
111
|
-
assert.match(js, /\/provisioning\/fleet\/cost-ledger/);
|
|
112
|
-
assert.match(js, /status === 403[\s\S]{0,200}fl-sealed/, 'a refusal shows the sealed state');
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test('the page gate is the atom the ledger route checks, and the nav mirrors its floor', () => {
|
|
116
|
-
const route = read(path.join(ROOT, 'modules', 'provisioning', 'routes', 'provisioning.js'));
|
|
117
|
-
assert.match(route, /router\.get\('\/provisioning\/fleet\/cost-ledger', auth\.requireBuilder, auth\.requirePermission\('provisioning\.fleet\.manage'\)/);
|
|
118
|
-
assert.match(read(path.join(ROOT, 'src', 'bongos', 'auth.js')), /requireFleetPage: Object\.freeze\(\{ permission: 'provisioning\.fleet\.manage'/);
|
|
119
|
-
// The nav is cosmetic (the page gate is the wall); 'government' is the metic floor.
|
|
120
|
-
const { byKey } = require(path.join(ROOT, 'modules', 'government', 'catalog.js'));
|
|
121
|
-
assert.equal(byKey('provisioning.fleet.manage').floor, 'metic');
|
|
122
|
-
assert.match(read(path.join(HALL, 'shell.js')), /href: '\/fleet', gate: 'government', module: 'provisioning'/);
|
|
123
|
-
});
|
|
106
|
+
// The page's wiring and its gate moved to tests/hall_money_page.mjs with the page
|
|
107
|
+
// (task 1005047): the ledger is the money page's #hosting section, and /fleet redirects.
|
|
124
108
|
|
|
125
109
|
// ── the owner's line on their own project page ───────────────────────────────
|
|
126
110
|
const HUB = read(path.join(ROOT, 'modules', 'public-landing', 'public', 'projects.html'));
|
|
@@ -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_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', '
|
|
86
|
+
for (const page of ['board-room', 'deploy', 'gate', 'government', 'money', '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*)*/, '');
|