@bongos/core 1.24.41 → 1.24.43
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 +117 -92
- package/docs/copy-inventory.md +98 -137
- package/docs/copy-registry.json +244 -644
- package/docs/module-api-changelog.md +4 -0
- package/docs/onboarding/primer.md +1 -1
- package/docs/page-inventory.json +3 -3
- package/docs/page-readings.json +494 -535
- package/modules/hall-ui/public/ai-studio.html +4 -4
- package/modules/hall-ui/public/atlas.html +4 -4
- package/modules/hall-ui/public/blockers.html +4 -4
- package/modules/hall-ui/public/board-room.html +4 -4
- package/modules/hall-ui/public/builders.js +35 -763
- package/modules/hall-ui/public/city.html +4 -4
- package/modules/hall-ui/public/collab.html +4 -4
- package/modules/hall-ui/public/commands.html +4 -4
- package/modules/hall-ui/public/copy-desk.html +4 -4
- package/modules/hall-ui/public/craft-mode.js +1 -1
- package/modules/hall-ui/public/deploy.html +4 -4
- package/modules/hall-ui/public/dom-utils.js +2 -1
- package/modules/hall-ui/public/goal-map.html +4 -4
- package/modules/hall-ui/public/goals.html +4 -4
- package/modules/hall-ui/public/government.html +4 -4
- package/modules/hall-ui/public/hall-render.js +25 -771
- package/modules/hall-ui/public/hall-widgets.js +14 -123
- package/modules/hall-ui/public/idea.html +4 -4
- package/modules/hall-ui/public/ideas.html +4 -4
- package/modules/hall-ui/public/index.html +8 -22
- package/modules/hall-ui/public/index.states.json +0 -33
- package/modules/hall-ui/public/market-watch-item.html +4 -4
- package/modules/hall-ui/public/market-watch-rules.html +4 -4
- package/modules/hall-ui/public/market-watch.html +4 -4
- package/modules/hall-ui/public/modules.html +4 -4
- package/modules/hall-ui/public/money.html +4 -4
- package/modules/hall-ui/public/my-agents.html +4 -4
- package/modules/hall-ui/public/palette.js +7 -6
- package/modules/hall-ui/public/primer.html +4 -4
- package/modules/hall-ui/public/profile-own.js +639 -0
- package/modules/hall-ui/public/profile.css +2 -1
- package/modules/hall-ui/public/profile.html +48 -5
- package/modules/hall-ui/public/profile.js +27 -2
- package/modules/hall-ui/public/profile.states.json +18 -0
- package/modules/hall-ui/public/project-settings.html +4 -4
- package/modules/hall-ui/public/roadmap.html +4 -4
- package/modules/hall-ui/public/roster.html +4 -4
- package/modules/hall-ui/public/settings.html +4 -4
- package/modules/hall-ui/public/setup-gate.js +156 -0
- package/modules/hall-ui/public/shell-trail.js +2 -1
- package/modules/hall-ui/public/shell.js +44 -26
- package/modules/hall-ui/public/street.html +4 -4
- package/modules/hall-ui/public/studio.html +4 -4
- package/modules/hall-ui/public/style.css +85 -117
- package/modules/hall-ui/public/task.html +4 -4
- package/modules/hall-ui/public/thinking.html +4 -4
- package/modules/hall-ui/public/tweak-editor.html +4 -4
- package/modules/hall-ui/public/watch.html +4 -4
- package/modules/hall-ui/public/work.html +4 -4
- package/modules/hall-ui/records/setup-gate.md +43 -0
- package/modules/lifecycle/routes/tasks.js +2 -2
- package/modules/onboarding/onboarding-state.js +41 -20
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/src/bongos/routes/me.js +2 -2
- package/src/module-api.js +1 -1
- package/tests/curve_controls_delegation.mjs +9 -8
- package/tests/curve_kind_overlay.mjs +5 -5
- package/tests/hall_acclaim_vote_control.mjs +5 -3
- package/tests/hall_audit.mjs +10 -6
- package/tests/hall_board_world.mjs +41 -27
- package/tests/hall_builders_page_pass.mjs +14 -5
- package/tests/hall_home_ships_bounded.mjs +10 -21
- package/tests/hall_kit_charts.mjs +3 -2
- package/tests/hall_landing_boot.mjs +2 -3
- package/tests/hall_loading.mjs +2 -1
- package/tests/hall_nav.mjs +36 -37
- package/tests/hall_numbers_page.mjs +100 -0
- package/tests/hall_onboarding_bongos_setup.mjs +14 -14
- package/tests/hall_palette.mjs +19 -24
- package/tests/hall_profile_world.mjs +7 -2
- package/tests/hall_reading_room.mjs +3 -2
- package/tests/helpers.mjs +8 -2
- package/tests/leaderboard_roles.mjs +1 -1
- package/tests/module_contributions.mjs +10 -6
- package/tests/nav_permission_atoms.mjs +4 -2
- package/tests/onboarding_state.mjs +33 -0
- package/tests/page_tweak_artist.mjs +2 -1
- package/tests/rank_tier_single_source.mjs +3 -0
- package/tests/setup_flow.mjs +242 -0
- package/tests/task_visuals.mjs +3 -1
- package/tests/welcome_boot.mjs +3 -2
package/tests/hall_palette.mjs
CHANGED
|
@@ -35,7 +35,7 @@ const SHELL_SRC = read('shell.js');
|
|
|
35
35
|
// SAME stub every time — real enough to fire DOM events at and read innerHTML
|
|
36
36
|
// back from, which the pure-compute-only harness never needed before task 1003460
|
|
37
37
|
// wired a real fetch() into the input handler.
|
|
38
|
-
function bootPalette({ navItems = [], visibleIds = ['nav-
|
|
38
|
+
function bootPalette({ navItems = [], visibleIds = ['nav-profile', 'nav-work', 'shell-user'], modules = null, fetchImpl = undefined } = {}) {
|
|
39
39
|
const registry = {};
|
|
40
40
|
const makeNav = (it) => ({
|
|
41
41
|
hidden: !!it.hidden,
|
|
@@ -113,7 +113,7 @@ async function settleSearch() { await tick(400); }
|
|
|
113
113
|
const NAV_FIXTURE = [
|
|
114
114
|
{ label: 'Home', href: '/' },
|
|
115
115
|
{ label: 'Tasks', href: '/work' },
|
|
116
|
-
{ label: '
|
|
116
|
+
{ label: 'Profile', href: '/profile' },
|
|
117
117
|
{ label: 'Economy', href: '/drachmae' },
|
|
118
118
|
{ label: 'API docs', href: '/docs', external: true },
|
|
119
119
|
{ label: 'Watch', href: '/watch', hidden: true }, // Archon-only, gated off
|
|
@@ -126,22 +126,17 @@ function declaredHrefs(prefixTest) {
|
|
|
126
126
|
return [...PALETTE_SRC.matchAll(/href:\s*'([^']+)'/g)].map((m) => m[1]).filter(prefixTest);
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
// task
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
assert.ok(
|
|
136
|
-
const declared = declaredHrefs((h) => h.startsWith('
|
|
137
|
-
assert.ok(declared.length, 'palette declares
|
|
138
|
-
for (const
|
|
139
|
-
|
|
140
|
-
assert.ok(groups.includes(key), `${id} must be a real STANDING_GROUPS key (builders.js)`);
|
|
141
|
-
// shell.js must alias it, or route() ignores the hash and the jump goes nowhere.
|
|
142
|
-
assert.match(SHELL_SRC, new RegExp(`'${id}':\\s*\\{ view: 'standing', anchor: 'standing-${key}' \\}`),
|
|
143
|
-
`${id} must have a VIEW_ALIASES entry in shell.js`);
|
|
144
|
-
}
|
|
129
|
+
// task 1005050: Accomplishments folded into Profile, so the palette's numbers
|
|
130
|
+
// jumps are the profile's own screens — profile.js RAIL_ITEMS — and its old words
|
|
131
|
+
// ("Accomplishments", karma, progress) still find the "Just you" screen.
|
|
132
|
+
test('Profile section anchors are real profile.js rail keys', () => {
|
|
133
|
+
const block = (read('profile.js').match(/const RAIL_ITEMS = \[([\s\S]*?)\];/) || [, ''])[1];
|
|
134
|
+
const keys = [...block.matchAll(/id: '([a-z]+)'/g)].map((m) => m[1]);
|
|
135
|
+
assert.ok(keys.includes('yours'), `sanity: profile.js rail keys (${keys})`);
|
|
136
|
+
const declared = declaredHrefs((h) => h.startsWith('/profile#'));
|
|
137
|
+
assert.ok(declared.length >= 3, 'palette declares the profile screens');
|
|
138
|
+
for (const h of declared) assert.ok(keys.includes(h.split('#')[1]), `${h} must be a real profile.js rail key`);
|
|
139
|
+
assert.ok(!declaredHrefs((h) => /^\/#(standing|accomplishments)/.test(h)).length, 'no jump points at the retired view');
|
|
145
140
|
});
|
|
146
141
|
|
|
147
142
|
test('Tasks tab anchors exist in work.js makeTabs', () => {
|
|
@@ -195,8 +190,8 @@ test('My agents anchors are ones my-agents.js lands, and the moved Settings pane
|
|
|
195
190
|
});
|
|
196
191
|
|
|
197
192
|
test('the anchor guard actually CATCHES a declared id that does not exist', () => {
|
|
198
|
-
const
|
|
199
|
-
assert.equal(
|
|
193
|
+
const keys = ['overview', 'works', 'activity', 'connections', 'yours'];
|
|
194
|
+
assert.equal(keys.includes('profile#nope'.split('#')[1]), false,
|
|
200
195
|
'an invented section id fails the membership check the tests above apply');
|
|
201
196
|
});
|
|
202
197
|
|
|
@@ -205,17 +200,17 @@ test('the anchor guard actually CATCHES a declared id that does not exist', () =
|
|
|
205
200
|
test('pages come from the sidebar, and a gated-off nav item is not a destination', () => {
|
|
206
201
|
const P = bootPalette({ navItems: NAV_FIXTURE });
|
|
207
202
|
const labels = P.compute('').filter((r) => r.group === 'Pages').map((r) => r.label);
|
|
208
|
-
assert.deepEqual(labels, ['Home', 'Tasks', '
|
|
203
|
+
assert.deepEqual(labels, ['Home', 'Tasks', 'Profile', 'Economy', 'API docs']);
|
|
209
204
|
assert.ok(!labels.includes('Watch'), 'a hidden (rank-gated) nav item must not be jumpable');
|
|
210
205
|
});
|
|
211
206
|
|
|
212
207
|
test('section groups appear only when their owning nav item is visible', () => {
|
|
213
208
|
const all = bootPalette({ navItems: NAV_FIXTURE }).compute('');
|
|
214
|
-
assert.ok(all.some((r) => r.group === '
|
|
209
|
+
assert.ok(all.some((r) => r.group === 'Profile'), 'Profile sections offered when nav-profile shows');
|
|
215
210
|
assert.ok(all.some((r) => r.group === 'Settings'), 'Settings panels offered when the profile chip shows');
|
|
216
211
|
|
|
217
212
|
const gatedOut = bootPalette({ navItems: NAV_FIXTURE, visibleIds: [] }).compute('');
|
|
218
|
-
assert.ok(!gatedOut.some((r) => ['
|
|
213
|
+
assert.ok(!gatedOut.some((r) => ['Profile', 'Tasks', 'Settings'].includes(r.group)),
|
|
219
214
|
'no sub-destinations when their owners are hidden');
|
|
220
215
|
});
|
|
221
216
|
|
|
@@ -242,7 +237,7 @@ test('a non-matching query yields nothing (the empty state), and text still matc
|
|
|
242
237
|
const P = bootPalette({ navItems: NAV_FIXTURE });
|
|
243
238
|
assert.deepEqual(P.compute('zzzqqq'), []);
|
|
244
239
|
assert.equal(P.compute('econ')[0].label, 'Economy', 'substring match');
|
|
245
|
-
assert.equal(P.compute('
|
|
240
|
+
assert.equal(P.compute('accomplishments')[0].href, '/profile#yours', 'the old word still finds where its private half went');
|
|
246
241
|
});
|
|
247
242
|
|
|
248
243
|
// ---- progressive enhancement + wiring -------------------------------------
|
|
@@ -63,7 +63,8 @@ const profileCss = stripComments(read('profile.css'));
|
|
|
63
63
|
// The plot's dataviz spec lives in the kit's sheet since task 1003445.
|
|
64
64
|
const styleCss = stripComments(read('style.css'));
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
// task 1005050: 'yours' joined — Accomplishments' private half, on your own profile only.
|
|
67
|
+
const GROUPS = ['overview', 'works', 'activity', 'connections', 'yours'];
|
|
67
68
|
|
|
68
69
|
// ---- the rail and the groups ---------------------------------------------------
|
|
69
70
|
|
|
@@ -89,8 +90,12 @@ test('every section in the groups carries a known data-group, and the keys are t
|
|
|
89
90
|
assert.equal(groupOf('works-scroll'), 'works', 'the works ledger is its own key');
|
|
90
91
|
assert.equal(groupOf('activity-scroll'), 'activity');
|
|
91
92
|
assert.equal(groupOf('conn-scroll'), 'connections');
|
|
93
|
+
for (const id of ['rank-scroll', 'grades-scroll', 'karma-scroll', 'acclaim-scroll', 'analytics-scroll']) {
|
|
94
|
+
assert.equal(groupOf(id), 'yours', `#${id} is on the "Just you" screen`);
|
|
95
|
+
assert.match(html, new RegExp(`id="${id}"[^>]*data-group="yours" hidden>`), `#${id} starts hidden`);
|
|
96
|
+
}
|
|
92
97
|
const railKeys = profileJs.match(/const RAIL_ITEMS = \[([\s\S]*?)\];/)[1].match(/id: '([a-z]+)'/g).map((s) => s.slice(5, -1));
|
|
93
|
-
assert.deepEqual(railKeys, GROUPS, 'the rail lists the
|
|
98
|
+
assert.deepEqual(railKeys, GROUPS, 'the rail lists the five keys in reading order');
|
|
94
99
|
});
|
|
95
100
|
|
|
96
101
|
test('the sheet shows exactly the active group, and a hidden section stays hidden', () => {
|
|
@@ -156,7 +156,8 @@ test('the lifecycle, the rank ladder and credits and karma are each explained ON
|
|
|
156
156
|
const ladder = PRIMER_MD.slice(PRIMER_MD.indexOf('## The rank ladder'), PRIMER_MD.indexOf('## Credits and karma'));
|
|
157
157
|
for (const rank of ['Xenos', 'Thetes', 'Metic', 'Archon']) assert.match(ladder, new RegExp(`\\*\\*${rank}\\*\\*`));
|
|
158
158
|
assert.match(ladder, /### How you advance/);
|
|
159
|
-
|
|
159
|
+
// task 1005050: Accomplishments folded into Profile; the card is its "Just you" screen.
|
|
160
|
+
assert.match(ladder, /\/builders\/profile#yours/, 'and it points the reader at their own standing');
|
|
160
161
|
});
|
|
161
162
|
|
|
162
163
|
test('the two stale primer lines are gone', () => {
|
|
@@ -172,7 +173,7 @@ test('the primer keeps a hall address in the hall, and sends repo paths to GitHu
|
|
|
172
173
|
// Lift the resolver out of primer.js without booting the page.
|
|
173
174
|
const src = primerJs.slice(primerJs.indexOf(' const resolveLinkHref = (() => {'), primerJs.indexOf(' function toast('));
|
|
174
175
|
const fn = vm.runInContext(`(() => { const MD = window.OTBMdReader; const PRIMER_DIR = 'docs/onboarding/'; let repoBlobBase = 'https://gh/blob/main'; let repoTreeBase = 'https://gh/tree/main';\n${src}\nreturn resolveLinkHref; })()`, sandbox);
|
|
175
|
-
assert.equal(fn('/builders#
|
|
176
|
+
assert.equal(fn('/builders/profile#yours'), '/builders/profile#yours');
|
|
176
177
|
assert.equal(fn('drachmae-and-karma.md'), 'https://gh/blob/main/docs/onboarding/drachmae-and-karma.md',
|
|
177
178
|
'the full reference is a repo doc now that its hall page is a chapter');
|
|
178
179
|
assert.equal(fn('../adr/'), 'https://gh/tree/main/docs/adr');
|
package/tests/helpers.mjs
CHANGED
|
@@ -542,7 +542,9 @@ const PROFILE_CRAFTS_JS = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/publi
|
|
|
542
542
|
// tab each. The default leads with the ideator, so the trail is the first tab
|
|
543
543
|
// and a suite that reads it needs no switch; switchCraft('engineer') opens the
|
|
544
544
|
// works and goals, switchCraft('artist') the approved pages.
|
|
545
|
-
|
|
545
|
+
// `search` is the page's ?query (task 1005050: ?id= decides whose profile it is), and
|
|
546
|
+
// `withOwn` also loads profile-own.js, the "Just you" screen drawn on your own profile.
|
|
547
|
+
export function bootProfilePage({ signedIn = true, trail = [], roles = ['ideator', 'engineer'], artistPages = [], search = '', withOwn = false } = {}) {
|
|
546
548
|
const els = new Map();
|
|
547
549
|
const makeEl = () => ({
|
|
548
550
|
dataset: {}, style: {}, textContent: '', hidden: false, _html: '',
|
|
@@ -617,6 +619,8 @@ export function bootProfilePage({ signedIn = true, trail = [], roles = ['ideator
|
|
|
617
619
|
return { active: () => current, activate(id) { current = id; onChange(id); }, setCount() {} };
|
|
618
620
|
},
|
|
619
621
|
shipVisualThumbHtml: () => '',
|
|
622
|
+
// profile-own.js's peer-recognition pager (task 1005050).
|
|
623
|
+
paginate: (rows) => rows, wirePager() {}, pagerBarHtml: () => '',
|
|
620
624
|
};
|
|
621
625
|
|
|
622
626
|
const windowObj = {
|
|
@@ -638,7 +642,7 @@ export function bootProfilePage({ signedIn = true, trail = [], roles = ['ideator
|
|
|
638
642
|
rankLabel: (r) => r,
|
|
639
643
|
toast() {},
|
|
640
644
|
},
|
|
641
|
-
location: { hostname: 'builders.cloudbongos.com', search
|
|
645
|
+
location: { hostname: 'builders.cloudbongos.com', search, pathname: '/profile' },
|
|
642
646
|
localStorage: { getItem: () => null, setItem() {} },
|
|
643
647
|
addEventListener() {},
|
|
644
648
|
matchMedia: () => ({ matches: false }),
|
|
@@ -655,6 +659,7 @@ export function bootProfilePage({ signedIn = true, trail = [], roles = ['ideator
|
|
|
655
659
|
vm.runInContext(PROFILE_ROLES_JS, sandbox, { filename: 'profile-roles.js' });
|
|
656
660
|
vm.runInContext(IDEA_OBJECTS_JS, sandbox, { filename: 'idea-objects.js' });
|
|
657
661
|
vm.runInContext(PROFILE_CRAFTS_JS, sandbox, { filename: 'profile-crafts.js' });
|
|
662
|
+
if (withOwn) vm.runInContext(fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/profile-own.js'), 'utf8'), sandbox, { filename: 'profile-own.js' });
|
|
658
663
|
vm.runInContext(PROFILE_JS, sandbox, { filename: 'profile.js' });
|
|
659
664
|
|
|
660
665
|
const htmlFor = (mountId) => {
|
|
@@ -666,6 +671,7 @@ export function bootProfilePage({ signedIn = true, trail = [], roles = ['ideator
|
|
|
666
671
|
htmlFor,
|
|
667
672
|
switchCraft: (id) => { if (tabsOnChange) tabsOnChange(id); },
|
|
668
673
|
el: (id) => documentObj.getElementById(id),
|
|
674
|
+
window: windowObj,
|
|
669
675
|
};
|
|
670
676
|
}
|
|
671
677
|
|
|
@@ -236,7 +236,7 @@ function extract(src, start, end) {
|
|
|
236
236
|
|
|
237
237
|
await test('the standing card adds the place inside the builder\'s main role, only when it says something new', () => {
|
|
238
238
|
const src = read('modules/hall-ui/public/hall-render.js');
|
|
239
|
-
const code = extract(src, 'const lbState = { rows: [] };', ' return {
|
|
239
|
+
const code = extract(src, 'const lbState = { rows: [] };', ' return { lbState');
|
|
240
240
|
const sandbox = { rows: FIX.leaderboard };
|
|
241
241
|
vm.createContext(sandbox);
|
|
242
242
|
vm.runInContext(`${code}; lbState.rows = rows; this.place = leaderboardPlace; this.text = rolePlaceText;`, sandbox);
|
|
@@ -228,7 +228,8 @@ test('the role axis has exactly one user-facing word, and no surface types it',
|
|
|
228
228
|
'modules/hall-ui/public/work.js',
|
|
229
229
|
'modules/hall-ui/public/goals-page.js',
|
|
230
230
|
'modules/hall-ui/public/profile.js',
|
|
231
|
-
'
|
|
231
|
+
// task 1005076: the setup screen's role step replaced builders.js's checklist picker.
|
|
232
|
+
'modules/hall-ui/public/setup-gate.js',
|
|
232
233
|
];
|
|
233
234
|
for (const p of SURFACES) {
|
|
234
235
|
const s = readSrc(p);
|
|
@@ -266,7 +267,8 @@ test('craft picker options: neither picker hardcodes the craft list any more', (
|
|
|
266
267
|
// silently dropped a module-contributed craft. Whichever picker is edited next
|
|
267
268
|
// must keep reading the server list.
|
|
268
269
|
const read = (p) => readFileSync(path.join(ROOT, p), 'utf8');
|
|
269
|
-
|
|
270
|
+
// task 1005076: the second picker is the setup screen's role step (it was the Home checklist's).
|
|
271
|
+
for (const p of ['modules/hall-ui/public/settings.js', 'modules/hall-ui/public/setup-gate.js']) {
|
|
270
272
|
const src = read(p);
|
|
271
273
|
assert.ok(/OTB\.disciplines\(\)/.test(src), `${p} builds its options from OTB.disciplines()`);
|
|
272
274
|
assert.ok(!/\['engineer',\s*'artist',\s*'ideator'\]/.test(src), `${p} no longer hardcodes the craft triple`);
|
|
@@ -312,11 +314,13 @@ test('hall-widget registry: placement rules reject bad registrations, accept goo
|
|
|
312
314
|
assert.ok(reg({ id: 'e', view: 'home', zone: 'home-main', summary: true }));
|
|
313
315
|
assert.ok(reg({ id: 'f', view: 'home', zone: 'home-main', collapsedByDefault: true }));
|
|
314
316
|
assert.ok(reg({ id: 'g', view: 'home', zone: 'home-main', core: true }));
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
+
// task 1005050: 'standing' (Accomplishments) left the views with the view
|
|
318
|
+
// itself — it folded into the Profile page — so a widget naming it is refused.
|
|
319
|
+
assert.equal(reg({ id: 'h', view: 'standing', group: 'work' }), null);
|
|
320
|
+
assert.match(errors.at(-1), /unknown view "standing"/);
|
|
317
321
|
|
|
318
|
-
// only the accepted
|
|
319
|
-
assert.deepEqual(OTB.hallWidgetIds(), ['e', 'f', 'g'
|
|
322
|
+
// only the accepted three reached the registry
|
|
323
|
+
assert.deepEqual(OTB.hallWidgetIds(), ['e', 'f', 'g']);
|
|
320
324
|
} finally {
|
|
321
325
|
console.error = realError;
|
|
322
326
|
delete global.window;
|
|
@@ -96,8 +96,10 @@ test('the hall-preview fixture carries every atom /me resolves', () => {
|
|
|
96
96
|
// Market Watch settings nav item and its fuel glyph; task 1005041: the Update pill replaced the
|
|
97
97
|
// core-update banner; task 1005045: the Market Watch settings item left (one sidebar item);
|
|
98
98
|
// task 1005046: Ranks, Economy and Diagrams left Docs (one reading room, the Primer);
|
|
99
|
-
// task 1005049: Sessions and Autobongos left the sidebar (tabs on Watch)
|
|
100
|
-
|
|
99
|
+
// task 1005049: Sessions and Autobongos left the sidebar (tabs on Watch);
|
|
100
|
+
// task 1005076: the setup flow's holdForSetup;
|
|
101
|
+
// task 1005050: Accomplishments left it (folded into Profile; its hashes redirect).
|
|
102
|
+
const SHELL_SHA = '050dbf4ceac560dc';
|
|
101
103
|
|
|
102
104
|
test('shell.js has not changed without its cache-bust being bumped', () => {
|
|
103
105
|
const actual = crypto.createHash('sha256').update(SHELL).digest('hex').slice(0, 16);
|
|
@@ -322,6 +322,39 @@ test('graduation is engine-only (display signals never gate it)', async () => {
|
|
|
322
322
|
assert.ok(shaped.stages.every((s) => s.cleared === true));
|
|
323
323
|
});
|
|
324
324
|
|
|
325
|
+
// ---- the setup flow (task 1005076) -----------------------------------------
|
|
326
|
+
|
|
327
|
+
const SETUP_IDS = ['signed_in', 'cli_setup', 'pick_disciplines', 'first_start'];
|
|
328
|
+
const setupOf = (state, opts) => onboarding.shapeForApi(state, opts).setup;
|
|
329
|
+
const clearedIds = (setup) => setup.steps.filter((s) => s.cleared).map((s) => s.id);
|
|
330
|
+
|
|
331
|
+
test('setup: a brand-new xenos is held, with only signed_in cleared', () => {
|
|
332
|
+
const setup = setupOf(null, { rank: 'xenos' });
|
|
333
|
+
assert.equal(setup.complete, false);
|
|
334
|
+
assert.deepEqual(setup.steps.map((s) => s.id), SETUP_IDS);
|
|
335
|
+
assert.deepEqual(clearedIds(setup), ['signed_in']);
|
|
336
|
+
assert.ok(setup.steps.every((s) => s.label && s.hint), 'every step says what to do');
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test('setup: each step ticks from its own signal, and all four complete it', () => {
|
|
340
|
+
const state = { current_stage: 'pick_disciplines', completed_stages: ['auth_complete'] };
|
|
341
|
+
assert.deepEqual(clearedIds(setupOf(state, { rank: 'xenos', cliAuthed: true })), ['signed_in', 'cli_setup']);
|
|
342
|
+
const picked = { current_stage: 'first_start', completed_stages: ['auth_complete', 'pick_disciplines'] };
|
|
343
|
+
const almost = setupOf(picked, { rank: 'xenos', cliAuthed: true });
|
|
344
|
+
assert.equal(almost.complete, false, 'first_start not yet run from Claude');
|
|
345
|
+
const started = { current_stage: 'first_claim', completed_stages: ['auth_complete', 'pick_disciplines', 'first_start'] };
|
|
346
|
+
assert.equal(setupOf(started, { rank: 'xenos', cliAuthed: true }).complete, true);
|
|
347
|
+
assert.equal(setupOf(started, { rank: 'xenos', cliAuthed: false }).complete, false, 'no linked machine, not set up');
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test('setup: anyone already past it goes straight in', () => {
|
|
351
|
+
const fresh = { current_stage: 'auth_complete', completed_stages: [] };
|
|
352
|
+
for (const rank of ['thetes', 'metic', 'archon']) assert.equal(setupOf(fresh, { rank }).complete, true, rank);
|
|
353
|
+
assert.equal(setupOf({ current_stage: 'graduated', completed_stages: [] }, { rank: 'xenos' }).complete, true, 'graduated');
|
|
354
|
+
assert.equal(setupOf({ current_stage: 'ship_three', completed_stages: ['first_claim'] }, { rank: 'xenos' }).complete, true, 'claimed a task');
|
|
355
|
+
assert.equal(setupOf(fresh, {}).complete, false, 'an unknown rank is not a pass');
|
|
356
|
+
});
|
|
357
|
+
|
|
325
358
|
// ---- wrap-up --------------------------------------------------------------
|
|
326
359
|
|
|
327
360
|
Promise.resolve().then(async () => {
|
|
@@ -195,7 +195,8 @@ await test('the status page and the acclaim feed render the helper', () => {
|
|
|
195
195
|
const status = readFileSync(new URL('../modules/status-ui/public/status.js', import.meta.url), 'utf8');
|
|
196
196
|
assert.match(status, /t\.helper_display_name \|\| t\.helper_login/);
|
|
197
197
|
assert.match(status, /with help from \$\{helper\}/);
|
|
198
|
-
|
|
198
|
+
// task 1005050: the acclaim feed moved with Accomplishments to the profile's "Just you" screen.
|
|
199
|
+
const hall = readFileSync(new URL('../modules/hall-ui/public/profile-own.js', import.meta.url), 'utf8');
|
|
199
200
|
assert.match(hall, /with help from \$\{escapeHtml\(helperName\)\}/, 'escaped before it reaches innerHTML');
|
|
200
201
|
const feed = readFileSync(new URL('../modules/lifecycle/routes/tasks.js', import.meta.url), 'utf8');
|
|
201
202
|
assert.match(feed, /helper\.helper_login, helper\.helper_display_name/, 'the acclaim feed carries the helper');
|
|
@@ -93,6 +93,9 @@ const STATIC_LADDER_FILES = [
|
|
|
93
93
|
'modules/discord/discord-channels.js',
|
|
94
94
|
'modules/memory/search.js',
|
|
95
95
|
'modules/hall-ui/public/profile.js',
|
|
96
|
+
// task 1005050: Where you stand moved with Accomplishments to the profile's "Just
|
|
97
|
+
// you" screen and reads the rung above yours off this ladder.
|
|
98
|
+
'modules/hall-ui/public/profile-own.js',
|
|
96
99
|
// task 1005048: the rank <select> on each roster row moved from watch.js to the
|
|
97
100
|
// Builders page with the roster itself; the copy moved with it.
|
|
98
101
|
'modules/hall-ui/public/roster.js',
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// tests/setup_flow.mjs — the setup flow a new builder finishes once (task 1005076).
|
|
2
|
+
//
|
|
3
|
+
// Owner, 2026-11-05: a new builder is held on one setup screen until every step is done,
|
|
4
|
+
// and once set up they are in for good — no setup banner on Home. Three halves, each run
|
|
5
|
+
// for real: the claimable route ticks first_start only for a Claude (CLI) session; the
|
|
6
|
+
// shell loads setup-gate.js and draws nothing else over it while setup is incomplete; and
|
|
7
|
+
// the screen draws the steps, saves a craft, and lets the builder in once /me says done.
|
|
8
|
+
// The rule itself (which steps, who goes straight in) is in tests/onboarding_state.mjs.
|
|
9
|
+
//
|
|
10
|
+
// Run: node tests/setup_flow.mjs
|
|
11
|
+
|
|
12
|
+
import { strict as assert } from 'node:assert';
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import vm from 'node:vm';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
import { makeEl, realOTB, settle } from './helpers.mjs';
|
|
18
|
+
|
|
19
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
20
|
+
const read = (rel) => fs.readFileSync(path.join(ROOT, rel), 'utf8');
|
|
21
|
+
const ROUTE = read('modules/lifecycle/routes/tasks.js');
|
|
22
|
+
const SHELL = read('modules/hall-ui/public/shell.js');
|
|
23
|
+
const GATE = read('modules/hall-ui/public/setup-gate.js');
|
|
24
|
+
|
|
25
|
+
let passed = 0;
|
|
26
|
+
let failed = 0;
|
|
27
|
+
const t = async (name, fn) => {
|
|
28
|
+
try { await fn(); passed += 1; console.log(` PASS ${name}`); }
|
|
29
|
+
catch (e) { failed += 1; console.log(` FAIL ${name}\n ${e.message}`); }
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const STEPS = (cleared) => ['signed_in', 'cli_setup', 'pick_disciplines', 'first_start']
|
|
33
|
+
.map((id) => ({ id, label: `Label ${id}`, hint: `Hint ${id}`, cleared: cleared.includes(id) }));
|
|
34
|
+
|
|
35
|
+
console.log('the setup flow (task 1005076)');
|
|
36
|
+
|
|
37
|
+
// ---- the claimable route: first_start only from Claude --------------------
|
|
38
|
+
|
|
39
|
+
// The real guarded statement, run against each kind of session.
|
|
40
|
+
const from = ROUTE.indexOf("if (req.gdsSession && req.gdsSession.source === 'cli')");
|
|
41
|
+
const guard = ROUTE.slice(from, ROUTE.indexOf('\n', from));
|
|
42
|
+
const runGuard = (gdsSession) => {
|
|
43
|
+
const marks = [];
|
|
44
|
+
const seams = { resolveOptional: () => ({ safeMarkStage: (_pool, id, stage) => marks.push([id, stage]) }) };
|
|
45
|
+
vm.runInNewContext(guard, { req: { gdsSession, builder: { id: 7 } }, seams, pool: {} });
|
|
46
|
+
return marks;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
await t('GET /tasks/claimable marks first_start for a Claude (CLI) session, never for the browser', () => {
|
|
50
|
+
assert.ok(from > 0, 'the guard is in the claimable handler');
|
|
51
|
+
assert.match(guard, /safeMarkStage\(pool, req\.builder\.id, 'first_start', req\.builder\)/);
|
|
52
|
+
assert.deepEqual(runGuard({ source: 'cli' }), [[7, 'first_start']]);
|
|
53
|
+
assert.deepEqual(runGuard({ source: 'web' }), [], 'the hall pages read this feed from the browser');
|
|
54
|
+
assert.deepEqual(runGuard(null), []);
|
|
55
|
+
assert.equal((ROUTE.match(/'first_start'/g) || []).length, 1, 'no unguarded mark elsewhere in the routes');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// ---- the screen ------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
function bootGate({ patchOk = true } = {}) {
|
|
61
|
+
const body = makeEl('body');
|
|
62
|
+
const htmlClasses = new Set();
|
|
63
|
+
const requests = [];
|
|
64
|
+
let reloaded = 0;
|
|
65
|
+
let me = null;
|
|
66
|
+
const api = { request: (method, p, opts) => {
|
|
67
|
+
requests.push({ method, p, body: opts && opts.body });
|
|
68
|
+
if (method === 'PATCH') return Promise.resolve({ ok: patchOk, status: patchOk ? 200 : 422, data: {} });
|
|
69
|
+
return Promise.resolve({ ok: true, status: 200, data: me });
|
|
70
|
+
} };
|
|
71
|
+
const intervals = [];
|
|
72
|
+
const document = {
|
|
73
|
+
body,
|
|
74
|
+
documentElement: { classList: { add: (c) => htmlClasses.add(c), remove: (c) => htmlClasses.delete(c) } },
|
|
75
|
+
createElement: (tag) => makeEl(tag),
|
|
76
|
+
getElementById: (id) => body.querySelector('#' + id),
|
|
77
|
+
activeElement: null,
|
|
78
|
+
};
|
|
79
|
+
// the crafts this project offers (dom-utils.js reads them off __MODULES__)
|
|
80
|
+
const window = { OTB: { ...realOTB(), disciplines: () => ['engineer', 'artist', 'ui'] }, location: { reload: () => { reloaded += 1; } } };
|
|
81
|
+
const sandbox = { window, document, setInterval: (fn) => { intervals.push(fn); return intervals.length; }, clearInterval() {} };
|
|
82
|
+
vm.createContext(sandbox);
|
|
83
|
+
vm.runInContext(GATE, sandbox, { filename: 'setup-gate.js' });
|
|
84
|
+
return {
|
|
85
|
+
show: (setup, builder) => window.OTBSetupGate.show(setup, { api, builder }),
|
|
86
|
+
gate: () => body.querySelector('#setup-gate'),
|
|
87
|
+
htmlClasses, requests, intervals,
|
|
88
|
+
setMe: (v) => { me = v; },
|
|
89
|
+
reloaded: () => reloaded,
|
|
90
|
+
click: (target) => body.querySelector('#setup-gate').fire('click', { target }),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
await t('the screen lists every step in order, ticks the cleared ones, and opens the first gap', () => {
|
|
95
|
+
const g = bootGate();
|
|
96
|
+
g.show({ complete: false, steps: STEPS(['signed_in', 'cli_setup']) }, { display_name: 'Sam' });
|
|
97
|
+
const gate = g.gate();
|
|
98
|
+
assert.ok(gate, 'drawn');
|
|
99
|
+
assert.equal(gate.getAttribute('aria-modal'), 'true');
|
|
100
|
+
assert.ok(g.htmlClasses.has('setup-gate-open'), 'the hall behind it does not scroll');
|
|
101
|
+
assert.match(gate.querySelector('#setup-gate-h').textContent, /Welcome, Sam/);
|
|
102
|
+
assert.equal(gate.querySelector('.setup-gate__count').textContent, '2 of 4 done');
|
|
103
|
+
const steps = gate.querySelectorAll('.setup-gate__step');
|
|
104
|
+
assert.deepEqual(steps.map((s) => s.className), ['setup-gate__step is-done', 'setup-gate__step is-done', 'setup-gate__step is-current', 'setup-gate__step']);
|
|
105
|
+
assert.match(steps[2].textContent, /Hint pick_disciplines/, 'only the current step shows its hint');
|
|
106
|
+
assert.doesNotMatch(steps[3].textContent, /Hint first_start/);
|
|
107
|
+
assert.deepEqual(gate.querySelectorAll('[data-craft]').map((b) => b.textContent), ['Engineer', 'Artist', 'UI'],
|
|
108
|
+
'the crafts this project offers');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
await t('choosing a craft saves it through PATCH /me/disciplines, the route every picker uses', async () => {
|
|
112
|
+
const g = bootGate();
|
|
113
|
+
g.show({ complete: false, steps: STEPS(['signed_in', 'cli_setup']) });
|
|
114
|
+
const save = () => g.gate().querySelector('[data-save-crafts]');
|
|
115
|
+
assert.equal(save().disabled, true, 'nothing picked, nothing to save');
|
|
116
|
+
g.click(g.gate().querySelector('[data-craft="artist"]'));
|
|
117
|
+
assert.equal(g.gate().querySelector('[data-craft="artist"]').getAttribute('aria-pressed'), 'true');
|
|
118
|
+
assert.equal(save().disabled, false);
|
|
119
|
+
g.setMe({ onboarding: { setup: { complete: false, steps: STEPS(['signed_in', 'cli_setup', 'pick_disciplines']) } } });
|
|
120
|
+
g.click(save());
|
|
121
|
+
await settle(); await settle();
|
|
122
|
+
const patch = g.requests.find((r) => r.method === 'PATCH');
|
|
123
|
+
assert.equal(patch.p, '/api/bongos/me/disciplines');
|
|
124
|
+
assert.deepEqual(JSON.parse(JSON.stringify(patch.body)), { preferred_disciplines: ['artist'] });
|
|
125
|
+
assert.equal(g.gate().querySelector('.setup-gate__count').textContent, '3 of 4 done', 're-read /me and ticked');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
await t('a craft that does not save says so and can be tried again', async () => {
|
|
129
|
+
const g = bootGate({ patchOk: false });
|
|
130
|
+
g.show({ complete: false, steps: STEPS(['signed_in', 'cli_setup']) });
|
|
131
|
+
g.click(g.gate().querySelector('[data-craft="engineer"]'));
|
|
132
|
+
g.click(g.gate().querySelector('[data-save-crafts]'));
|
|
133
|
+
await settle();
|
|
134
|
+
const err = g.gate().querySelector('[data-craft-err]');
|
|
135
|
+
assert.equal(err.hidden, false);
|
|
136
|
+
assert.match(err.textContent, /did not save/);
|
|
137
|
+
assert.equal(g.gate().querySelector('[data-save-crafts]').disabled, false);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
await t('it checks /me while it is up, and lets the builder in the moment setup is complete', async () => {
|
|
141
|
+
const g = bootGate();
|
|
142
|
+
g.show({ complete: false, steps: STEPS(['signed_in', 'cli_setup', 'pick_disciplines']) });
|
|
143
|
+
assert.equal(g.intervals.length, 1, 'it polls');
|
|
144
|
+
g.setMe({ onboarding: { setup: { complete: true, steps: STEPS(['signed_in', 'cli_setup', 'pick_disciplines', 'first_start']) } } });
|
|
145
|
+
g.intervals[0]();
|
|
146
|
+
await settle();
|
|
147
|
+
assert.equal(g.gate(), null, 'the screen is gone');
|
|
148
|
+
assert.ok(!g.htmlClasses.has('setup-gate-open'));
|
|
149
|
+
assert.equal(g.reloaded(), 1, 'the page reloads into the hall');
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
await t('a complete setup draws nothing', () => {
|
|
153
|
+
const g = bootGate();
|
|
154
|
+
g.show({ complete: true, steps: STEPS(['signed_in', 'cli_setup', 'pick_disciplines', 'first_start']) });
|
|
155
|
+
assert.equal(g.gate(), null);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// ---- the shell: loads the screen, and nothing else is drawn over it ---------
|
|
159
|
+
|
|
160
|
+
function bootShell({ deferLoad = false } = {}) {
|
|
161
|
+
const loaded = [];
|
|
162
|
+
const pending = [];
|
|
163
|
+
const asked = [];
|
|
164
|
+
let sandbox = null;
|
|
165
|
+
const stubEl = () => ({
|
|
166
|
+
dataset: {}, style: {}, textContent: '', hidden: false, innerHTML: '',
|
|
167
|
+
classList: { add() {}, remove() {}, toggle() { return false; }, contains() { return false; } },
|
|
168
|
+
setAttribute() {}, getAttribute() { return null; }, removeAttribute() {}, addEventListener() {},
|
|
169
|
+
querySelector() { return null; }, querySelectorAll() { return []; }, appendChild() {}, focus() {},
|
|
170
|
+
});
|
|
171
|
+
const els = new Map();
|
|
172
|
+
const document = {
|
|
173
|
+
body: { dataset: { page: 'work' }, appendChild() {} },
|
|
174
|
+
documentElement: { dataset: {}, style: {}, classList: { add() {}, remove() {}, toggle() { return false; }, contains() { return false; } } },
|
|
175
|
+
getElementById(id) { if (!els.has(id)) els.set(id, stubEl()); return els.get(id); },
|
|
176
|
+
querySelectorAll() { return []; }, createElement: () => stubEl(),
|
|
177
|
+
addEventListener() {}, dispatchEvent() {}, get cookie() { return ''; }, set cookie(_v) {},
|
|
178
|
+
head: { appendChild(tag) {
|
|
179
|
+
loaded.push(tag.src);
|
|
180
|
+
const land = () => {
|
|
181
|
+
if (/setup-gate/.test(tag.src)) sandbox.window.OTBSetupGate = { show: (setup) => asked.push(['gate', setup.complete]) };
|
|
182
|
+
if (tag.onload) tag.onload();
|
|
183
|
+
};
|
|
184
|
+
if (deferLoad && /setup-gate/.test(tag.src)) pending.push(land); else land();
|
|
185
|
+
} },
|
|
186
|
+
};
|
|
187
|
+
const location = { hostname: 'h', origin: 'https://h', pathname: '/work', hash: '', assign() {}, replace() {} };
|
|
188
|
+
const window = {
|
|
189
|
+
OTB: realOTB(), __BRANDING__: {}, __MODULES__: { enabled: {} },
|
|
190
|
+
BongosClient: { createClient: () => ({ request: () => new Promise(() => {}) }) },
|
|
191
|
+
// the boot pass runs before anyone is signed in, when neither draws anything: count signed-in asks only
|
|
192
|
+
OTBWelcome: { onAccess: (access) => { if (access.authed === true) asked.push(['welcome']); return false; } },
|
|
193
|
+
OTBCraftMode: { onAccess: (access) => { if (access.authed === true) asked.push(['craft']); }, applyNav() {}, mount() {}, setPositions() {}, switchHtml: () => '' },
|
|
194
|
+
matchMedia: () => ({ matches: false, addEventListener() {} }),
|
|
195
|
+
localStorage: { getItem: () => null, setItem() {} },
|
|
196
|
+
requestAnimationFrame() {}, scrollTo() {}, location, addEventListener() {},
|
|
197
|
+
};
|
|
198
|
+
sandbox = { window, document, location, console, setTimeout, clearTimeout, queueMicrotask,
|
|
199
|
+
CustomEvent: class { constructor(tp, i) { this.type = tp; Object.assign(this, i); } } };
|
|
200
|
+
sandbox.globalThis = sandbox;
|
|
201
|
+
vm.createContext(sandbox);
|
|
202
|
+
vm.runInContext(SHELL, sandbox, { filename: 'shell.js' });
|
|
203
|
+
return { apply: (state) => window.OTBShell.applyAccess(state), land: () => pending.splice(0).forEach((f) => f()), loaded, asked };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
await t('the shell holds an unfinished builder on the setup screen, with no welcome or craft picker over it', () => {
|
|
207
|
+
const s = bootShell();
|
|
208
|
+
s.apply({ authed: true, rank: 'xenos', builder: { id: 9 }, setup: { complete: false, steps: STEPS(['signed_in']) } });
|
|
209
|
+
assert.equal(s.loaded.filter((u) => /^\/builders\/setup-gate\.js/.test(u)).length, 1, 'its file is loaded');
|
|
210
|
+
assert.deepEqual(s.asked, [['gate', false]], 'the screen, and nothing over it');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
await t('the setup screen loads once, however often the shell re-checks access before it lands', () => {
|
|
214
|
+
const s = bootShell({ deferLoad: true });
|
|
215
|
+
const held = { authed: true, rank: 'xenos', builder: { id: 9 }, setup: { complete: false, steps: STEPS(['signed_in']) } };
|
|
216
|
+
s.apply(held);
|
|
217
|
+
s.apply(held);
|
|
218
|
+
s.apply(held);
|
|
219
|
+
assert.equal(s.loaded.filter((u) => /setup-gate\.js/.test(u)).length, 1, 'one script tag, so one screen');
|
|
220
|
+
s.land();
|
|
221
|
+
assert.deepEqual(s.asked, [['gate', false]]);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
await t('a set-up builder loads no setup file and gets the hall as before', () => {
|
|
225
|
+
const s = bootShell();
|
|
226
|
+
s.apply({ authed: true, rank: 'xenos', builder: { id: 9 }, setup: { complete: true, steps: [] } });
|
|
227
|
+
assert.equal(s.loaded.filter((u) => /setup-gate/.test(u)).length, 0);
|
|
228
|
+
assert.ok(!s.asked.some(([k]) => k === 'gate'), 'no setup screen');
|
|
229
|
+
assert.deepEqual(s.asked.slice(0, 2), [['welcome'], ['craft']], 'the daily welcome and craft picker as before');
|
|
230
|
+
const unknown = bootShell();
|
|
231
|
+
unknown.apply({ authed: true, rank: 'xenos', builder: { id: 9 }, setup: null });
|
|
232
|
+
assert.equal(unknown.loaded.filter((u) => /setup-gate/.test(u)).length, 0, 'an older server that sends no setup holds nobody');
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
await t('Home carries no setup banner: the Welcome and Getting started panels are gone', () => {
|
|
236
|
+
const widgets = read('modules/hall-ui/public/hall-widgets.js');
|
|
237
|
+
assert.doesNotMatch(widgets, /w\('welcome-scroll'|w\('onboarding-scroll'/);
|
|
238
|
+
assert.doesNotMatch(read('modules/hall-ui/public/index.html'), /welcome-reopen|Show the setup guide again/);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
console.log(`\n${passed} passed, ${failed} failed`);
|
|
242
|
+
if (failed) process.exit(1);
|
package/tests/task_visuals.mjs
CHANGED
|
@@ -280,7 +280,9 @@ await t('the callers reach for OTBVisual, and no longer for the kit', () => {
|
|
|
280
280
|
assert.ok(!/OTBKit(\.|\[')?\s*(shipVisualThumbHtml|taskVisualFigureHtml|isTaskVisualUrl)/.test(src),
|
|
281
281
|
`${f} still reads a visual renderer off OTBKit`);
|
|
282
282
|
}
|
|
283
|
-
|
|
283
|
+
// task 1005050: Home's ship ledger (the one hall-render.js thumbnail) left with
|
|
284
|
+
// Accomplishments; the Profile's Works ledger is the ship-thumbnail caller now.
|
|
285
|
+
assert.ok(HALL('profile.js').includes('window.OTBVisual.shipVisualThumbHtml'));
|
|
284
286
|
assert.ok(HALL('studio.js').includes('vis.taskVisualFigureHtml('));
|
|
285
287
|
});
|
|
286
288
|
|
package/tests/welcome_boot.mjs
CHANGED
|
@@ -122,6 +122,7 @@ test('Home loads the welcome before the shell, and the shell asks it before the
|
|
|
122
122
|
'welcome-boot.js, then the shell trio');
|
|
123
123
|
assert.ok(at('welcome-boot.css') > 0, 'and its sheet');
|
|
124
124
|
const shell = fs.readFileSync(path.join(HALL, 'shell.js'), 'utf8');
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
// task 1005076: and neither is asked while the setup screen holds the builder (tests/setup_flow.mjs).
|
|
126
|
+
assert.match(shell, /if \(!holdForSetup\(state && state\.builder\) && !\(window\.OTBWelcome && window\.OTBWelcome\.onAccess\(access, state && state\.builder\)\) && CRAFT\) CRAFT\.onAccess\(/,
|
|
127
|
+
'the picker is asked only when setup is done and the welcome did not show');
|
|
127
128
|
});
|