@bongos/core 1.24.73 → 1.24.75
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 +90 -70
- package/docs/api/openapi.json +1 -1
- package/docs/api-reference.md +1 -1
- package/docs/copy-inventory.md +13 -13
- package/docs/copy-registry.json +15 -15
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +33 -0
- package/docs/page-readings.json +39 -39
- package/modules/government/board.js +10 -4
- package/modules/government/db.js +7 -5
- package/modules/government/genesis-close.js +45 -0
- package/modules/government/migrations/government_038_vote_cast_via.sql +23 -0
- package/modules/government/routes/government.js +4 -5
- package/modules/hall-ui/public/ai-studio.html +2 -1
- package/modules/hall-ui/public/atlas.html +2 -1
- package/modules/hall-ui/public/blockers.html +2 -1
- package/modules/hall-ui/public/board-room.html +2 -1
- package/modules/hall-ui/public/board-room.js +1 -0
- package/modules/hall-ui/public/city.html +2 -1
- package/modules/hall-ui/public/collab.html +2 -1
- package/modules/hall-ui/public/commands.html +2 -1
- package/modules/hall-ui/public/copy-desk.html +2 -1
- package/modules/hall-ui/public/deploy.html +2 -1
- package/modules/hall-ui/public/genesis-home.js +7 -12
- package/modules/hall-ui/public/goal-map.html +2 -1
- package/modules/hall-ui/public/goals.html +2 -1
- package/modules/hall-ui/public/government.html +2 -1
- package/modules/hall-ui/public/idea.html +2 -1
- package/modules/hall-ui/public/ideas.html +2 -1
- package/modules/hall-ui/public/index.html +2 -1
- package/modules/hall-ui/public/market-watch-item.html +2 -1
- package/modules/hall-ui/public/market-watch-rules.html +2 -1
- package/modules/hall-ui/public/market-watch.html +2 -1
- package/modules/hall-ui/public/modules.html +2 -1
- package/modules/hall-ui/public/money.html +2 -1
- package/modules/hall-ui/public/my-agents.html +2 -1
- package/modules/hall-ui/public/primer.html +2 -1
- package/modules/hall-ui/public/profile.html +2 -1
- package/modules/hall-ui/public/project-settings.html +2 -1
- package/modules/hall-ui/public/roster.html +2 -1
- package/modules/hall-ui/public/settings.html +2 -1
- package/modules/hall-ui/public/shell-game.js +50 -0
- package/modules/hall-ui/public/shell.js +11 -11
- package/modules/hall-ui/public/street.html +2 -1
- package/modules/hall-ui/public/studio.html +2 -1
- package/modules/hall-ui/public/task.html +2 -1
- package/modules/hall-ui/public/thinking.html +2 -1
- package/modules/hall-ui/public/tweak-editor.html +2 -1
- package/modules/hall-ui/public/watch.html +2 -1
- package/modules/hall-ui/public/work.html +2 -1
- package/modules/hall-ui/records/game-mode.md +20 -0
- package/modules/onboarding/genesis-home.js +65 -8
- package/modules/onboarding/routes/onboarding.js +15 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/bongos/government-walk.js +3 -3
- package/src/module-api.js +1 -1
- package/tests/genesis_home.mjs +86 -1
- package/tests/goal_map_page.mjs +2 -2
- package/tests/government_board_genesis_stage.mjs +83 -0
- package/tests/government_seed.mjs +1 -0
- package/tests/hall_city.mjs +1 -1
- package/tests/hall_fullscreen.mjs +101 -0
- package/tests/hall_my_agents.mjs +1 -1
- package/tests/hall_street.mjs +1 -1
- package/tests/nav_permission_atoms.mjs +1 -1
|
@@ -326,10 +326,25 @@ async function applyGovernmentChoice(form, builderId, s) {
|
|
|
326
326
|
return { state: 'not_filed', form, reason };
|
|
327
327
|
}
|
|
328
328
|
|
|
329
|
-
// closeStage —
|
|
330
|
-
//
|
|
331
|
-
//
|
|
332
|
-
//
|
|
329
|
+
// closeStage — CLOSE the current stage, in one call (task 1005121; owner ruling
|
|
330
|
+
// 2026-11-07: "closing a stage shouldn't be such a big thing"). The answer says, by
|
|
331
|
+
// itself, which of two things happened — a caller never needs a second request:
|
|
332
|
+
//
|
|
333
|
+
// { ok: true, closed: true, stage, next_stage, item_id, confirm }
|
|
334
|
+
// The stage is closed and the project has moved on (`next_stage` null at
|
|
335
|
+
// legislation: the alive door, POST /founding/alive, is open instead).
|
|
336
|
+
// `confirm` is 'stage_close' when the founder's own yes carried it — the
|
|
337
|
+
// government port records that HOW on the vote (cast_via).
|
|
338
|
+
// { ok: false, closed: false, reason: 'sitting_open', stage, item_id, already_open,
|
|
339
|
+
// waiting: { pass_rule, needs, voted, waiting_on, closes_at, remaining_seconds },
|
|
340
|
+
// next }
|
|
341
|
+
// A real Board Room sitting is deciding it (more than one person, or a rule
|
|
342
|
+
// one yes cannot carry). Never ok: true — the stage did NOT close. The route
|
|
343
|
+
// answers 202 with this whole body; `next` says what to do, in words.
|
|
344
|
+
//
|
|
345
|
+
// With a government module the port's closeGenesisStage does the deciding (its
|
|
346
|
+
// finished-tasks gate, the founder's confirm where one person decides). With none,
|
|
347
|
+
// the founder's ask closes a finished stage itself. Same rule at every stage.
|
|
333
348
|
async function closeStage(stageKey, builderId, deps = {}) {
|
|
334
349
|
const s = seams(deps);
|
|
335
350
|
const mode = await reconcile(s);
|
|
@@ -337,17 +352,59 @@ async function closeStage(stageKey, builderId, deps = {}) {
|
|
|
337
352
|
if (mode.stage !== stageKey) return { ok: false, reason: 'not_current_stage', stage: mode.stage };
|
|
338
353
|
const all = s.lifecycle && typeof s.lifecycle.genesisTasks === 'function' ? await s.lifecycle.genesisTasks() : {};
|
|
339
354
|
const list = all[stageKey] || [];
|
|
355
|
+
const taskIds = list.map((t) => t.id);
|
|
340
356
|
const gov = s.government;
|
|
357
|
+
const nextOf = (key) => (key === 'legislation' ? null : fm.STAGES[fm.STAGES.indexOf(key) + 1]);
|
|
358
|
+
const closedAnswer = async (itemId, confirm) => {
|
|
359
|
+
const after = await reconcile(s);
|
|
360
|
+
return {
|
|
361
|
+
ok: true, closed: true, stage: stageKey,
|
|
362
|
+
next_stage: stageKey === 'legislation' ? null : (after && after.stage) || nextOf(stageKey),
|
|
363
|
+
item_id: itemId, confirm,
|
|
364
|
+
};
|
|
365
|
+
};
|
|
366
|
+
if (gov && typeof gov.closeGenesisStage === 'function') {
|
|
367
|
+
const r = await gov.closeGenesisStage({ stageKey, taskIds, closedBy: builderId });
|
|
368
|
+
if (r.reason) return { ok: false, reason: r.reason, unfinished: r.unfinished || [] };
|
|
369
|
+
const itemId = r.item ? String(r.item.id) : null;
|
|
370
|
+
if (r.closed) return closedAnswer(itemId, r.cast ? r.cast.cast_via : null);
|
|
371
|
+
return sittingOpen(stageKey, itemId, !r.opened, r.status, builderId);
|
|
372
|
+
}
|
|
341
373
|
if (gov && typeof gov.openGenesisStageClose === 'function') {
|
|
342
|
-
|
|
374
|
+
// An older government module without the one-call close: the sitting opens and
|
|
375
|
+
// the answer says so, rather than claiming a close that did not happen.
|
|
376
|
+
const r = await gov.openGenesisStageClose({ stageKey, taskIds, openedBy: builderId });
|
|
343
377
|
if (!r.opened && r.reason !== 'already_open') return { ok: false, reason: r.reason, unfinished: r.unfinished || [] };
|
|
344
|
-
|
|
378
|
+
const board = await boardByStage(gov);
|
|
379
|
+
return sittingOpen(stageKey, r.item ? String(r.item.id) : null, !r.opened, board[stageKey] || null, builderId);
|
|
345
380
|
}
|
|
346
381
|
if (!list.length || !list.every((t) => FINISHED.includes(t.status))) return { ok: false, reason: 'stage_tasks_unfinished' };
|
|
347
382
|
if (stageKey !== 'legislation') {
|
|
348
|
-
await fm.setFoundingMode({ stage:
|
|
383
|
+
await fm.setFoundingMode({ stage: nextOf(stageKey) }, builderId, modeDeps(s));
|
|
349
384
|
}
|
|
350
|
-
return { ok: true, item_id: null,
|
|
385
|
+
return { ok: true, closed: true, stage: stageKey, next_stage: nextOf(stageKey), item_id: null, confirm: null };
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// The "a sitting is deciding it" answer: what happened, what it waits on, what next.
|
|
389
|
+
function sittingOpen(stageKey, itemId, alreadyOpen, status, builderId) {
|
|
390
|
+
const st = status || {};
|
|
391
|
+
const waitingOn = Array.isArray(st.waiting_on) ? st.waiting_on : [];
|
|
392
|
+
const names = waitingOn.map((m) => m.display_name || m.github_login || `builder ${m.builder_id}`);
|
|
393
|
+
const callerWaited = waitingOn.some((m) => String(m.builder_id) === String(builderId));
|
|
394
|
+
let next = `The Board Room is deciding this stage (sitting ${itemId || 'open'}).`;
|
|
395
|
+
if (st.needs) next += ` Its rule: ${st.needs}`;
|
|
396
|
+
if (names.length) next += ` Still to vote: ${names.join(', ')}.`;
|
|
397
|
+
if (callerWaited && itemId) next += ` Your vote is one of them: POST /api/bongos/government/board/items/${itemId}/votes { "direction": "yes" }.`;
|
|
398
|
+
next += ' The stage moves on by itself when the sitting passes; there is nothing else to call.';
|
|
399
|
+
return {
|
|
400
|
+
ok: false, closed: false, reason: 'sitting_open', stage: stageKey, item_id: itemId, already_open: !!alreadyOpen,
|
|
401
|
+
waiting: {
|
|
402
|
+
pass_rule: st.pass_rule || null, needs: st.needs || null,
|
|
403
|
+
voted: Array.isArray(st.voted) ? st.voted : [], waiting_on: waitingOn,
|
|
404
|
+
closes_at: st.closes_at || null, remaining_seconds: st.remaining_seconds ?? null,
|
|
405
|
+
},
|
|
406
|
+
next,
|
|
407
|
+
};
|
|
351
408
|
}
|
|
352
409
|
|
|
353
410
|
// comeAlive — the last door. Only once legislation has passed (or, with no Board Room,
|
|
@@ -170,6 +170,20 @@ function mountGenesisRoutes(router) {
|
|
|
170
170
|
})) return;
|
|
171
171
|
next();
|
|
172
172
|
}, handle((req) => genesis.savePlan(req.body || {}, req.builder.id)));
|
|
173
|
-
|
|
173
|
+
// task 1005121: one call. 200 { closed: true, next_stage } when the stage closed;
|
|
174
|
+
// 202 { ok: false, closed: false, reason: 'sitting_open', waiting, next } when a
|
|
175
|
+
// real sitting is deciding it — never a success for a stage that did not close.
|
|
176
|
+
router.post('/founding/stages/:stage/close', api.requireBuilder, owns, async (req, res) => {
|
|
177
|
+
try {
|
|
178
|
+
const r = await genesis.closeStage(String(req.params.stage), req.builder.id);
|
|
179
|
+
res.set('Cache-Control', 'no-store');
|
|
180
|
+
if (r && r.reason === 'sitting_open') return res.status(202).json(r);
|
|
181
|
+
if (r && r.ok === false) return refuse(res, r);
|
|
182
|
+
res.json(r);
|
|
183
|
+
} catch (err) {
|
|
184
|
+
api.logger('onboarding').error('[onboarding] genesis', err);
|
|
185
|
+
res.status(500).json({ error: { code: 'genesis_failed', message: 'internal error' } });
|
|
186
|
+
}
|
|
187
|
+
});
|
|
174
188
|
router.post('/founding/alive', api.requireBuilder, owns, handle((req) => genesis.comeAlive(req.builder.id)));
|
|
175
189
|
}
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.75",
|
|
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.75",
|
|
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.75",
|
|
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
|
@@ -11463,5 +11463,17 @@
|
|
|
11463
11463
|
"id": "1005140",
|
|
11464
11464
|
"text": "When a project's board votes in a new form of government, that choice now stays in force after the server restarts. Before, it was quietly lost on every restart and the project fell back to the original one-person rule. A proj"
|
|
11465
11465
|
}
|
|
11466
|
+
],
|
|
11467
|
+
"1.24.74": [
|
|
11468
|
+
{
|
|
11469
|
+
"id": "1005142",
|
|
11470
|
+
"text": "The hall has a game mode: once it is on, every page opens full screen with no sidebar, and Escape brings the menus back when you need them."
|
|
11471
|
+
}
|
|
11472
|
+
],
|
|
11473
|
+
"1.24.75": [
|
|
11474
|
+
{
|
|
11475
|
+
"id": "1005121",
|
|
11476
|
+
"text": "Closing a project-setup stage is now one step: if the founder alone decides, the stage closes and the project moves on at once; if others must vote, the answer says who and what to do next instead of falsely reporting success."
|
|
11477
|
+
}
|
|
11466
11478
|
]
|
|
11467
11479
|
}
|
|
@@ -801,12 +801,12 @@ async function runWalk() {
|
|
|
801
801
|
for (const stage of genesis.PROPER) {
|
|
802
802
|
store.tasks.get(STAGE_TASK_IDS[stage]).status = 'shipped';
|
|
803
803
|
let itemId = null;
|
|
804
|
-
await check(`${stage}: its finished stage is put to the board`, 'a sitting under the chosen government', async () => {
|
|
804
|
+
await check(`${stage}: its finished stage is put to the board`, '202 not closed, a sitting under the chosen government', async () => {
|
|
805
805
|
const r = await call(FOUNDER, 'POST', `/founding/stages/${stage}/close`);
|
|
806
806
|
itemId = r.json && r.json.item_id;
|
|
807
807
|
const snap = itemId && itemOf(itemId) ? itemOf(itemId).constitution : {};
|
|
808
|
-
const reads = charter.readCharter(snap).form;
|
|
809
|
-
return { got: `${r.status}, sitting under ${reads}`, pass: r.status ===
|
|
808
|
+
const reads = charter.readCharter(snap).form, notClosed = !!r.json && r.json.closed === false && r.json.reason === 'sitting_open'; // task 1005121: never a 200 for an open stage
|
|
809
|
+
return { got: `${r.status} ${notClosed ? 'not closed' : 'closed?'}, a sitting under ${reads}`, pass: r.status === 202 && notClosed && !!itemId && reads === CHOSEN };
|
|
810
810
|
});
|
|
811
811
|
await check(`${stage}: the founder alone cannot close it; every trusted member's yes does`, 'open, open, passed', async () => {
|
|
812
812
|
const after = await voteAll(itemId, timocrats);
|
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.75'; // 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');
|
package/tests/genesis_home.mjs
CHANGED
|
@@ -205,8 +205,13 @@ test('decide → five stages through the Board Room → alive, and the hall open
|
|
|
205
205
|
w.finish(s);
|
|
206
206
|
assert.equal((await gh.readGenesis(w.deps())).can_close, true);
|
|
207
207
|
const closed = await gh.closeStage(s, 1, w.deps());
|
|
208
|
-
|
|
208
|
+
// task 1005121: a module without the one-call close only OPENS a sitting, and
|
|
209
|
+
// the answer must say so — never ok: true for a stage that did not close.
|
|
210
|
+
assert.equal(closed.ok, false);
|
|
211
|
+
assert.equal(closed.closed, false);
|
|
212
|
+
assert.equal(closed.reason, 'sitting_open');
|
|
209
213
|
assert.equal(closed.item_id, '9');
|
|
214
|
+
assert.match(closed.next, /moves on by itself/);
|
|
210
215
|
assert.deepEqual(w.opened.at(-1), { stageKey: s, taskIds: [String(100 + STAGES5.indexOf(s))] }, 'the stage\'s own task ids go to the gate');
|
|
211
216
|
g = await gh.readGenesis(w.deps());
|
|
212
217
|
assert.equal(g.stage, s, 'sitting is not passing: the stage holds until the vote');
|
|
@@ -355,3 +360,83 @@ test('the hall carries the five life plates byte-identical to the world\'s', ()
|
|
|
355
360
|
}
|
|
356
361
|
}
|
|
357
362
|
});
|
|
363
|
+
|
|
364
|
+
// ── task 1005121: closing a stage is ONE call ────────────────────────────────
|
|
365
|
+
|
|
366
|
+
function oneCallWorld({ solo = true } = {}) {
|
|
367
|
+
const w = world();
|
|
368
|
+
const calls = [];
|
|
369
|
+
w.deps = ((base) => () => {
|
|
370
|
+
const d = base();
|
|
371
|
+
d.government = {
|
|
372
|
+
...d.government,
|
|
373
|
+
closeGenesisStage: async ({ stageKey, taskIds, closedBy }) => {
|
|
374
|
+
calls.push({ stageKey, taskIds, closedBy });
|
|
375
|
+
if (!(w.tasks[stageKey] || []).every((t) => ['shipped', 'abandoned'].includes(t.status))) {
|
|
376
|
+
return { closed: false, reason: 'stage_tasks_unfinished', unfinished: [{ task_id: taskIds[0] }] };
|
|
377
|
+
}
|
|
378
|
+
if (solo) {
|
|
379
|
+
w.board[stageKey] = { state: 'passed', item_id: '9', waiting_on: [] };
|
|
380
|
+
return { closed: true, item: { id: 9 }, stage: { key: stageKey }, cast: { cast_via: 'stage_close', fresh: true } };
|
|
381
|
+
}
|
|
382
|
+
const status = {
|
|
383
|
+
key: stageKey, state: 'sitting', item_id: '9', pass_rule: 'consent', needs: 'Passes unless someone objects.',
|
|
384
|
+
voted: [], waiting_on: [{ builder_id: '1', display_name: 'Founder' }, { builder_id: '2', github_login: 'avery' }],
|
|
385
|
+
closes_at: 'T', remaining_seconds: 600,
|
|
386
|
+
};
|
|
387
|
+
w.board[stageKey] = status;
|
|
388
|
+
return { closed: false, item: { id: 9 }, stage: { key: stageKey }, opened: true, status, cast: null };
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
return d;
|
|
392
|
+
})(w.deps);
|
|
393
|
+
return { w, calls };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
test('task 1005121: one call closes the stage and moves the project on, at every stage, the same way', async () => {
|
|
397
|
+
const { w, calls } = oneCallWorld();
|
|
398
|
+
await gh.startFounding(1, w.deps());
|
|
399
|
+
await gh.savePlan({}, 1, w.deps());
|
|
400
|
+
for (const s of STAGES5) {
|
|
401
|
+
assert.equal((await gh.closeStage(s, 1, w.deps())).reason, 'stage_tasks_unfinished');
|
|
402
|
+
w.finish(s);
|
|
403
|
+
const r = await gh.closeStage(s, 1, w.deps());
|
|
404
|
+
assert.equal(r.ok, true, `${s}: one call`);
|
|
405
|
+
assert.equal(r.closed, true);
|
|
406
|
+
assert.equal(r.stage, s);
|
|
407
|
+
assert.equal(r.confirm, 'stage_close', "the founder's confirm carried it, and the answer says how");
|
|
408
|
+
const next = s === 'legislation' ? null : STAGES5[STAGES5.indexOf(s) + 1];
|
|
409
|
+
assert.equal(r.next_stage, next, 'the answer names the next stage');
|
|
410
|
+
const g = await gh.readGenesis(w.deps());
|
|
411
|
+
assert.equal(g.stage, next || 'legislation', 'no second request: the project has already moved on');
|
|
412
|
+
}
|
|
413
|
+
assert.ok(calls.every((c) => String(c.closedBy) === '1'), 'the caller is who confirms');
|
|
414
|
+
assert.equal((await gh.readGenesis(w.deps())).can_come_alive, true, 'legislation closed: the alive door is open');
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test('task 1005121: where a real sitting decides, the answer is not ok and says what it waits on and what to do', async () => {
|
|
418
|
+
const { w } = oneCallWorld({ solo: false });
|
|
419
|
+
await gh.startFounding(1, w.deps());
|
|
420
|
+
await gh.savePlan({}, 1, w.deps());
|
|
421
|
+
w.finish('scope');
|
|
422
|
+
const r = await gh.closeStage('scope', 1, w.deps());
|
|
423
|
+
assert.equal(r.ok, false, 'never ok: true for a stage that did not close');
|
|
424
|
+
assert.equal(r.closed, false);
|
|
425
|
+
assert.equal(r.reason, 'sitting_open');
|
|
426
|
+
assert.equal(r.item_id, '9');
|
|
427
|
+
assert.equal(r.waiting.pass_rule, 'consent');
|
|
428
|
+
assert.deepEqual(r.waiting.waiting_on.map((m) => m.builder_id), ['1', '2']);
|
|
429
|
+
assert.match(r.next, /Founder, avery/);
|
|
430
|
+
assert.ok(r.next.includes('Your vote is one of them: POST /api/bongos/government/board/items/9/votes'), r.next);
|
|
431
|
+
assert.equal((await gh.readGenesis(w.deps())).stage, 'scope', 'the stage holds until the sitting passes');
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
test('task 1005121: the close route answers 202 with the whole body for an open sitting, never a 200', () => {
|
|
435
|
+
const src = fs.readFileSync(path.join(ROOT, 'modules', 'onboarding', 'routes', 'onboarding.js'), 'utf8');
|
|
436
|
+
const at = src.indexOf("router.post('/founding/stages/:stage/close'");
|
|
437
|
+
const route = src.slice(at, at + 700);
|
|
438
|
+
assert.ok(route.includes("r.reason === 'sitting_open') return res.status(202).json(r)"), 'an open sitting is a 202 with the whole body');
|
|
439
|
+
const hall = fs.readFileSync(path.join(ROOT, 'modules', 'hall-ui', 'public', 'genesis-home.js'), 'utf8');
|
|
440
|
+
const click = hall.slice(hall.indexOf("t.hasAttribute('data-genesis-close')"), hall.indexOf("t.hasAttribute('data-genesis-confirm')"));
|
|
441
|
+
assert.ok(click.length > 0 && !click.includes('/votes'),'the hall no longer casts a second vote after closing — the server did it');
|
|
442
|
+
});
|
package/tests/goal_map_page.mjs
CHANGED
|
@@ -434,9 +434,9 @@ await test('the page is the sky\'s own surface — the same sheet and the same s
|
|
|
434
434
|
assert.match(html, /<main id="hall-main" class="sky-stage">/, 'the sky is drawn in the stage, the skip link\'s target');
|
|
435
435
|
const scripts = (h) => [...h.matchAll(/<script src="\/builders\/([^"?]+)\?v=([^"]+)"/g)].map((m) => [m[1], m[2]]);
|
|
436
436
|
// criterion-uat-ui.js (task 1004402): the rings' UAT state words, shared with the goal page.
|
|
437
|
-
assert.deepEqual(scripts(html).filter((s) => !['dom-utils.js', 'shell-trail.js', 'craft-mode.js', 'shell.js'].includes(s[0])).map((s) => s[0]), ['idea-objects.js', 'criterion-uat-ui.js', 'sky-data.js', 'sky-layout.js', 'sky-draw.js', 'sky-interaction.js', 'sky-panel.js', 'sky-composer.js', 'sky-tour.js']);
|
|
437
|
+
assert.deepEqual(scripts(html).filter((s) => !['dom-utils.js', 'shell-game.js', 'shell-trail.js', 'craft-mode.js', 'shell.js'].includes(s[0])).map((s) => s[0]), ['idea-objects.js', 'criterion-uat-ui.js', 'sky-data.js', 'sky-layout.js', 'sky-draw.js', 'sky-interaction.js', 'sky-panel.js', 'sky-composer.js', 'sky-tour.js']);
|
|
438
438
|
// Both doors load the shell (tasks 1004327, 1004430); the SKY's files are the same.
|
|
439
|
-
const skyScripts = (h) => scripts(h).filter((s) => !['dom-utils.js', 'shell-trail.js', 'craft-mode.js', 'shell.js'].includes(s[0]));
|
|
439
|
+
const skyScripts = (h) => scripts(h).filter((s) => !['dom-utils.js', 'shell-game.js', 'shell-trail.js', 'craft-mode.js', 'shell.js'].includes(s[0]));
|
|
440
440
|
assert.deepEqual(skyScripts(html), skyScripts(thinking), 'the two doors load the same sky files at the same stamp');
|
|
441
441
|
assert.equal(html.match(/thinking\.css\?v=([^"]+)/)[1], thinking.match(/thinking\.css\?v=([^"]+)/)[1]);
|
|
442
442
|
assert.match(read('thinking.css'), /body\[data-page\] \.sky-stage \{/, 'the sheet dresses the one stage both doors draw in inside the hall');
|
|
@@ -303,3 +303,86 @@ test('the port carries the open and the status; the status route is pinned on th
|
|
|
303
303
|
const pins = fs.readFileSync(path.join(ROOT, 'src', 'bongos', 'route-rank-check.js'), 'utf8');
|
|
304
304
|
assert.match(pins, /'GET \/government\/board\/genesis-stages': 'perm:board\.vote\.cast'/);
|
|
305
305
|
});
|
|
306
|
+
|
|
307
|
+
// ── task 1005121: closing a stage is ONE call ────────────────────────────────
|
|
308
|
+
|
|
309
|
+
const { closeGenesisStage } = require(path.join(GOV, 'genesis-close.js'));
|
|
310
|
+
|
|
311
|
+
test('task 1005121: under Monarchy / BDFL closeGenesisStage opens, confirms and CLOSES in one call, and records HOW', async () => {
|
|
312
|
+
const votes = [];
|
|
313
|
+
let karma = 0;
|
|
314
|
+
const passed = await capturing('board.genesis_stage.passed', () => withDb({
|
|
315
|
+
getTaskStatuses: async () => [{ id: '101', status: 'shipped' }],
|
|
316
|
+
openBoardItem: async (args) => ({ ...stageItem(), constitution: MONARCHY, opened_by: args.openedBy }),
|
|
317
|
+
getBoardItemForVote: async () => stageItem(),
|
|
318
|
+
builderHoldsRankActive: async (id) => id === FOUNDER,
|
|
319
|
+
getActiveBuildersWithRank: async () => [member(FOUNDER, 'founder')],
|
|
320
|
+
getBoardItemAuthor: async (item) => item.opened_by,
|
|
321
|
+
insertBoardVote: async (row) => { const v = { ...row, voter_id: row.voterId, cast_via: row.castVia }; votes.push(v); return v; },
|
|
322
|
+
insertVoteKarma: async () => { karma += 1; },
|
|
323
|
+
getVotesForItem: async () => votes,
|
|
324
|
+
closeBoardItem: async ({ outcome }) => ({ ...stageItem(), closed_at: 'T', outcome }),
|
|
325
|
+
insertAuthorRatifyKarma: async () => true,
|
|
326
|
+
}, async () => {
|
|
327
|
+
const out = await closeGenesisStage({ stageKey: 'charter', taskIds: [101], closedBy: FOUNDER });
|
|
328
|
+
assert.equal(out.closed, true, 'the answer says CLOSED — no second request needed');
|
|
329
|
+
assert.equal(out.stage.key, 'charter');
|
|
330
|
+
assert.equal(out.cast.cast_via, 'stage_close');
|
|
331
|
+
}));
|
|
332
|
+
assert.equal(votes.length, 1);
|
|
333
|
+
assert.equal(votes[0].castVia, 'stage_close', 'the vote row records it was a confirm given by closing the stage');
|
|
334
|
+
assert.equal(votes[0].direction, 'yes');
|
|
335
|
+
assert.equal(karma, 0, 'the confirm decided nothing the ask had not — no vote karma');
|
|
336
|
+
assert.equal(passed.length, 1, 'the genesis home hears the pass');
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test('task 1005121: where one yes cannot carry it, nothing is cast and the answer says what the sitting waits on', async () => {
|
|
340
|
+
let inserted = 0;
|
|
341
|
+
await withDb({
|
|
342
|
+
getTaskStatuses: async () => [{ id: '101', status: 'shipped' }],
|
|
343
|
+
openBoardItem: async () => ({ ...stageItem({ constitution: CONSENT }) }),
|
|
344
|
+
insertBoardVote: async () => { inserted += 1; return null; },
|
|
345
|
+
listLatestGenesisStageItems: async () => [stageItem({ constitution: CONSENT })],
|
|
346
|
+
getVotesForItems: async () => [],
|
|
347
|
+
getActiveBuildersWithRank: async () => [member(FOUNDER, 'founder'), member(AVERY, 'avery')],
|
|
348
|
+
}, async () => {
|
|
349
|
+
const out = await closeGenesisStage({ stageKey: 'charter', taskIds: [101], closedBy: FOUNDER });
|
|
350
|
+
assert.equal(out.closed, false);
|
|
351
|
+
assert.equal(out.opened, true);
|
|
352
|
+
assert.equal(out.item.id, '90');
|
|
353
|
+
assert.equal(out.status.state, 'sitting');
|
|
354
|
+
assert.deepEqual(out.status.waiting_on.map((m) => m.builder_id), [FOUNDER, AVERY]);
|
|
355
|
+
assert.ok(out.status.needs, 'the rule, in words');
|
|
356
|
+
});
|
|
357
|
+
assert.equal(inserted, 0, 'no vote is cast on anyone\'s behalf under a rule one person cannot carry');
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test('task 1005121: a refusal from the gate passes straight through, nothing opened', async () => {
|
|
361
|
+
await withDb({
|
|
362
|
+
getTaskStatuses: async () => [{ id: '101', status: 'active' }],
|
|
363
|
+
openBoardItem: async () => { throw new Error('must not be reached'); },
|
|
364
|
+
}, async () => {
|
|
365
|
+
const out = await closeGenesisStage({ stageKey: 'charter', taskIds: [101], closedBy: FOUNDER });
|
|
366
|
+
assert.equal(out.closed, false);
|
|
367
|
+
assert.equal(out.reason, 'stage_tasks_unfinished');
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
test('task 1005121: cast_via stage_close is internal, genesis-only and yes-only; the vote route never passes it', async () => {
|
|
372
|
+
assert.equal((await castVote({ itemId: '90', voterId: FOUNDER, direction: 'yes', castVia: 'proxy' })).reason, 'bad_cast_via');
|
|
373
|
+
await withDb({
|
|
374
|
+
getBoardItemForVote: async () => ({ ...stageItem(), subject_type: 'full_idea' }),
|
|
375
|
+
insertBoardVote: async () => { throw new Error('must not be reached'); },
|
|
376
|
+
}, async () => {
|
|
377
|
+
assert.equal((await castVote({ itemId: '90', voterId: FOUNDER, direction: 'yes', castVia: 'stage_close' })).reason, 'bad_cast_via');
|
|
378
|
+
});
|
|
379
|
+
const routes = fs.readFileSync(path.join(GOV, 'routes', 'government.js'), 'utf8');
|
|
380
|
+
const call = routes.slice(routes.indexOf('const out = await board.castVote({'), routes.indexOf('const out = await board.castVote({') + 300);
|
|
381
|
+
assert.doesNotMatch(call, /castVia|cast_via/, 'a request can never claim its vote was a stage-close confirm');
|
|
382
|
+
assert.ok(routes.includes("closeGenesisStage: require('../genesis-close').closeGenesisStage"), 'the one-call close is on the port');
|
|
383
|
+
const sql = fs.readFileSync(path.join(GOV, 'migrations', 'government_038_vote_cast_via.sql'), 'utf8');
|
|
384
|
+
assert.match(sql, /cast_via text NOT NULL DEFAULT 'ballot'/, 'every vote before the column reads as a ballot');
|
|
385
|
+
assert.match(sql, /cast_via IN \('ballot', 'stage_close'\) AND \(cast_via = 'ballot' OR direction = 'yes'\)/);
|
|
386
|
+
const dbSrc = fs.readFileSync(path.join(GOV, 'db.js'), 'utf8');
|
|
387
|
+
assert.match(dbSrc, /INSERT INTO government_board_votes \(item_id, voter_id, direction, reason_md, section_key, cast_via\)/);
|
|
388
|
+
});
|
|
@@ -54,6 +54,7 @@ const NON_GRANT_MIGRATIONS = new Set([
|
|
|
54
54
|
'government_032_ask_flags.sql', // the reword list: matters a reader flagged as unclear (owner 2026-10-23)
|
|
55
55
|
'government_036_access_holders.sql', // who holds the keys outside Bongos + the access:<key> ask seat (task 1005000)
|
|
56
56
|
'government_037_constitution.sql', // the constitution in force as a durable row (task 1005140)
|
|
57
|
+
'government_038_vote_cast_via.sql', // HOW a vote was cast: a ballot, or a stage-close confirm (task 1005121)
|
|
57
58
|
]);
|
|
58
59
|
|
|
59
60
|
// Permission keys RENAMED after the grant migrations that first granted them.
|
package/tests/hall_city.mjs
CHANGED
|
@@ -289,7 +289,7 @@ test('the city sits in the hall chrome, behind the shell\'s full-screen switch',
|
|
|
289
289
|
// task 1004685 (the living city): gov-living.js, the model under the drawing, and
|
|
290
290
|
// gov-feeds.js, its one reader per page (shared with the shell's tally), load before it
|
|
291
291
|
// task 1004702: the card answers its first ask in place, so the deck's card and sender (and the desk rows under them) load before the page
|
|
292
|
-
assert.deepEqual(scripts, ['dom-utils', 'shell-trail', 'craft-mode', 'shell', 'hall-dialog', 'palette', 'gov-living', 'gov-feeds', 'city-draw', 'hall-kit', 'street-draw', 'street-card', 'street-inside', 'street-desk', 'street-deck', 'city-decide', 'city-switchboard', 'city'], 'the shell first, then the shared dialog (task 1005085), as on every hall page');
|
|
292
|
+
assert.deepEqual(scripts, ['dom-utils', 'shell-game', 'shell-trail', 'craft-mode', 'shell', 'hall-dialog', 'palette', 'gov-living', 'gov-feeds', 'city-draw', 'hall-kit', 'street-draw', 'street-card', 'street-inside', 'street-desk', 'street-deck', 'city-decide', 'city-switchboard', 'city'], 'the shell first, then the shared dialog (task 1005085), as on every hall page');
|
|
293
293
|
// task 1004925: every script but dom-utils.js is deferred, so none holds up the
|
|
294
294
|
// parser; defer runs them in document order, which is why the order above still
|
|
295
295
|
// holds. dom-utils.js stays synchronous: the one-escaper guard
|
|
@@ -25,6 +25,8 @@ const HALL = path.join(ROOT, 'modules/hall-ui/public');
|
|
|
25
25
|
const SHELL = fs.readFileSync(path.join(HALL, 'shell.js'), 'utf8');
|
|
26
26
|
// task 1004492: the way back lives in shell-trail.js, which every page loads first.
|
|
27
27
|
const TRAIL = fs.readFileSync(path.join(HALL, 'shell-trail.js'), 'utf8');
|
|
28
|
+
// task 1005142: game mode, the hall-wide half of the switch; every page loads it before the shell.
|
|
29
|
+
const GAME = fs.readFileSync(path.join(HALL, 'shell-game.js'), 'utf8');
|
|
28
30
|
const read = (f) => fs.readFileSync(path.join(HALL, f), 'utf8');
|
|
29
31
|
|
|
30
32
|
// Boot the shell once — one page load. `store` is the browser's localStorage and
|
|
@@ -125,11 +127,16 @@ function boot({ page = 'thinking', optIn = true, store = {}, storageThrows = fal
|
|
|
125
127
|
};
|
|
126
128
|
sandbox.globalThis = sandbox;
|
|
127
129
|
vm.createContext(sandbox);
|
|
130
|
+
vm.runInContext(GAME, sandbox, { filename: 'shell-game.js' });
|
|
128
131
|
vm.runInContext(TRAIL, sandbox, { filename: 'shell-trail.js' });
|
|
129
132
|
vm.runInContext(SHELL, sandbox, { filename: 'shell.js' });
|
|
130
133
|
const root = documentObj.documentElement;
|
|
131
134
|
return {
|
|
132
135
|
store,
|
|
136
|
+
game: () => windowObj.OTBShellGame,
|
|
137
|
+
gameStamp: () => root.dataset.game === 'on',
|
|
138
|
+
// The "how to leave" line: showing = the shell created it and it is not hidden.
|
|
139
|
+
hintShowing: () => els.has('fullscreen-hint') && els.get('fullscreen-hint').hidden === false,
|
|
133
140
|
button: () => getElementById('fullscreen-toggle'),
|
|
134
141
|
on: () => root.dataset.fullscreen === 'on',
|
|
135
142
|
plain: () => root.dataset.fullscreenPlain === 'on',
|
|
@@ -477,3 +484,97 @@ test('source: full screen hides the sidebar and foot, and collapses the top bar
|
|
|
477
484
|
assert.match(css, /html\[data-fullscreen="on"\] \.topbar-side > :not\(\.fullscreen-toggle\)/);
|
|
478
485
|
assert.doesNotMatch(css, /html\[data-fullscreen="on"\] \.app-topbar \{ display: none/);
|
|
479
486
|
});
|
|
487
|
+
|
|
488
|
+
// GAME MODE (task 1005142, BONGOS-V3 R02). The spec's done-when: no page shows the
|
|
489
|
+
// sidebar in game mode. Game mode is hall-wide — one key, read by the SAME switch
|
|
490
|
+
// — so these drive the real shell-game.js + shell.js the way a reader moves
|
|
491
|
+
// through the hall: page after page over one browser store.
|
|
492
|
+
test('game mode: EVERY page opens without its bars — plain, laid-out, and one that remembers "off"', () => {
|
|
493
|
+
const store = { 'hall-game': 'on', 'hall-fullscreen:goals': 'off' };
|
|
494
|
+
for (const [page, optIn] of [['goals', false], ['work', false], ['thinking', true], ['city', true]]) {
|
|
495
|
+
const p = boot({ page, optIn, store });
|
|
496
|
+
assert.equal(p.on(), true, `${page} opens in full screen`);
|
|
497
|
+
assert.equal(p.gameStamp(), true, `${page}: html[data-game="on"]`);
|
|
498
|
+
assert.equal(p.plain(), optIn === false, `${page}: the plain mark still follows the page`);
|
|
499
|
+
}
|
|
500
|
+
assert.equal(boot({ page: 'goals', optIn: false, store: {} }).on(), false, 'and outside game mode nothing changed');
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
test('game mode: every hall page carries the switch — none opts out, and each loads shell-game.js before the shell', () => {
|
|
504
|
+
const pages = fs.readdirSync(HALL).filter((f) => f.endsWith('.html'));
|
|
505
|
+
assert.ok(pages.length >= 30, `found ${pages.length} hall pages`);
|
|
506
|
+
for (const f of pages) {
|
|
507
|
+
const html = read(f);
|
|
508
|
+
assert.doesNotMatch(html, /data-fullscreen-switch="off"/, `${f} must not opt out: it would keep its sidebar in game mode`);
|
|
509
|
+
assert.match(html, /id="app-topbar"/, `${f}: the top bar is where the shell renders the switch`);
|
|
510
|
+
const tags = [...html.matchAll(/<script src="\/builders\/([a-z-]+)\.js[^"]*"( defer)?><\/script>/g)].map((m) => [m[1], !!m[2]]);
|
|
511
|
+
const game = tags.findIndex((t) => t[0] === 'shell-game');
|
|
512
|
+
const shell = tags.findIndex((t) => t[0] === 'shell');
|
|
513
|
+
assert.ok(game >= 0 && game < shell, `${f} loads shell-game.js before shell.js`);
|
|
514
|
+
assert.equal(tags[game][1], tags[shell][1], `${f}: both load the same way, so the order holds`);
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
test('game mode: leaving brings the bars back for THIS page only, and never rewrites a page\'s own memory', () => {
|
|
519
|
+
const store = { 'hall-game': 'on', 'hall-fullscreen:thinking': 'on' };
|
|
520
|
+
const goals = boot({ page: 'goals', optIn: false, store });
|
|
521
|
+
goals.press('Escape');
|
|
522
|
+
assert.equal(goals.on(), false, 'Escape still leaves');
|
|
523
|
+
assert.equal(store['hall-fullscreen:goals'], undefined, 'nothing remembered for the page');
|
|
524
|
+
assert.equal(boot({ page: 'goals', optIn: false, store }).on(), true, 'the next load opens without bars again');
|
|
525
|
+
const sky = boot({ page: 'thinking', store });
|
|
526
|
+
sky.button().click();
|
|
527
|
+
assert.equal(sky.on(), false, 'the corner button leaves too');
|
|
528
|
+
assert.equal(store['hall-fullscreen:thinking'], 'on', 'the sky\'s own choice survives game mode');
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
test('game mode: the Escape hand-off is unchanged — a press the page used, or one in a field, does not leave', () => {
|
|
532
|
+
const p = boot({ page: 'goals', optIn: false, store: { 'hall-game': 'on' } });
|
|
533
|
+
p.press('Escape', { pageUses: true });
|
|
534
|
+
assert.equal(p.on(), true, 'a page listener that called preventDefault keeps the press');
|
|
535
|
+
p.press('Escape', { target: { tagName: 'TEXTAREA' } });
|
|
536
|
+
assert.equal(p.on(), true);
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
test('game mode: set() switches the page live, and turning it off hands each page back its own memory', () => {
|
|
540
|
+
const store = { 'hall-fullscreen:thinking': 'on' };
|
|
541
|
+
const goals = boot({ page: 'goals', optIn: false, store });
|
|
542
|
+
goals.game().set(true);
|
|
543
|
+
assert.equal(store['hall-game'], 'on');
|
|
544
|
+
assert.equal(goals.on(), true, 'on at once, no reload');
|
|
545
|
+
assert.ok(goals.resizes() >= 1, 'a page that sizes to its box is told to re-measure');
|
|
546
|
+
goals.game().set(false);
|
|
547
|
+
assert.equal(goals.on(), false, 'goals remembered nothing, so its bars return');
|
|
548
|
+
assert.equal(goals.gameStamp(), false);
|
|
549
|
+
assert.equal(store['hall-fullscreen:goals'], undefined, 'and switching the mode wrote nothing for the page');
|
|
550
|
+
const sky = boot({ page: 'thinking', store: Object.assign(store, { 'hall-game': 'on' }) });
|
|
551
|
+
sky.game().set(false);
|
|
552
|
+
assert.equal(sky.on(), true, 'the sky had chosen full screen itself, and keeps it');
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
test('game mode: ?game=on / ?game=off set it from the URL; anything else is ignored', () => {
|
|
556
|
+
const store = {};
|
|
557
|
+
assert.equal(boot({ page: 'work', optIn: false, store, search: '?game=on' }).on(), true);
|
|
558
|
+
assert.equal(store['hall-game'], 'on');
|
|
559
|
+
assert.equal(boot({ page: 'goals', optIn: false, store }).on(), true, 'and it holds on the next page');
|
|
560
|
+
assert.equal(boot({ page: 'work', optIn: false, store, search: '?game=off' }).on(), false);
|
|
561
|
+
boot({ page: 'work', optIn: false, store, search: '?game=%22%3E%3Cimg%3E' });
|
|
562
|
+
assert.equal(store['hall-game'], 'off', 'a value that is not on/off is not stored');
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
test('game mode: how to leave is said once per tab, not on every page; outside it the shell\'s rule stands', () => {
|
|
566
|
+
const store = { 'hall-game': 'on' };
|
|
567
|
+
const session = {};
|
|
568
|
+
assert.equal(boot({ page: 'goals', optIn: false, store, session }).hintShowing(), true, 'the first page says it');
|
|
569
|
+
assert.equal(boot({ page: 'work', optIn: false, store, session }).hintShowing(), false, 'the next does not repeat it');
|
|
570
|
+
const own = { 'hall-fullscreen:thinking': 'on' };
|
|
571
|
+
assert.equal(boot({ page: 'thinking', store: own, session: {} }).hintShowing(), true);
|
|
572
|
+
assert.equal(boot({ page: 'thinking', store: own, session: {} }).hintShowing(), true, 'a remembered page says it on every open, as before');
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
test('game mode: storage that throws leaves the hall out of game mode, with the switch working', () => {
|
|
576
|
+
const p = boot({ page: 'goals', optIn: false, storageThrows: true, search: '?game=on' });
|
|
577
|
+
assert.equal(p.on(), false);
|
|
578
|
+
p.button().click();
|
|
579
|
+
assert.equal(p.on(), true);
|
|
580
|
+
});
|
package/tests/hall_my_agents.mjs
CHANGED
|
@@ -443,7 +443,7 @@ test('PAGE: in the hall family, with no prototype key', () => {
|
|
|
443
443
|
assert.deepEqual(sheets, ['style.css', 'oversight.css', 'speciality-tools.css', 'my-agents.css'],
|
|
444
444
|
'rides the oversight archetype; the shared speciality sheet (task 1005052) before its own, so the page skin wins');
|
|
445
445
|
const scripts = [...html.matchAll(/src="\/builders\/([a-z-]+)\.js/g)].map((m) => m[1]);
|
|
446
|
-
assert.deepEqual(scripts.slice(0,
|
|
446
|
+
assert.deepEqual(scripts.slice(0, 5), ['dom-utils', 'shell-game', 'shell-trail', 'craft-mode', 'shell']); // dom-utils first: the shell escapes through it (task 1004876)
|
|
447
447
|
for (const s of ['gov-living', 'gov-positions', 'my-agents', 'speciality-tools']) assert.ok(scripts.includes(s), `loads ${s}.js`);
|
|
448
448
|
assert.doesNotMatch(html, /ma-key|data-proto|Prototype data/, 'nothing on the page is prototype data, so there is no key');
|
|
449
449
|
assert.doesNotMatch(read('my-agents.js'), /governor-overlay/, 'and it reads no overlay');
|
package/tests/hall_street.mjs
CHANGED
|
@@ -490,7 +490,7 @@ test('the page holds the prototype\'s header, the street, the readings and the c
|
|
|
490
490
|
assert.match(html, /<a class="street-mark" id="streetHome" href="\/builders\/city"[^>]*>\{\{productName\}\}</);
|
|
491
491
|
for (const id of ['hudNeeds', 'hudMoving', 'hudShare', 'streetMode', 'streetSigns', 'streetPins', 'streetDoors', 'streetCard']) assert.match(html, new RegExp(`id="${id}"`), id);
|
|
492
492
|
const scripts = [...html.matchAll(/<script src="\/builders\/([a-z-]+)\.js/g)].map((m) => m[1]);
|
|
493
|
-
assert.deepEqual(scripts.filter((s) => !/^street-(deck|office)$/.test(s)), ['dom-utils', 'shell-trail', 'craft-mode', 'shell', 'hall-dialog', 'palette', 'gov-living', 'gov-feeds', 'city-draw', 'street-draw', 'street-card', 'street-inside', 'street-depth', 'street-desk', 'street-amend', 'street'], 'the hall shell (task 1004544), the living model and its one reader (task 1004685: city-draw.js counts with it, so it loads first), the shared kit, then the card (task 1004528), the interiors (task 1004526), the living street\'s exterior (street-depth.js), the floor desk (task 1004558) and the drafted amendment (task 1004530); the deck rides beside them');
|
|
493
|
+
assert.deepEqual(scripts.filter((s) => !/^street-(deck|office)$/.test(s)), ['dom-utils', 'shell-game', 'shell-trail', 'craft-mode', 'shell', 'hall-dialog', 'palette', 'gov-living', 'gov-feeds', 'city-draw', 'street-draw', 'street-card', 'street-inside', 'street-depth', 'street-desk', 'street-amend', 'street'], 'the hall shell (task 1004544), the living model and its one reader (task 1004685: city-draw.js counts with it, so it loads first), the shared kit, then the card (task 1004528), the interiors (task 1004526), the living street\'s exterior (street-depth.js), the floor desk (task 1004558) and the drafted amendment (task 1004530); the deck rides beside them');
|
|
494
494
|
assert.equal(City.STREET_HREF, '/builders/street', 'the city\'s door is this page');
|
|
495
495
|
});
|
|
496
496
|
|
|
@@ -102,7 +102,7 @@ test('the hall-preview fixture carries every atom /me resolves', () => {
|
|
|
102
102
|
// task 1005051: Roadmap left it (folded into Goals; /roadmap redirects).
|
|
103
103
|
// task 1005070: the remembered nav (the rail paints before /me).
|
|
104
104
|
// task 1005088: the sidebar mark is drawn by reference to the one brand mark.
|
|
105
|
-
const SHELL_SHA = '
|
|
105
|
+
const SHELL_SHA = 'e9c13c142bcd2a95';
|
|
106
106
|
|
|
107
107
|
test('shell.js has not changed without its cache-bust being bumped', () => {
|
|
108
108
|
const actual = crypto.createHash('sha256').update(SHELL).digest('hex').slice(0, 16);
|