@bongos/core 1.20.47 → 1.20.49
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 +108 -73
- package/docs/copy-inventory.md +200 -184
- package/docs/copy-registry.json +364 -220
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +11 -4
- package/docs/page-readings.json +411 -386
- package/modules/hall-ui/public/atlas.html +1 -1
- package/modules/hall-ui/public/blockers.html +1 -1
- package/modules/hall-ui/public/board-room.html +1 -1
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/commands.html +1 -1
- package/modules/hall-ui/public/copy-desk.html +1 -1
- package/modules/hall-ui/public/criterion-uat-ui.js +280 -0
- package/modules/hall-ui/public/deploy.html +1 -1
- package/modules/hall-ui/public/diagrams.html +1 -1
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/fleet.html +1 -1
- package/modules/hall-ui/public/gate.html +1 -1
- package/modules/hall-ui/public/goal-map.html +5 -3
- package/modules/hall-ui/public/goals-page.js +23 -44
- package/modules/hall-ui/public/goals-render.js +24 -15
- package/modules/hall-ui/public/goals.css +45 -0
- package/modules/hall-ui/public/goals.html +6 -3
- package/modules/hall-ui/public/goals.states.json +38 -0
- package/modules/hall-ui/public/government.html +1 -1
- package/modules/hall-ui/public/hall-render.js +3 -0
- package/modules/hall-ui/public/idea.html +1 -1
- package/modules/hall-ui/public/ideas.html +1 -1
- package/modules/hall-ui/public/index.html +1 -1
- package/modules/hall-ui/public/modules.html +1 -1
- package/modules/hall-ui/public/primer.html +1 -1
- package/modules/hall-ui/public/profile.html +1 -1
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/ranks.html +1 -1
- package/modules/hall-ui/public/roadmap.html +1 -1
- package/modules/hall-ui/public/roster.html +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings.html +1 -1
- package/modules/hall-ui/public/shell.js +82 -1
- package/modules/hall-ui/public/sky-data.js +9 -1
- package/modules/hall-ui/public/sky-interaction.js +1 -1
- package/modules/hall-ui/public/sky-panel.js +1 -1
- package/modules/hall-ui/public/studio.html +1 -1
- package/modules/hall-ui/public/style.css +36 -0
- package/modules/hall-ui/public/task.html +1 -1
- package/modules/hall-ui/public/thinking.html +6 -4
- package/modules/hall-ui/public/tweak-editor.html +1 -1
- package/modules/hall-ui/public/watch.html +1 -1
- package/modules/hall-ui/public/work-founding.states.json +39 -0
- package/modules/hall-ui/public/work.html +1 -1
- package/modules/hall-ui/records/founding-mode.md +31 -0
- package/modules/lifecycle/done-when.js +3 -1
- package/modules/lifecycle/sky.js +23 -8
- package/modules/onboarding/founding-mode.js +185 -0
- package/modules/onboarding/port.js +7 -0
- package/modules/ui-design/kit/fixtures/me-founding.json +24 -0
- package/modules/ui-design/kit/serve.js +3 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/hall-preview/server.js +2 -0
- package/src/bongos/routes/me.js +18 -1
- package/src/module-api.js +1 -1
- package/tests/claim_action.mjs +6 -1
- package/tests/criterion_uat_ui.mjs +113 -0
- package/tests/founding_mode.mjs +294 -0
- package/tests/goal_criterion_confirm_ui.mjs +188 -91
- package/tests/goal_map_page.mjs +2 -1
- package/tests/hall_sky.mjs +16 -0
- package/tests/hall_tweak_editor.mjs +1 -1
- package/tests/helpers.mjs +3 -1
- package/tests/nav_permission_atoms.mjs +1 -1
- package/tests/sky_read.mjs +7 -1
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
// tests/founding_mode.mjs — PROJECT FOUNDING MODE is a mode the existing hall wears,
|
|
2
|
+
// never a second hall (task 1004422 / BV2.PS03, goal 1000121; the blend rule in
|
|
3
|
+
// docs/design/project-startup-direction.md).
|
|
4
|
+
//
|
|
5
|
+
// The owner's no-hijack condition is the headline: a hall that is NOT founding must be
|
|
6
|
+
// the hall it was. So the client half is RUN, not grepped. The real applyFounding is
|
|
7
|
+
// sliced out of shell.js and driven over a small fake DOM, fed by the REAL server
|
|
8
|
+
// shaper, so the path from the stored stage to the dimmed nav item is exercised
|
|
9
|
+
// whole. Then OFF is proven to leave, and to restore, the DOM exactly.
|
|
10
|
+
import { strict as assert } from 'node:assert';
|
|
11
|
+
import { test } from 'node:test';
|
|
12
|
+
import { createRequire } from 'node:module';
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
|
|
17
|
+
const require = createRequire(import.meta.url);
|
|
18
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
19
|
+
const read = (...p) => fs.readFileSync(path.join(ROOT, ...p), 'utf8').replace(/\r\n/g, '\n');
|
|
20
|
+
const fm = require(path.join(ROOT, 'modules', 'onboarding', 'founding-mode.js'));
|
|
21
|
+
|
|
22
|
+
const SHELL = read('modules', 'hall-ui', 'public', 'shell.js');
|
|
23
|
+
const HALL_RENDER = read('modules', 'hall-ui', 'public', 'hall-render.js');
|
|
24
|
+
const ME = read('src', 'bongos', 'routes', 'me.js');
|
|
25
|
+
const PORT = read('modules', 'onboarding', 'port.js');
|
|
26
|
+
|
|
27
|
+
// ── the server shaper (pure) ────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
test('not founding in any form is null (OFF)', () => {
|
|
30
|
+
assert.equal(fm.shapeFoundingMode(null), null);
|
|
31
|
+
assert.equal(fm.shapeFoundingMode({ active: null }), null);
|
|
32
|
+
assert.equal(fm.shapeFoundingMode({ active: 'false', stage: 'scope' }), null);
|
|
33
|
+
assert.equal(fm.shapeFoundingMode({ active: 'maybe', stage: 'scope' }), null);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('an unknown stage fails CLOSED — OFF, never a hall stuck half-asleep', () => {
|
|
37
|
+
assert.equal(fm.shapeFoundingMode({ active: 'true', stage: 'launch' }), null);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('active with no stage yet is the decide pre-stage, and every room sleeps', () => {
|
|
41
|
+
const f = fm.shapeFoundingMode({ active: 'true' });
|
|
42
|
+
assert.equal(f.stage, 'decide');
|
|
43
|
+
assert.equal(f.stage_number, 0);
|
|
44
|
+
assert.equal(f.stage_count, 5);
|
|
45
|
+
for (const id of ['roadmap', 'goals', 'goal-map', 'work', 'board-room', 'economy', 'ranks', 'collab', 'roster']) {
|
|
46
|
+
assert.ok(id in f.asleep, `${id} sleeps while deciding`);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('a room wakes once the stage that wakes it is behind', () => {
|
|
51
|
+
const f = fm.shapeFoundingMode({ active: 'true', stage: 'charter' });
|
|
52
|
+
for (const id of ['roadmap', 'goals', 'goal-map', 'work']) assert.ok(!(id in f.asleep), `${id} woke`);
|
|
53
|
+
assert.equal(f.asleep['board-room'], 'charter');
|
|
54
|
+
assert.equal(f.asleep.economy, 'legalize');
|
|
55
|
+
assert.equal(f.asleep.ranks, 'legislation');
|
|
56
|
+
assert.equal(f.asleep.collab, 'alive', 'community wakes only when the planet is alive');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('the day counts from the founding day; an unreadable timestamp drops it', () => {
|
|
60
|
+
const now = new Date('2026-10-09T12:00:00Z');
|
|
61
|
+
assert.equal(fm.dayOf('2026-10-01T09:00:00Z', now), 9);
|
|
62
|
+
assert.equal(fm.dayOf('not a date', now), null);
|
|
63
|
+
assert.equal(fm.dayOf(null, now), null);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('foundingModeFor fails closed when the settings read throws', async () => {
|
|
67
|
+
const got = await fm.foundingModeFor({ getSetting: async () => { throw new Error('db down'); } });
|
|
68
|
+
assert.equal(got, null);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('setFoundingMode refuses an unknown stage and stamps started_at once', async () => {
|
|
72
|
+
const store = new Map();
|
|
73
|
+
const deps = {
|
|
74
|
+
getSetting: async (k) => (store.has(k) ? { key: k, value: store.get(k) } : null),
|
|
75
|
+
setSetting: async (k, v) => { store.set(k, String(v)); return { key: k, value: String(v) }; },
|
|
76
|
+
now: new Date('2026-10-01T00:00:00Z'),
|
|
77
|
+
};
|
|
78
|
+
await assert.rejects(fm.setFoundingMode({ stage: 'launch' }, 1, deps), /unknown founding stage/);
|
|
79
|
+
const first = await fm.setFoundingMode({ active: true, stage: 'decide' }, 1, deps);
|
|
80
|
+
assert.equal(first.stage, 'decide');
|
|
81
|
+
const stamp = store.get(fm.KEYS.STARTED_AT);
|
|
82
|
+
deps.now = new Date('2026-10-05T00:00:00Z');
|
|
83
|
+
await fm.setFoundingMode({ active: true, stage: 'scope' }, 1, deps);
|
|
84
|
+
assert.equal(store.get(fm.KEYS.STARTED_AT), stamp, 'started_at is not re-stamped');
|
|
85
|
+
assert.equal(await fm.setFoundingMode({ active: false }, 1, deps), null, 'switching off reads back as OFF');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('every room the server can put to sleep is a real NAV item', () => {
|
|
89
|
+
// The map is keyed by NAV id so the client never re-derives it; a renamed NAV id
|
|
90
|
+
// would silently leave a room awake. Hold the two together.
|
|
91
|
+
const nav = SHELL.slice(SHELL.indexOf('var NAV = ['), SHELL.indexOf('var NAV_COLLAPSE_KEY'));
|
|
92
|
+
const ids = new Set([...nav.matchAll(/id: '([a-z-]+)'/g)].map((m) => m[1]));
|
|
93
|
+
const all = [...Object.values(fm.WAKES).flat(), ...fm.WAKES_WHEN_ALIVE];
|
|
94
|
+
for (const id of all) assert.ok(ids.has(id), `NAV has no item '${id}'`);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// ── the client, RUN over a fake DOM ─────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
class El {
|
|
100
|
+
constructor(tag) {
|
|
101
|
+
this.tag = tag; this.id = ''; this.className = ''; this.dataset = {}; this.attrs = {};
|
|
102
|
+
this.children = []; this.parent = null; this.textContent = ''; this.innerHTML = '';
|
|
103
|
+
const self = this;
|
|
104
|
+
this.classList = {
|
|
105
|
+
add(c) { const s = new Set(self.className.split(' ').filter(Boolean)); s.add(c); self.className = [...s].join(' '); },
|
|
106
|
+
remove(c) { self.className = self.className.split(' ').filter((x) => x && x !== c).join(' '); },
|
|
107
|
+
contains(c) { return self.className.split(' ').includes(c); },
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
appendChild(c) { c.parent = this; this.children.push(c); return c; }
|
|
111
|
+
insertBefore(c, ref) { c.parent = this; const i = this.children.indexOf(ref); this.children.splice(i < 0 ? 0 : i, 0, c); return c; }
|
|
112
|
+
get parentNode() { return this.parent; }
|
|
113
|
+
removeChild(c) { this.children = this.children.filter((x) => x !== c); c.parent = null; return c; }
|
|
114
|
+
setAttribute(k, v) { this.attrs[k] = v; }
|
|
115
|
+
querySelector(sel) { return sel === '[data-wakes-tag]' ? (this.children.find((c) => 'data-wakes-tag' in c.attrs) || null) : null; }
|
|
116
|
+
get firstChild() { return this.children[0] || null; }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function makeHall() {
|
|
120
|
+
const navIds = ['home', 'roadmap', 'goals', 'goal-map', 'work', 'board-room', 'economy', 'ranks', 'collab', 'roster', 'profile'];
|
|
121
|
+
const nav = navIds.map((id) => { const a = new El('a'); a.id = 'nav-' + id; a.className = 'nav-item'; return a; });
|
|
122
|
+
const appBody = new El('div');
|
|
123
|
+
const topbar = new El('header'); topbar.id = 'app-topbar';
|
|
124
|
+
appBody.appendChild(topbar);
|
|
125
|
+
const root = { dataset: {} };
|
|
126
|
+
const document = {
|
|
127
|
+
documentElement: root,
|
|
128
|
+
createElement: (t) => new El(t),
|
|
129
|
+
querySelector: (sel) => (sel === '.app-body' ? appBody : null),
|
|
130
|
+
querySelectorAll: (sel) => (sel === '#app-sidebar .nav-item--asleep' ? nav.filter((a) => a.classList.contains('nav-item--asleep')) : []),
|
|
131
|
+
getElementById: (id) => nav.find((a) => a.id === id) || appBody.children.find((c) => c.id === id) || null,
|
|
132
|
+
};
|
|
133
|
+
return { document, nav, appBody, root };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// The REAL escaper and the real wake map are sliced out of shell.js with the function,
|
|
137
|
+
// so the band's escaping is the shell's own, not a stand-in that agrees with the test.
|
|
138
|
+
function sliceFn(name) {
|
|
139
|
+
const start = SHELL.indexOf(' function ' + name + '(');
|
|
140
|
+
const end = SHELL.indexOf('\n }\n', start);
|
|
141
|
+
assert.ok(start > -1 && end > start, name + ' was located in shell.js');
|
|
142
|
+
return SHELL.slice(start, end + 4);
|
|
143
|
+
}
|
|
144
|
+
function loadApplyFounding(document, access, brand = { identity: { productName: 'Kestrel' } }) {
|
|
145
|
+
const wakeStart = SHELL.indexOf(' var WAKE_TEXT = {');
|
|
146
|
+
const wakeEnd = SHELL.indexOf('};', wakeStart) + 2;
|
|
147
|
+
assert.ok(wakeStart > -1, 'WAKE_TEXT was located in shell.js');
|
|
148
|
+
const src = [SHELL.slice(wakeStart, wakeEnd), sliceFn('esc'), sliceFn('applyFounding')].join('\n');
|
|
149
|
+
return new Function('document', 'brand', 'access', `${src}\nreturn applyFounding;`)(document, brand, access);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const snapshot = (h) => JSON.stringify({
|
|
153
|
+
root: h.root.dataset,
|
|
154
|
+
body: h.appBody.children.map((c) => c.id),
|
|
155
|
+
nav: h.nav.map((a) => ({ id: a.id, cls: a.className, data: a.dataset, kids: a.children.length })),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('THE NO-HIJACK CONDITION: a hall that is not founding is untouched', () => {
|
|
159
|
+
const h = makeHall();
|
|
160
|
+
const before = snapshot(h);
|
|
161
|
+
const access = { founding: null };
|
|
162
|
+
loadApplyFounding(h.document, access)();
|
|
163
|
+
assert.equal(snapshot(h), before, 'null founding changes nothing at all');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('not founding touches NO node — the document is never even queried', () => {
|
|
167
|
+
// Stronger than "ends up the same": a page that never wore the mode is not read.
|
|
168
|
+
const h = makeHall();
|
|
169
|
+
const touched = [];
|
|
170
|
+
const spy = new Proxy(h.document, {
|
|
171
|
+
get(t, k) { if (k !== 'documentElement') touched.push(String(k)); return t[k]; },
|
|
172
|
+
});
|
|
173
|
+
loadApplyFounding(spy, { founding: null })();
|
|
174
|
+
loadApplyFounding(spy, { founding: { active: false } })();
|
|
175
|
+
assert.deepEqual(touched, [], 'no DOM method was called');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('founding ON: the band above the topbar, and the rooms the server named asleep', () => {
|
|
179
|
+
const h = makeHall();
|
|
180
|
+
const now = new Date('2026-10-09T12:00:00Z');
|
|
181
|
+
const access = { founding: fm.shapeFoundingMode({ active: 'true', stage: 'strategize', startedAt: '2026-10-01T09:00:00Z' }, now) };
|
|
182
|
+
loadApplyFounding(h.document, access)();
|
|
183
|
+
assert.equal(h.root.dataset.founding, 'on');
|
|
184
|
+
const band = h.appBody.children[0];
|
|
185
|
+
assert.equal(band.id, 'founding-band', 'the band is the first child of .app-body, above the topbar');
|
|
186
|
+
assert.equal(h.appBody.children[1].id, 'app-topbar', 'the topbar itself is kept, not replaced');
|
|
187
|
+
assert.match(band.innerHTML, /Project founding mode/);
|
|
188
|
+
assert.match(band.innerHTML, /Kestrel is still being founded/);
|
|
189
|
+
assert.match(band.innerHTML, /stage 2 of 5 · strategize · day 9/);
|
|
190
|
+
const byId = (id) => h.nav.find((a) => a.id === 'nav-' + id);
|
|
191
|
+
assert.ok(!byId('goals').classList.contains('nav-item--asleep'), 'scope is behind: goals is awake');
|
|
192
|
+
assert.ok(byId('work').classList.contains('nav-item--asleep'), 'strategize is current: tasks still sleeps');
|
|
193
|
+
assert.equal(byId('work').children[0].textContent, 'opens after strategy', 'a sentence, never a raw slug');
|
|
194
|
+
assert.equal(byId('collab').children[0].textContent, 'opens when alive');
|
|
195
|
+
assert.ok(!byId('home').classList.contains('nav-item--asleep'), 'home is never put to sleep');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test('founding OFF after ON restores the hall exactly — OFF removes, it does not hide', () => {
|
|
199
|
+
const h = makeHall();
|
|
200
|
+
const before = snapshot(h);
|
|
201
|
+
const access = { founding: fm.shapeFoundingMode({ active: 'true', stage: 'decide' }) };
|
|
202
|
+
const apply = loadApplyFounding(h.document, access);
|
|
203
|
+
apply();
|
|
204
|
+
assert.notEqual(snapshot(h), before);
|
|
205
|
+
access.founding = null;
|
|
206
|
+
apply();
|
|
207
|
+
assert.equal(snapshot(h), before, 'band, asleep classes, wake tags and the root attribute are all gone');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test('the band escapes what it prints — a project name cannot inject markup', () => {
|
|
211
|
+
const h = makeHall();
|
|
212
|
+
const access = { founding: Object.assign(fm.shapeFoundingMode({ active: 'true', stage: 'scope' }), { waiting_on: ['<img src=x onerror=1>'] }) };
|
|
213
|
+
loadApplyFounding(h.document, access, { identity: { productName: '<script>alert(1)</script>' } })();
|
|
214
|
+
const html = h.appBody.children[0].innerHTML;
|
|
215
|
+
assert.ok(!html.includes('<script>'), 'the name is escaped');
|
|
216
|
+
assert.ok(!html.includes('<img'), 'the waiting-on names are escaped');
|
|
217
|
+
assert.match(html, /<script>/);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test('who the stage close is waiting on shows in the band when the server sends it', async () => {
|
|
221
|
+
const f = await fm.foundingModeFor({
|
|
222
|
+
getSetting: async (k) => ({ [fm.KEYS.ACTIVE]: { value: 'true' }, [fm.KEYS.STAGE]: { value: 'charter' } }[k] || null),
|
|
223
|
+
waitingOnFor: async (stage) => (stage === 'charter' ? ['Avery', 'Jordan'] : []),
|
|
224
|
+
});
|
|
225
|
+
assert.deepEqual(f.waiting_on, ['Avery', 'Jordan']);
|
|
226
|
+
const h = makeHall();
|
|
227
|
+
loadApplyFounding(h.document, { founding: f })();
|
|
228
|
+
assert.match(h.appBody.children[0].innerHTML, /waiting on Avery, Jordan/);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('waiting-on reads the government port\'s genesis-stage status, sitting stages only', async () => {
|
|
232
|
+
const gov = {
|
|
233
|
+
genesisStageStatus: async () => ({ stages: [
|
|
234
|
+
{ key: 'scope', state: 'passed', waiting_on: [] },
|
|
235
|
+
{ key: 'charter', state: 'sitting', waiting_on: [{ display_name: 'Avery' }, { github_login: 'jordan' }] },
|
|
236
|
+
{ key: 'legalize', state: 'not_put', waiting_on: [] },
|
|
237
|
+
] }),
|
|
238
|
+
};
|
|
239
|
+
const read = (stage) => fm.foundingModeFor({
|
|
240
|
+
getSetting: async (k) => ({ [fm.KEYS.ACTIVE]: { value: 'true' }, [fm.KEYS.STAGE]: { value: stage } }[k] || null),
|
|
241
|
+
government: gov,
|
|
242
|
+
});
|
|
243
|
+
assert.deepEqual((await read('charter')).waiting_on, ['Avery', 'jordan']);
|
|
244
|
+
assert.equal((await read('scope')).waiting_on, null, 'a passed stage waits on nobody');
|
|
245
|
+
assert.equal(fm.waitingOnFromGovernment({}), null, 'a port without the read offers none');
|
|
246
|
+
assert.equal((await fm.foundingModeFor({
|
|
247
|
+
getSetting: async (k) => ({ [fm.KEYS.ACTIVE]: { value: 'true' } }[k] || null), government: null,
|
|
248
|
+
})).waiting_on, null, 'no government module: the mode still reads, without names');
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test('a failing waiting-on source never turns the mode off', async () => {
|
|
252
|
+
const f = await fm.foundingModeFor({
|
|
253
|
+
getSetting: async (k) => ({ [fm.KEYS.ACTIVE]: { value: 'true' }, [fm.KEYS.STAGE]: { value: 'scope' } }[k] || null),
|
|
254
|
+
waitingOnFor: async () => { throw new Error('government down'); },
|
|
255
|
+
});
|
|
256
|
+
assert.equal(f.active, true);
|
|
257
|
+
assert.equal(f.waiting_on, null);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test('re-applying ON does not stack a second band or duplicate wake tags', () => {
|
|
261
|
+
const h = makeHall();
|
|
262
|
+
const access = { founding: fm.shapeFoundingMode({ active: 'true', stage: 'decide' }) };
|
|
263
|
+
const apply = loadApplyFounding(h.document, access);
|
|
264
|
+
apply(); apply();
|
|
265
|
+
assert.equal(h.appBody.children.filter((c) => c.id === 'founding-band').length, 1);
|
|
266
|
+
assert.equal(h.nav.find((a) => a.id === 'nav-work').children.length, 1);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// ── the wiring ──────────────────────────────────────────────────────────────
|
|
270
|
+
|
|
271
|
+
test('only an explicit active:true turns the mode on', () => {
|
|
272
|
+
const fn = SHELL.slice(SHELL.indexOf(' function applyFounding()'));
|
|
273
|
+
assert.match(fn.slice(0, 400), /f && f\.active === true/);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test('/me carries founding_mode from the onboarding port, fail-open to null', () => {
|
|
277
|
+
assert.match(ME, /founding_mode: foundingMode/);
|
|
278
|
+
assert.match(ME, /onboarding\.foundingModeFor\(\)/);
|
|
279
|
+
const at = ME.indexOf('onboarding.foundingModeFor()');
|
|
280
|
+
assert.match(ME.slice(at, at + 200), /catch \(_\) \{ return null; \}/);
|
|
281
|
+
assert.match(PORT, /foundingModeFor: \(\.\.\.a\) => foundingMode\.foundingModeFor\(\.\.\.a\)/);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test('BOTH applyAccess callers pass it, or the index would never show the band', () => {
|
|
285
|
+
assert.match(SHELL, /founding: me\.founding_mode \|\| null/);
|
|
286
|
+
assert.match(HALL_RENDER, /founding: \(ctx\.lastMeState && ctx\.lastMeState\.founding_mode\) \|\| null/);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test('founding mode adds no page, no route and no nav item — it is a mode', () => {
|
|
290
|
+
const nav = SHELL.slice(SHELL.indexOf('var NAV = ['), SHELL.indexOf('var NAV_COLLAPSE_KEY'));
|
|
291
|
+
assert.ok(!/founding|genesis/i.test(nav), 'the NAV model must not grow a founding entry');
|
|
292
|
+
const hall = fs.readdirSync(path.join(ROOT, 'modules', 'hall-ui', 'public'));
|
|
293
|
+
assert.ok(!hall.some((f) => /^founding\./.test(f)), 'no founding page file');
|
|
294
|
+
});
|