@bongos/core 1.24.42 → 1.24.44
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 -103
- package/docs/copy-inventory.md +115 -165
- package/docs/copy-registry.json +230 -781
- package/docs/file-map.md +1 -1
- package/docs/module-api-changelog.md +4 -0
- package/docs/onboarding/primer.md +1 -1
- package/docs/page-inventory.json +10 -38
- package/docs/page-readings.json +528 -627
- 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 +22 -505
- 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 +2 -2
- 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 +16 -4
- package/modules/hall-ui/public/government.html +4 -4
- package/modules/hall-ui/public/hall-render.js +23 -524
- package/modules/hall-ui/public/hall-widgets.js +11 -109
- 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 +6 -19
- 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.css +7 -138
- package/modules/hall-ui/public/roadmap.js +35 -293
- package/modules/hall-ui/public/roster.html +4 -4
- package/modules/hall-ui/public/settings.html +4 -4
- package/modules/hall-ui/public/shell-trail.js +2 -1
- package/modules/hall-ui/public/shell.js +31 -28
- package/modules/hall-ui/public/street.html +4 -4
- package/modules/hall-ui/public/studio.html +4 -4
- 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 +5 -5
- package/modules/onboarding/founding-mode.js +2 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/bongos/gen-hall-sitemap.js +1 -0
- package/src/bongos/serve-internal.js +6 -6
- package/src/module-api.js +1 -1
- package/tests/craft_mode_nav.mjs +2 -1
- package/tests/curve_controls_delegation.mjs +9 -8
- package/tests/curve_kind_overlay.mjs +5 -5
- package/tests/founding_mode.mjs +5 -3
- package/tests/hall_acclaim_vote_control.mjs +5 -3
- package/tests/hall_audit.mjs +10 -6
- package/tests/hall_board_world.mjs +40 -26
- package/tests/hall_builders_page_pass.mjs +14 -5
- package/tests/hall_goals_band.mjs +88 -0
- package/tests/hall_home_ships_bounded.mjs +10 -21
- package/tests/hall_kit_charts.mjs +7 -5
- package/tests/hall_loading.mjs +2 -1
- package/tests/hall_nav.mjs +41 -39
- package/tests/hall_numbers_page.mjs +100 -0
- package/tests/hall_page_gate_map.mjs +5 -1
- 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/hall_reading_world.mjs +17 -18
- package/tests/hall_sitemap.mjs +2 -1
- package/tests/helpers.mjs +8 -2
- package/tests/leaderboard_roles.mjs +1 -1
- package/tests/module_contributions.mjs +6 -4
- package/tests/nav_permission_atoms.mjs +4 -2
- package/tests/page_tweak_artist.mjs +2 -1
- package/tests/rank_tier_single_source.mjs +3 -0
- package/tests/task_visuals.mjs +3 -1
- package/modules/hall-ui/public/roadmap.html +0 -58
- package/modules/hall-ui/public/roadmap.states.json +0 -31
|
@@ -134,28 +134,17 @@ test('Home: the Standing chip\'s count comes from the small page, with no ledger
|
|
|
134
134
|
assert.equal(el('recent-ships tbody').innerHTML, '', 'the ledger is untouched until its view opens');
|
|
135
135
|
});
|
|
136
136
|
|
|
137
|
-
|
|
138
|
-
|
|
137
|
+
// task 1005050: the full ledger was the Accomplishments view's, and that view
|
|
138
|
+
// folded into the Profile page (whose Works ledger reads its own record). Home
|
|
139
|
+
// now NEVER asks for the whole history — not on a view change, not on a refresh.
|
|
140
|
+
test('Home never downloads the whole history, whatever view event or refresh arrives', async () => {
|
|
141
|
+
const { r, el, fetched, showView } = loadRender({ view: 'standing' });
|
|
139
142
|
r.setRecentShips(homePage(3000));
|
|
140
143
|
showView('standing');
|
|
141
|
-
await r.loadShipLedger();
|
|
142
|
-
assert.deepEqual(fetched, ['/api/bongos/me/recent-ships?limit=all']);
|
|
143
|
-
assert.match(el('ships-count').textContent, /3000 works shipped/);
|
|
144
|
-
showView('home');
|
|
145
|
-
showView('standing');
|
|
146
|
-
await r.loadShipLedger();
|
|
147
|
-
assert.equal(fetched.length, 1, 'reopening the view with nothing new does not refetch');
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
test('a live refresh refetches the open ledger only when the newest ships or the count moved', async () => {
|
|
151
|
-
const { r, fetched } = loadRender({ view: 'standing' });
|
|
152
|
-
r.setRecentShips(homePage(3000));
|
|
153
|
-
await r.loadShipLedger();
|
|
154
|
-
assert.equal(fetched.length, 1, 'opening straight onto Accomplishments loads the ledger');
|
|
155
|
-
r.setRecentShips(homePage(3000)); // a nudge from someone else's write
|
|
156
|
-
await r.loadShipLedger();
|
|
157
|
-
assert.equal(fetched.length, 1, 'an unrelated write does not re-download the history');
|
|
158
144
|
r.setRecentShips(homePage(3001, 3001)); // this builder shipped
|
|
159
|
-
await
|
|
160
|
-
assert.
|
|
145
|
+
await new Promise((done) => setTimeout(done, 0));
|
|
146
|
+
assert.deepEqual(fetched, [], 'no ?limit=all read from Home');
|
|
147
|
+
assert.equal(el('standing-shipped').textContent, '3001', 'the chip follows the newest total');
|
|
148
|
+
assert.equal(r.loadShipLedger, undefined, 'the ledger loader went with the view');
|
|
161
149
|
});
|
|
150
|
+
|
|
@@ -338,7 +338,8 @@ test('Watch\'s trend takes the neutral tone — on an oversight page the accent
|
|
|
338
338
|
});
|
|
339
339
|
|
|
340
340
|
test('the Curve keeps its own line colour, which is not the kit default', () => {
|
|
341
|
-
|
|
341
|
+
// task 1005050: the Curve moved with Accomplishments to the profile's "Just you" screen.
|
|
342
|
+
const src = read('profile-own.js');
|
|
342
343
|
assert.match(src, /sparklineHtml\(values, \{ color: 'var\(--accent-deep\)'/,
|
|
343
344
|
'the Curve\'s first chart is accent-deep; the kit\'s fallback is --accent');
|
|
344
345
|
assert.match(src, /OTBKit\.barSeriesHtml\(/, 'and its kinds row draws through the kit');
|
|
@@ -348,13 +349,14 @@ test('the kit is loaded before every page that draws through it', () => {
|
|
|
348
349
|
// A page script reading window.OTBKit at render time with the kit loaded
|
|
349
350
|
// after it is a blank chart in production and a green unit suite.
|
|
350
351
|
for (const [page, script] of [
|
|
351
|
-
['
|
|
352
|
+
['profile.html', 'profile-own.js'],
|
|
352
353
|
['watch.html', 'watch.js'],
|
|
353
354
|
['profile.html', 'profile-charts.js'],
|
|
354
355
|
// task 1003447: the roadmap's own series module reads the kit, and the page
|
|
355
|
-
// reads that. Both edges of the chain are ordered here
|
|
356
|
-
|
|
357
|
-
['
|
|
356
|
+
// reads that. Both edges of the chain are ordered here — on Goals since task
|
|
357
|
+
// 1005051 folded the Roadmap into its top.
|
|
358
|
+
['goals.html', 'roadmap-timeline.js'],
|
|
359
|
+
['goals.html', 'roadmap.js'],
|
|
358
360
|
]) {
|
|
359
361
|
// The loaded ORDER, read off the script tags — index.html's prose mentions
|
|
360
362
|
// hall-render.js ~90 lines above where it loads it, so a substring search
|
package/tests/hall_loading.mjs
CHANGED
|
@@ -135,7 +135,8 @@ test('no hall page waits on a bare line of text any more', () => {
|
|
|
135
135
|
assert.deepEqual(misses, [], 'every waiting placeholder wears the loader');
|
|
136
136
|
// The table placeholder rows keep their cell, with the loader inside it.
|
|
137
137
|
assert.match(read('work.html'), /<td colspan="6" class="ledger__empty"><span class="hall-loading" role="status">Loading…<\/span><\/td>/);
|
|
138
|
-
|
|
138
|
+
// task 1005050: the karma feed's table moved with Accomplishments into profile.html.
|
|
139
|
+
assert.match(read('profile.html'), /<td colspan="4" class="ledger__empty"><span class="hall-loading" role="status">Loading…<\/span><\/td>/);
|
|
139
140
|
});
|
|
140
141
|
|
|
141
142
|
test('the placeholders the page scripts write wear it too, and a failure stops it', () => {
|
package/tests/hall_nav.mjs
CHANGED
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
// - every nav href resolves to a page that exists ("all links land"),
|
|
11
11
|
// - a group's head is never more permissive than its most permissive item
|
|
12
12
|
// (otherwise the head renders over an empty group),
|
|
13
|
-
// - Achievements has no nav entry,
|
|
14
|
-
//
|
|
13
|
+
// - Achievements has no nav entry, and since task 1005050 neither has
|
|
14
|
+
// Accomplishments: both folded into the Profile page, and every old
|
|
15
|
+
// #achievements / #accomplishments / #standing link redirects there.
|
|
15
16
|
//
|
|
16
17
|
// Pure vm + file-read (no DB/network) — runs in the DB-free unit gate.
|
|
17
18
|
import assert from 'node:assert/strict';
|
|
@@ -290,13 +291,17 @@ test('sidebar groups read by audience, in order', () => {
|
|
|
290
291
|
// default-off market-watch module is not enabled (this boot reports no module state).
|
|
291
292
|
// task 1005071: the project group split three ways. Work is the project's state;
|
|
292
293
|
// Make is where things get made; Govern is the board's rooms.
|
|
293
|
-
|
|
294
|
+
// task 1005051: 'roadmap' left — the version's progress and timeline open Goals,
|
|
295
|
+
// and /roadmap redirects there (owner decision 2026-11-03, idea 1001315).
|
|
296
|
+
assert.deepEqual(nav[0].items.map((i) => i.id), ['home', 'work', 'goals', 'goal-map', 'blockers', 'thinking'],
|
|
294
297
|
'Work = the project itself (task 1005071 order)');
|
|
298
|
+
assert.ok(!nav.flatMap((g) => g.items).some((i) => i.href === '/roadmap'), 'no item points at the folded Roadmap');
|
|
295
299
|
assert.deepEqual(nav[1].items.map((i) => i.id), ['studio', 'market-watch'], 'Make = where things get made');
|
|
296
300
|
// task 1004685 (the living-city prototype, round-3 board 6): 'my-agents' joins You —
|
|
297
301
|
// how your own Claude works for you, the personal half of "the city steers
|
|
298
302
|
// everyone, My agents steers yours". Its subject is the viewer's own agents.
|
|
299
|
-
|
|
303
|
+
// task 1005050: 'standing' (Accomplishments) left — your numbers are the one Profile page.
|
|
304
|
+
assert.deepEqual(nav[3].items.map((i) => i.id), ['profile', 'my-agents'], 'You = your own numbers and your own agents');
|
|
300
305
|
// task 1003444: 'people' LEFT the group — the owner deleted the hall's
|
|
301
306
|
// cross-project directory (ADR 0337 D5); its home is the hub, and /people
|
|
302
307
|
// redirects to /roster. Builders is the hall's directory now.
|
|
@@ -556,16 +561,13 @@ test('#leaderboard REDIRECTS to /roster from the index, and only from the index'
|
|
|
556
561
|
assert.deepEqual(unknown.replaced, [], 'an unknown hash is not a redirect');
|
|
557
562
|
});
|
|
558
563
|
|
|
559
|
-
test('Achievements has no nav entry,
|
|
564
|
+
test('Achievements has no nav entry, and #achievements lands on your own profile', () => {
|
|
560
565
|
const ids = parseNav(boot().sidebarHtml).flatMap((g) => g.items.map((i) => i.id));
|
|
561
566
|
assert.ok(!ids.includes('achievements'), 'the nav entry retired with task 2175');
|
|
562
|
-
|
|
563
|
-
//
|
|
564
|
-
// bookmarks and the Standing stat tiles still link #achievements.
|
|
567
|
+
// task 1005050: the Standing view it routed to folded into Profile, whose
|
|
568
|
+
// overview carries your achievements.
|
|
565
569
|
const idx = boot({ page: 'home', pathname: '/', hash: '#achievements' });
|
|
566
|
-
assert.
|
|
567
|
-
assert.equal(idx.viewHidden('home'), true, 'and hides Home');
|
|
568
|
-
assert.equal(idx.shell.currentView(), 'standing');
|
|
570
|
+
assert.deepEqual(idx.replaced, ['/profile'], '#achievements redirects to your own profile');
|
|
569
571
|
});
|
|
570
572
|
|
|
571
573
|
test('collapsible groups carry a stable storage key (collapse persistence)', () => {
|
|
@@ -860,7 +862,7 @@ test('POSITION: ?as=nobody views the hall with no seat, and can only hide', asyn
|
|
|
860
862
|
assert.equal(b.navHidden('my-agents'), false);
|
|
861
863
|
// a xenos stand-in stays a xenos: the pretend rank never rises above the real one
|
|
862
864
|
const x = await seated({ builder: { id: '9', rank: 'xenos' }, positions: block([lead('planning', 'Planning Lead')]), search: '?as=nobody' });
|
|
863
|
-
assert.equal(x.navHidden('
|
|
865
|
+
assert.equal(x.navHidden('goal-map'), true, 'a member page stays hidden from a real xenos');
|
|
864
866
|
});
|
|
865
867
|
|
|
866
868
|
test('My agents draws its own glyph and lands on a real page', () => {
|
|
@@ -871,35 +873,35 @@ test('My agents draws its own glyph and lands on a real page', () => {
|
|
|
871
873
|
assert.ok(hrefResolves(item), 'my-agents.html exists');
|
|
872
874
|
});
|
|
873
875
|
|
|
874
|
-
// task
|
|
875
|
-
//
|
|
876
|
-
//
|
|
877
|
-
|
|
876
|
+
// task 1005050 (owner decision 2026-11-03, idea 1001315): Accomplishments
|
|
877
|
+
// folded into the Profile page — one page for your numbers. Its nav item is gone
|
|
878
|
+
// and every old link, by either of its names, lands on your OWN profile: the
|
|
879
|
+
// section it named, or the "Just you" screen that holds its private half.
|
|
880
|
+
test('Accomplishments is gone from the sidebar, and every old link lands on your own profile', () => {
|
|
878
881
|
const sidebar = boot().sidebarHtml;
|
|
879
|
-
const
|
|
880
|
-
assert.ok(
|
|
881
|
-
assert.
|
|
882
|
-
const
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
882
|
+
const ids = parseNav(sidebar).flatMap((g) => g.items).map((i) => i.id);
|
|
883
|
+
assert.ok(!ids.includes('standing'), 'no Accomplishments item');
|
|
884
|
+
assert.ok(!/>Accomplishments</.test(sidebar), 'nothing in the nav is called Accomplishments');
|
|
885
|
+
const LANDS = {
|
|
886
|
+
'#accomplishments': '/profile#yours',
|
|
887
|
+
'#accomplishments-karma': '/profile#yours',
|
|
888
|
+
'#accomplishments-progress': '/profile#yours',
|
|
889
|
+
'#accomplishments-work': '/profile#works',
|
|
890
|
+
'#accomplishments-achievements': '/profile',
|
|
891
|
+
'#standing': '/profile#yours',
|
|
892
|
+
'#standing-karma': '/profile#yours',
|
|
893
|
+
'#standing-work': '/profile#works',
|
|
894
|
+
};
|
|
895
|
+
for (const [hash, to] of Object.entries(LANDS)) {
|
|
887
896
|
const idx = boot({ page: 'home', pathname: '/', hash });
|
|
888
|
-
assert.
|
|
889
|
-
assert.equal(idx.viewHidden('home'), true, `${hash} hides Home`);
|
|
890
|
-
}
|
|
891
|
-
const html = fs.readFileSync(path.join(HALL, 'index.html'), 'utf8');
|
|
892
|
-
assert.match(html, /id="view-standing" data-view-title="Accomplishments"/, 'the top bar names it Accomplishments');
|
|
893
|
-
assert.ok(!/href="#standing/.test(fs.readFileSync(path.join(HALL, 'hall-widgets.js'), 'utf8')), 'Home links use the new name');
|
|
894
|
-
});
|
|
895
|
-
|
|
896
|
-
test('the Accomplishments band carries no notices — Home\'s attention cards own them (task 1004826)', () => {
|
|
897
|
-
const src = fs.readFileSync(path.join(HALL, 'hall-render.js'), 'utf8');
|
|
898
|
-
const fn = src.slice(src.indexOf(' function renderProfile(builder) {'), src.indexOf(' function renderSystemNotices('));
|
|
899
|
-
const band = fn.slice(fn.indexOf("$('#profile-body').innerHTML"), fn.indexOf("const welcomeLine"));
|
|
900
|
-
assert.ok(band.length > 100, 'sanity: found the band template');
|
|
901
|
-
for (const cls of ['profile__rebase', 'profile__needs', 'profile__budget']) {
|
|
902
|
-
assert.ok(!band.includes(cls), `${cls} no longer renders on the band`);
|
|
897
|
+
assert.deepEqual(idx.replaced, [to], `${hash} lands on ${to}`);
|
|
903
898
|
}
|
|
899
|
+
// No view is left for them to open on the index.
|
|
900
|
+
assert.ok(!/id="view-standing"/.test(fs.readFileSync(path.join(HALL, 'index.html'), 'utf8')), 'the view is gone');
|
|
901
|
+
// Home's Standing card still names the shipped count and links to the profile.
|
|
902
|
+
const render = fs.readFileSync(path.join(HALL, 'hall-render.js'), 'utf8');
|
|
903
|
+
assert.match(render, /View full profile →/);
|
|
904
|
+
assert.ok(!/\$\('#profile-body'\)/.test(render), 'the Accomplishments band is not drawn any more');
|
|
905
|
+
const fn = render.slice(render.indexOf(' function renderProfile(builder) {'), render.indexOf(' function renderSystemNotices('));
|
|
904
906
|
assert.match(fn, /renderSystemNotices\(\{ rebaseTasks, need, actionNeeds, cost \}\)/, 'Home still gets every notice');
|
|
905
907
|
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// tests/hall_numbers_page.mjs — your numbers on one page (task 1005050, owner
|
|
2
|
+
// decision 2026-11-03, idea 1001315 item 2).
|
|
3
|
+
//
|
|
4
|
+
// Accomplishments (Home's 'standing' view) and Profile both showed your shipped
|
|
5
|
+
// work, karma and achievements. Profile is the one page now:
|
|
6
|
+
//
|
|
7
|
+
// * ONE PAGE. The Accomplishments view, its nav item and its widgets are gone;
|
|
8
|
+
// its private half — where you stand, reviews, the karma feed, peer
|
|
9
|
+
// recognition and the progress curve — is Profile's "Just you" screen.
|
|
10
|
+
// * NOTHING PRIVATE ON SOMEONE ELSE'S PROFILE. profile-own.js runs only when the
|
|
11
|
+
// profile ASKED FOR is the viewer's own; on anyone else's it reads nothing and
|
|
12
|
+
// draws nothing, whatever the hash says.
|
|
13
|
+
// * OLD LINKS LAND ON YOUR OWN PROFILE (shell.js VIEW_REDIRECTS; held, with the
|
|
14
|
+
// sidebar, in tests/hall_nav.mjs).
|
|
15
|
+
//
|
|
16
|
+
// These run the real profile.js and profile-own.js against the shared profile
|
|
17
|
+
// harness (tests/helpers.mjs bootProfilePage).
|
|
18
|
+
//
|
|
19
|
+
// Run: node tests/hall_numbers_page.mjs
|
|
20
|
+
|
|
21
|
+
import { test } from 'node:test';
|
|
22
|
+
import assert from 'node:assert/strict';
|
|
23
|
+
import fs from 'node:fs';
|
|
24
|
+
import path from 'node:path';
|
|
25
|
+
import { fileURLToPath } from 'node:url';
|
|
26
|
+
import { bootProfilePage } from './helpers.mjs';
|
|
27
|
+
|
|
28
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
29
|
+
const HALL = path.join(ROOT, 'modules', 'hall-ui', 'public');
|
|
30
|
+
const read = (f) => fs.readFileSync(path.join(HALL, f), 'utf8');
|
|
31
|
+
const settle = async () => { for (let i = 0; i < 30; i++) await new Promise((r) => setImmediate(r)); };
|
|
32
|
+
|
|
33
|
+
// The reads only the "Just you" screen makes: the peer-recognition feed and the
|
|
34
|
+
// viewer's own progress curve.
|
|
35
|
+
const OWN_READS = /\/tasks\/peer-votes\/feed|\/analytics\/builder\//;
|
|
36
|
+
|
|
37
|
+
test('your own profile (no ?id) draws the "Just you" screen', async () => {
|
|
38
|
+
const p = bootProfilePage({ withOwn: true });
|
|
39
|
+
await settle();
|
|
40
|
+
assert.match(p.el('rank-standing-body').innerHTML, /ranks-standing__title/, 'where you stand is drawn');
|
|
41
|
+
assert.equal(p.el('rank-scroll').hidden, false);
|
|
42
|
+
assert.equal(p.el('grades-scroll').hidden, false, 'the Reviews section shows');
|
|
43
|
+
// The two feeds with reads of their own wait until the screen is opened: a
|
|
44
|
+
// visit for your Works does not pay for them.
|
|
45
|
+
assert.equal(p.requests.filter((u) => OWN_READS.test(u)).length, 0, 'no feed is read before the screen opens');
|
|
46
|
+
p.window.OTBProfileOwn.open();
|
|
47
|
+
await settle();
|
|
48
|
+
assert.ok(p.requests.some((u) => /\/tasks\/peer-votes\/feed/.test(u)), 'the peer-recognition feed is read');
|
|
49
|
+
assert.ok(p.requests.some((u) => /\/analytics\/builder\/95\?/.test(u)), 'your own curve is read');
|
|
50
|
+
const n = p.requests.length;
|
|
51
|
+
p.window.OTBProfileOwn.open();
|
|
52
|
+
await settle();
|
|
53
|
+
assert.equal(p.requests.length, n, 'opening it again reads nothing more');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('?id= naming yourself is still your own profile', async () => {
|
|
57
|
+
const p = bootProfilePage({ withOwn: true, search: '?id=95' });
|
|
58
|
+
await settle();
|
|
59
|
+
assert.match(p.el('rank-standing-body').innerHTML, /ranks-standing__title/);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('someone else\'s profile reads and draws nothing private', async () => {
|
|
63
|
+
// The harness answers every profile with builder 95's payload, so this is the
|
|
64
|
+
// case that matters: the data alone would say "yours" — the REQUEST says not.
|
|
65
|
+
const p = bootProfilePage({ withOwn: true, search: '?id=2001' });
|
|
66
|
+
await settle();
|
|
67
|
+
p.window.OTBProfileOwn.open(); // even a forced open has no viewer to read for
|
|
68
|
+
await settle();
|
|
69
|
+
assert.equal(p.requests.filter((u) => OWN_READS.test(u)).length, 0, 'no private read is made');
|
|
70
|
+
assert.equal(p.el('rank-standing-body').innerHTML, '', 'where you stand is not drawn');
|
|
71
|
+
assert.equal(p.el('recent-grades tbody').innerHTML, '', 'no reviews are drawn');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('a signed-out visitor gets no "Just you" screen', async () => {
|
|
75
|
+
const p = bootProfilePage({ withOwn: true, signedIn: false, search: '?id=95' });
|
|
76
|
+
await settle();
|
|
77
|
+
assert.equal(p.requests.filter((u) => OWN_READS.test(u)).length, 0);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('the screen starts hidden, rail key and all, and only profile.js opens it', () => {
|
|
81
|
+
const html = read('profile.html');
|
|
82
|
+
for (const id of ['rank-scroll', 'grades-scroll', 'karma-scroll', 'acclaim-scroll', 'analytics-scroll']) {
|
|
83
|
+
assert.match(html, new RegExp(`id="${id}"[^>]*data-group="yours" hidden>`), `#${id} starts hidden`);
|
|
84
|
+
}
|
|
85
|
+
const js = read('profile.js');
|
|
86
|
+
assert.match(js, /if \(group === 'yours' && window\.OTBProfileOwn && window\.OTBProfileOwn\.open\) window\.OTBProfileOwn\.open\(\);/, 'the feeds load when the screen first opens');
|
|
87
|
+
assert.match(js, /\(i\.id !== 'yours' \|\| ownShown\)/, 'the rail offers the key only once the screen is drawn');
|
|
88
|
+
assert.match(js, /if \(ownRequest && selfId != null && selfMe && window\.OTBProfileOwn\)/, 'drawn only for your own request');
|
|
89
|
+
const i = (s) => html.indexOf(s);
|
|
90
|
+
assert.ok(i('/builders/profile-own.js') > 0 && i('/builders/profile-own.js') < i('/builders/profile.js?'), 'loaded before profile.js');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('Home carries no Accomplishments view, and no renderer of what moved', () => {
|
|
94
|
+
assert.ok(!/id="view-standing"/.test(read('index.html')));
|
|
95
|
+
assert.ok(!/view: 'standing'/.test(read('hall-widgets.js')), 'no widget mounts into it');
|
|
96
|
+
for (const fn of ['renderRecentGrades', 'renderKarmaFeed', 'renderAcclaimFeed', 'loadAnalytics', 'renderCurveBody', 'renderRankStanding', 'acclaimEvidenceHtml']) {
|
|
97
|
+
assert.ok(!new RegExp(`function ${fn}\\b`).test(read('builders.js') + read('hall-render.js')), `${fn} moved, not copied`);
|
|
98
|
+
assert.match(read('profile-own.js'), new RegExp(`function ${fn}\\b`), `${fn} lives in profile-own.js`);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
@@ -70,7 +70,9 @@ const ORACLE = {
|
|
|
70
70
|
// the URL now serves THE PROJECT SKY (the same page as /thinking, in its whole-project
|
|
71
71
|
// lens) on the same tier, and holds the nav slot under Goals the map was denied.
|
|
72
72
|
'/builders/goal-map': 'requireBuilderPage',
|
|
73
|
-
|
|
73
|
+
// Gone (task 1005051, owner decision 2026-11-03): the Roadmap is the top of Goals,
|
|
74
|
+
// and /builders/roadmap 302s there before any gate runs.
|
|
75
|
+
'/builders/roadmap': null,
|
|
74
76
|
// Gated at this tier all along, but with no row until task 1003448 walked the
|
|
75
77
|
// module's pages: copy-desk (task 1003113) and ideas (task 1002928) are open to
|
|
76
78
|
// every rank by design, and collab's reads (task 1003172) are scoped to the
|
|
@@ -365,6 +367,8 @@ const GONE_PAGES = {
|
|
|
365
367
|
// Task 1005049 — one oversight page: each is a tab on Watch, and the old URL 302s to it.
|
|
366
368
|
'/builders/sessions': 'task 1005049 — the session log is Watch\'s Sessions tab; /sessions redirects there',
|
|
367
369
|
'/builders/gate': 'task 1005049 — the runner panel is Watch\'s Autobongos tab; /gate redirects there',
|
|
370
|
+
// Task 1005051 — the version's progress and timeline open Goals; /roadmap redirects there.
|
|
371
|
+
'/builders/roadmap': 'task 1005051 — the roadmap is the top of Goals; /roadmap redirects there',
|
|
368
372
|
};
|
|
369
373
|
|
|
370
374
|
test('a row that is null because the page left the hall serves no page', () => {
|
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');
|
|
@@ -74,7 +74,8 @@ const primerJs = read('primer.js');
|
|
|
74
74
|
const viewerJs = read('diagram-viewer.js');
|
|
75
75
|
const mdReaderJs = read('md-reader.js');
|
|
76
76
|
const primerHtml = read('primer.html');
|
|
77
|
-
|
|
77
|
+
// task 1005051: the Roadmap folded into the top of Goals; its band draws there.
|
|
78
|
+
const roadmapHtml = read('goals.html');
|
|
78
79
|
|
|
79
80
|
const decomment = (css) => css.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
80
81
|
// The body of the first rule matching `selector {`, comments stripped. Naive on
|
|
@@ -106,12 +107,12 @@ test('no hall page sheet declares a :root of its own', () => {
|
|
|
106
107
|
'the pack :root is spliced at the end of <head> — a page-local one is outranked one way and defeats re-branding the other. Put page tokens on body.');
|
|
107
108
|
});
|
|
108
109
|
|
|
109
|
-
test('roadmap.css
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
110
|
+
test('roadmap.css keeps no page-local tokens and draws no shadow', () => {
|
|
111
|
+
// task 1005051: the two body tokens (--rm-plan, --rm-terra-weak) painted the
|
|
112
|
+
// goal summaries and the staged cards, which went with the page; nothing on the
|
|
113
|
+
// Goals band spends them, so they left with them rather than lingering unread.
|
|
114
|
+
assert.doesNotMatch(decomment(roadmapCss), /--rm-(plan|terra-weak)\s*:/, 'no unread page token left behind');
|
|
115
|
+
assert.doesNotMatch(decomment(roadmapCss), /:root\s*\{/, 'and never a page-local :root');
|
|
115
116
|
assert.doesNotMatch(decomment(roadmapCss), /--rm-shadow-soft/,
|
|
116
117
|
'v3 separates by TONE — a panel is never a drawn box and never a shadow');
|
|
117
118
|
assert.doesNotMatch(decomment(roadmapCss), /box-shadow\s*:\s*(?!inset)[^;]*(?:rgba|0 )/,
|
|
@@ -350,7 +351,7 @@ test('the viewer drops the white ground mermaid bakes into the SVG', () => {
|
|
|
350
351
|
// ---- 8. every room has exactly one h1 -------------------------------------
|
|
351
352
|
|
|
352
353
|
// task 1005046: diagrams.html and ranks.html folded into the primer's chapters.
|
|
353
|
-
const ROOMS = [['primer.html', primerHtml], ['
|
|
354
|
+
const ROOMS = [['primer.html', primerHtml], ['goals.html', roadmapHtml]];
|
|
354
355
|
|
|
355
356
|
// An HTML comment that mentions <h1> is prose, not markup — and these pages each
|
|
356
357
|
// carry one explaining why the heading is there at all.
|
|
@@ -369,7 +370,7 @@ test('the DOCS room takes the room voice; the deck takes the board\'s', () => {
|
|
|
369
370
|
assert.match(html, /class="page-head page-head--room"/, `${name} is a reading room`);
|
|
370
371
|
}
|
|
371
372
|
assert.match(roadmapHtml, /class="page-head page-head--board"/,
|
|
372
|
-
'the roadmap is a working surface whose figures are the thing to look at, so its own name stays quiet');
|
|
373
|
+
'Goals, which carries the roadmap band, is a working surface whose figures are the thing to look at, so its own name stays quiet');
|
|
373
374
|
});
|
|
374
375
|
|
|
375
376
|
test('the document\'s own h1 is suppressed, or the article would render a second one', () => {
|
|
@@ -418,19 +419,17 @@ test('no reading room carries an inline <style>, and none needs an audit excepti
|
|
|
418
419
|
// was the last page carrying a one-line block; task 1003440 deleted the page.)
|
|
419
420
|
});
|
|
420
421
|
|
|
421
|
-
test('
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
422
|
+
test('the version band\'s sub-24px link clears the floor', () => {
|
|
423
|
+
// task 1005051: the goal-row title (.rm-goal__t) went with the goal summaries
|
|
424
|
+
// when the Roadmap folded into Goals — the goals are the list right below — so
|
|
425
|
+
// the band's one small control left is its "View tasks" link.
|
|
426
|
+
assert.equal(rule(roadmapCss, '.rm-goal__t'), null, 'the goal rows are gone, and their rule with them');
|
|
425
427
|
const more = rule(roadmapCss, '.rm-gmore');
|
|
426
428
|
assert.ok(more, '.rm-gmore has a rule');
|
|
427
429
|
assert.match(more, /min-height:\s*24px/, 'measured 19px');
|
|
428
|
-
// the WIDTH half is the one that gets missed: at 320 the four fixed items in
|
|
429
|
-
// the row ate it and `flex: 1; min-width: 0` resolved the title to 0x26
|
|
430
|
-
assert.match(decomment(roadmapCss), /@media\s*\(max-width:400px\)[\s\S]*?\.rm-goal__t\s*\{[^}]*flex:\s*1 0 100%/,
|
|
431
|
-
'and the row wraps before the title can resolve to zero width');
|
|
432
430
|
});
|
|
433
431
|
|
|
432
|
+
|
|
434
433
|
// ---- 10. the parser, actually run -----------------------------------------
|
|
435
434
|
|
|
436
435
|
// md-reader.js is a browser IIFE that only touches `window` and `location`, so
|
|
@@ -709,7 +708,7 @@ test('RUN: a deck whose versions carry no dates renders every other section unha
|
|
|
709
708
|
versions: { versions: [{ id: 'BONGOS-V1', name: 'x', status: 'building', started_at: null, shipped_at: null }] },
|
|
710
709
|
});
|
|
711
710
|
assert.doesNotMatch(html, /In time</, 'no timeline section');
|
|
712
|
-
assert.match(html, /rm-
|
|
711
|
+
assert.match(html, /rm-card--feat/, 'but the version still renders');
|
|
713
712
|
assert.doesNotMatch(html, /NaN|undefined/);
|
|
714
713
|
});
|
|
715
714
|
|
package/tests/hall_sitemap.mjs
CHANGED
|
@@ -21,7 +21,8 @@ const model = gen.build();
|
|
|
21
21
|
test('the parse found the hall, so nothing below passes against an empty map', () => {
|
|
22
22
|
const files = fs.readdirSync(path.join(ROOT, 'modules/hall-ui/public')).filter((f) => f.endsWith('.html'));
|
|
23
23
|
assert.equal(model.pages.length, files.length, 'one row per page file');
|
|
24
|
-
|
|
24
|
+
// 24 since task 1005051 folded the Roadmap into Goals.
|
|
25
|
+
assert.ok(model.nav.length >= 24, `expected the shell's nav, found ${model.nav.length} items`);
|
|
25
26
|
for (const c of ['building', 'artistry', 'ideating', 'governing']) {
|
|
26
27
|
assert.ok(model.crafts.tabs[c].length > model.crafts.always.length, `${c} lists tabs of its own`);
|
|
27
28
|
}
|
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
|
|