@bongos/core 1.24.43 → 1.24.45

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.
Files changed (63) hide show
  1. package/.bongos-core.json +71 -76
  2. package/docs/copy-inventory.md +42 -74
  3. package/docs/copy-registry.json +87 -446
  4. package/docs/file-map.md +1 -1
  5. package/docs/module-api-changelog.md +4 -0
  6. package/docs/page-inventory.json +8 -36
  7. package/docs/page-readings.json +374 -431
  8. package/modules/hall-ui/public/ai-studio.html +2 -2
  9. package/modules/hall-ui/public/atlas.html +2 -2
  10. package/modules/hall-ui/public/blockers.html +2 -2
  11. package/modules/hall-ui/public/board-room.html +2 -2
  12. package/modules/hall-ui/public/city.html +2 -2
  13. package/modules/hall-ui/public/collab.html +2 -2
  14. package/modules/hall-ui/public/commands.html +2 -2
  15. package/modules/hall-ui/public/copy-desk.html +2 -2
  16. package/modules/hall-ui/public/craft-mode.js +4 -1
  17. package/modules/hall-ui/public/deploy.html +2 -2
  18. package/modules/hall-ui/public/goal-map.html +2 -2
  19. package/modules/hall-ui/public/goals.html +14 -2
  20. package/modules/hall-ui/public/government.html +2 -2
  21. package/modules/hall-ui/public/idea.html +2 -2
  22. package/modules/hall-ui/public/ideas.html +2 -2
  23. package/modules/hall-ui/public/index.html +2 -2
  24. package/modules/hall-ui/public/market-watch-item.html +2 -2
  25. package/modules/hall-ui/public/market-watch-rules.html +2 -2
  26. package/modules/hall-ui/public/market-watch.html +2 -2
  27. package/modules/hall-ui/public/modules.html +2 -2
  28. package/modules/hall-ui/public/money.html +2 -2
  29. package/modules/hall-ui/public/my-agents.html +2 -2
  30. package/modules/hall-ui/public/primer.html +2 -2
  31. package/modules/hall-ui/public/profile.html +2 -2
  32. package/modules/hall-ui/public/project-settings.html +2 -2
  33. package/modules/hall-ui/public/roadmap.css +7 -138
  34. package/modules/hall-ui/public/roadmap.js +35 -293
  35. package/modules/hall-ui/public/roster.html +2 -2
  36. package/modules/hall-ui/public/settings.html +2 -2
  37. package/modules/hall-ui/public/shell.js +24 -4
  38. package/modules/hall-ui/public/street.html +2 -2
  39. package/modules/hall-ui/public/studio.html +2 -2
  40. package/modules/hall-ui/public/task.html +2 -2
  41. package/modules/hall-ui/public/thinking.html +2 -2
  42. package/modules/hall-ui/public/tweak-editor.html +2 -2
  43. package/modules/hall-ui/public/watch.html +2 -2
  44. package/modules/hall-ui/public/work.html +3 -3
  45. package/modules/hall-ui/records/hall-navigation.md +5 -0
  46. package/modules/onboarding/founding-mode.js +2 -1
  47. package/package-lock.json +2 -2
  48. package/package.json +1 -1
  49. package/release-notes.json +12 -0
  50. package/scripts/bongos/gen-hall-sitemap.js +4 -1
  51. package/src/bongos/serve-internal.js +6 -6
  52. package/src/module-api.js +1 -1
  53. package/tests/craft_mode_nav.mjs +53 -4
  54. package/tests/founding_mode.mjs +5 -3
  55. package/tests/hall_goals_band.mjs +88 -0
  56. package/tests/hall_kit_charts.mjs +4 -3
  57. package/tests/hall_nav.mjs +5 -2
  58. package/tests/hall_page_gate_map.mjs +5 -1
  59. package/tests/hall_reading_world.mjs +17 -18
  60. package/tests/hall_sitemap.mjs +2 -1
  61. package/tests/nav_permission_atoms.mjs +4 -2
  62. package/modules/hall-ui/public/roadmap.html +0 -58
  63. package/modules/hall-ui/public/roadmap.states.json +0 -31
@@ -28,3 +28,8 @@ Covers: every shelled hall page (`shell.js`, `style.css`), plus `goal-map.html`
28
28
  `goal-map.html` now uses thinking.html's structure: sidebar and top-bar mounts, `style.css`, `shell.js`, the sky inside `<main id="hall-main" class="sky-stage">`, and a `<div class="brand">` because the shell's bar is the page's one `<header>`. In `thinking.css` both pages share one stage rule, `body[data-page] .sky-stage`. No hall page is standalone any more (`tests/hall_audit.mjs` `STANDALONE_EXEMPT` is empty).
29
29
 
30
30
  Tests: `tests/hall_fullscreen.mjs` (the standard switch, `plain`, Back link, trail, fallback and exit cases), `tests/hall_record_world.mjs` §10 (the record places its exit), `tests/goal_map_page.mjs`, `tests/hall_audit.mjs` §8.
31
+
32
+ ## The rail paints at once, and Home is one button (task 1005070)
33
+
34
+ - Every hall page is a full load, and the gate waits on `/me`, so the rail used to boot with only Home and pop the rest in once `/me` answered — longest for the owner, whose `/me` is the heaviest. `shell.js` now keeps the last settled access in `localStorage['hall-nav-access']` (rank, graduated/showWork, the nav permission map, the user chip; never the needs roster, so no badge is stale) and paints from it at boot. `/me` still settles the real state. It is forgotten on any signed-out settle (a 401 or a refused 403) and by the sign-out link, and is not painted once a day old.
35
+ - In a craft the plate under the brand is the Home button (task 1005065), so no mode's tab list names `home` — Building used to, which drew two home icons. With no craft chosen there is no plate and the Home tab shows. Pinned in `tests/craft_mode_nav.mjs`.
@@ -34,7 +34,8 @@ const STAGES = Object.freeze(['decide', 'scope', 'strategize', 'charter', 'legal
34
34
  // 'community' wakes only when the planet is ALIVE, which is when founding mode switches
35
35
  // off entirely, so during founding it is always asleep.
36
36
  const WAKES = Object.freeze({
37
- scope: Object.freeze(['roadmap', 'goals', 'goal-map']),
37
+ // task 1005051: Roadmap folded into Goals, so scope wakes Goals and its map.
38
+ scope: Object.freeze(['goals', 'goal-map']),
38
39
  strategize: Object.freeze(['work']),
39
40
  charter: Object.freeze(['board-room']),
40
41
  // task 1005046: legalize woke Economy and legislation woke Ranks. Both pages
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.24.43",
3
+ "version": "1.24.45",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.24.43",
9
+ "version": "1.24.45",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.24.43",
3
+ "version": "1.24.45",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
@@ -11221,5 +11221,17 @@
11221
11221
  "id": "1005050",
11222
11222
  "text": "Your numbers are on one page now: your Profile. The separate Accomplishments page is gone from the sidebar. The parts only you should see — your rank and how to reach the next one, how your work was reviewed, your karma histor"
11223
11223
  }
11224
+ ],
11225
+ "1.24.44": [
11226
+ {
11227
+ "id": "1005051",
11228
+ "text": "Goals now opens with the version's progress and the timeline of every version, so the separate Roadmap page is gone from the sidebar. Old Roadmap links open Goals. The Tasks board's 'see the version' line points at Goals too."
11229
+ }
11230
+ ],
11231
+ "1.24.45": [
11232
+ {
11233
+ "id": "1005070",
11234
+ "text": "The hall's sidebar now appears instantly on every page instead of popping in late, and Building mode shows a single Home button."
11235
+ }
11224
11236
  ]
11225
11237
  }
@@ -64,6 +64,7 @@ const ROUTES = {
64
64
  '/diagrams': { to: '/primer', why: 'now a Primer chapter, redirects; its images still serve (task 1005046)' },
65
65
  '/sessions': { to: '/watch', why: 'now a Watch tab, redirects (task 1005049)' },
66
66
  '/gate': { to: '/watch', why: 'Autobongos, now a Watch tab, redirects (task 1005049)' },
67
+ '/roadmap': { to: '/goals', why: 'now the top of Goals, redirects (task 1005051)' },
67
68
  };
68
69
  // Targets that leave the hall on purpose: sign-in, the hub's /account (platform-server.js;
69
70
  // settings.js points the link at the hub), the API, module assets, and any file with an extension.
@@ -268,7 +269,9 @@ function build() {
268
269
  };
269
270
  });
270
271
 
271
- const inAnyCraft = new Set(CRAFTS.flatMap((c) => crafts.tabs[c]));
272
+ // 'home' is in every craft: there the plate under the brand IS the Home button
273
+ // (tasks 1005065, 1005070), so no mode lists the Home tab beside it.
274
+ const inAnyCraft = new Set(['home', ...CRAFTS.flatMap((c) => crafts.tabs[c])]);
272
275
  const findings = {
273
276
  broken: dedupe(broken).sort((a, b) => (a.from + a.href).localeCompare(b.from + b.href)),
274
277
  orphan: rows.filter((r) => r.url !== '/' && !r.nav.length && !r.in.length).map((r) => r.url),
@@ -1039,10 +1039,10 @@ function mountInternalSurfaces(app) {
1039
1039
  return res.redirect(302, `${isBuildersHost(req) ? '' : '/builders'}/market-watch${qs}#settings`);
1040
1040
  });
1041
1041
 
1042
- // Pages folded into a section of another page; 302 and no gate, as above. Task 1005047:
1043
- // Fleet costs is /money#hosting. Task 1005049: Sessions and Autobongos are Watch tabs.
1044
- const RETIRED_TO_SECTION = Object.freeze({ fleet: 'money#hosting', sessions: 'watch#sessions', gate: 'watch#autobongos' });
1045
- const RETIRED_SECTION_RE = /^\/builders\/(fleet|sessions|gate)(?:\.html)?\/?$/;
1042
+ // Pages folded into another page; 302 and no gate, as above. Task 1005047: Fleet costs is
1043
+ // /money#hosting; 1005049: Sessions and Autobongos are Watch tabs; 1005051: Roadmap is Goals'.
1044
+ const RETIRED_TO_SECTION = Object.freeze({ fleet: 'money#hosting', sessions: 'watch#sessions', gate: 'watch#autobongos', roadmap: 'goals' });
1045
+ const RETIRED_SECTION_RE = /^\/builders\/(fleet|sessions|gate|roadmap)(?:\.html)?\/?$/;
1046
1046
  app.use((req, res, next) => {
1047
1047
  if (isModuleSurfaceHost(req)) return next();
1048
1048
  if (req.method !== 'GET' && req.method !== 'HEAD') return next();
@@ -1050,7 +1050,7 @@ function mountInternalSurfaces(app) {
1050
1050
  if (!m) return next();
1051
1051
  const qs = req.url.includes('?') ? req.url.slice(req.url.indexOf('?')) : '';
1052
1052
  const [page, section] = RETIRED_TO_SECTION[m[1]].split('#');
1053
- return res.redirect(302, `${isBuildersHost(req) ? '' : '/builders'}/${page}${qs}#${section}`);
1053
+ return res.redirect(302, `${isBuildersHost(req) ? '' : '/builders'}/${page}${qs}${section ? `#${section}` : ''}`);
1054
1054
  });
1055
1055
 
1056
1056
  // task 1005046 (owner decision 2026-11-03, idea 1001311): ONE reading room. Ranks,
@@ -1348,7 +1348,7 @@ function mountInternalSurfaces(app) {
1348
1348
  // whole gate; the governing reads it adds are gated by their own routes.
1349
1349
  // task 1004921 `ai-studio` (the viewer's own routes).
1350
1350
  const SIGNEDIN_BUILDERS_PAGE_RE =
1351
- /^\/builders\/(?:work|settings|atlas|modules|goals|goal-map|roadmap|copy-desk|studio|ai-studio|tweak-editor|ideas|thinking|collab|task|idea|primer(?:\.md)?|my-agents|commands)(?:\.html)?\/?$/;
1351
+ /^\/builders\/(?:work|settings|atlas|modules|goals|goal-map|copy-desk|studio|ai-studio|tweak-editor|ideas|thinking|collab|task|idea|primer(?:\.md)?|my-agents|commands)(?:\.html)?\/?$/;
1352
1352
  // The diagram image assets under /builders/diagrams/ ride the same tier as the
1353
1353
  // page that embeds them — the Primer, since task 1005046 retired the Diagrams
1354
1354
  // page (its URL now 302s to the Primer's first diagram, above).
package/src/module-api.js CHANGED
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
81
81
  // MAJOR (see allowBoxScope below): passes the request through untouched.
82
82
  function deprecatedNoopMiddleware(_req, _res, next) { next(); }
83
83
 
84
- const CORE_VERSION = '1.24.43'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
84
+ const CORE_VERSION = '1.24.45'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
85
85
 
86
86
  // A namespaced logger so a module's log lines are attributable + consistent.
87
87
  // Usage: const log = api.logger('discord'); log.info('mounted');
@@ -63,7 +63,7 @@ function parseSidebar(html) {
63
63
  });
64
64
  }
65
65
 
66
- function boot({ session = {}, perms = { 'board.vote.cast': true } } = {}) {
66
+ function boot({ session = {}, local = {}, perms = { 'board.vote.cast': true } } = {}) {
67
67
  const root = makeEl('HTML');
68
68
  const topbar = makeEl('DIV');
69
69
  const sidebar = makeEl('NAV');
@@ -95,7 +95,7 @@ function boot({ session = {}, perms = { 'board.vote.cast': true } } = {}) {
95
95
  __BRANDING__: {}, __MODULES__: { enabled: { provisioning: true } },
96
96
  BongosClient: { createClient: () => ({ request: () => new Promise(() => {}) }) },
97
97
  matchMedia: () => ({ matches: false, addEventListener() {} }),
98
- localStorage: mk({}), sessionStorage: mk(session), location,
98
+ localStorage: mk(local), sessionStorage: mk(session), location,
99
99
  clearTimeout() {}, setTimeout() { return 0; }, requestAnimationFrame() {}, scrollTo() {},
100
100
  addEventListener() {}, dispatchEvent() { return true; },
101
101
  };
@@ -116,6 +116,7 @@ function boot({ session = {}, perms = { 'board.vote.cast': true } } = {}) {
116
116
  return {
117
117
  craft, root,
118
118
  signIn: (rank = 'archon') => shell.applyAccess({ authed: true, rank, permissions: perms, builder: { id: 7, rank } }),
119
+ signOut: () => shell.applyAccess({ authed: false }),
119
120
  // The tabs a reader can see: the gate let them through AND the mode kept them.
120
121
  showing: () => parsed().flatMap((g) => g.items).filter((a) => !a.hidden && !a.hasAttribute('data-craft-off')).map((a) => a.id.slice(4)),
121
122
  headShowing: (group) => {
@@ -146,10 +147,24 @@ test('THE ALWAYS-PRESENT LIST: one shared list, Builders first, then Collab and
146
147
 
147
148
  test('every tab a mode names is a real NAV item, and every NAV item lives in some mode', () => {
148
149
  const { craft } = boot();
149
- assert.ok(NAV_IDS.length >= 25, `read ${NAV_IDS.length} NAV ids from shell.js`);
150
+ // 24 since task 1005051 folded the Roadmap into Goals (a sanity floor, not a count).
151
+ assert.ok(NAV_IDS.length >= 24, `read ${NAV_IDS.length} NAV ids from shell.js`);
150
152
  const named = new Set(CRAFTS.flatMap((m) => craft.tabsFor(m)));
151
153
  for (const id of named) assert.ok(NAV_IDS.includes(id), `${id} is a NAV id`);
152
- for (const id of NAV_IDS) assert.ok(named.has(id), `${id} is reachable from some mode's nav`);
154
+ // Home is the one exception: in a mode the craft plate IS the Home button
155
+ // (task 1005065), so no mode lists the Home tab beside it (task 1005070).
156
+ for (const id of NAV_IDS) {
157
+ if (id === 'home') continue;
158
+ assert.ok(named.has(id), `${id} is reachable from some mode's nav`);
159
+ }
160
+ });
161
+
162
+ test('ONE HOME: no mode shows the Home tab, because the plate is Home in every mode (task 1005070)', () => {
163
+ const { craft } = boot();
164
+ for (const m of CRAFTS) assert.ok(!craft.tabsFor(m).includes('home'), `${m} does not list the Home tab beside the plate`);
165
+ const none = boot();
166
+ none.signIn();
167
+ assert.ok(none.showing().includes('home'), 'with no mode there is no plate, so the Home tab shows');
153
168
  });
154
169
 
155
170
  test('Government shows only in Governing, and Governing is the shortest list', () => {
@@ -325,3 +340,37 @@ test('the dropdown opens on its own when the page you are on is inside it', () =
325
340
  assert.equal(bootTree({ mode: 'artistry' }).fold(), null, 'no other mode folds anything');
326
341
  assert.equal(bootTree().fold(), null, 'nor does no mode');
327
342
  });
343
+
344
+ // ── the remembered nav (task 1005070) ──────────────────────────────────────
345
+ // Every hall page is a full load and /me is the slowest read the owner makes, so
346
+ // the rail used to boot empty and pop its tabs in once /me answered. The shell
347
+ // now keeps the last settled access and paints from it; /me (whose client never
348
+ // answers in this harness) still settles the rest.
349
+ const NAV_KEY = 'hall-nav-access';
350
+
351
+ test('THE REMEMBERED NAV: a signed-in settle is kept, and the next page paints it before /me answers', () => {
352
+ const local = {};
353
+ const first = boot({ local });
354
+ first.signIn();
355
+ const settled = first.showing();
356
+ const kept = JSON.parse(local[NAV_KEY]);
357
+ assert.equal(kept.rank, 'archon');
358
+ assert.equal(kept.builder.id, 7);
359
+ assert.ok(!('needs' in kept), 'never the needs roster, so a badge is never stale');
360
+ const next = boot({ local });
361
+ assert.deepEqual(next.showing(), settled, 'the next page paints the same tabs with /me still out');
362
+ const cold = boot();
363
+ assert.deepEqual(cold.showing(), ['home'], 'with nothing kept the rail waits for /me, as before');
364
+ });
365
+
366
+ test('THE REMEMBERED NAV is forgotten on a signed-out settle, and an old one is not painted', () => {
367
+ const local = {};
368
+ const p = boot({ local });
369
+ p.signIn();
370
+ assert.ok(local[NAV_KEY]);
371
+ p.signOut();
372
+ assert.ok(!(NAV_KEY in local), 'a sign-out or a 401 drops it');
373
+ const stale = boot({ local: { [NAV_KEY]: JSON.stringify({ at: Date.now() - 2 * 24 * 60 * 60 * 1000, rank: 'archon', builder: { id: 7 } }) } });
374
+ assert.deepEqual(stale.showing(), ['home'], 'a day-old memory is not painted');
375
+ assert.match(SHELL, /function doLogout\(\) \{\n\s+forgetAccess\(\);/, 'the sign-out link forgets it before it leaves');
376
+ });
@@ -43,14 +43,16 @@ test('active with no stage yet is the decide pre-stage, and every room sleeps',
43
43
  assert.equal(f.stage, 'decide');
44
44
  assert.equal(f.stage_number, 0);
45
45
  assert.equal(f.stage_count, 5);
46
- for (const id of ['roadmap', 'goals', 'goal-map', 'work', 'board-room', 'collab', 'roster']) {
46
+ // task 1005051: Roadmap folded into Goals; scope wakes Goals and its map.
47
+ for (const id of ['goals', 'goal-map', 'work', 'board-room', 'collab', 'roster']) {
47
48
  assert.ok(id in f.asleep, `${id} sleeps while deciding`);
48
49
  }
49
50
  });
50
51
 
51
52
  test('a room wakes once the stage that wakes it is behind', () => {
52
53
  const f = fm.shapeFoundingMode({ active: 'true', stage: 'charter' });
53
- for (const id of ['roadmap', 'goals', 'goal-map', 'work']) assert.ok(!(id in f.asleep), `${id} woke`);
54
+ for (const id of ['goals', 'goal-map', 'work']) assert.ok(!(id in f.asleep), `${id} woke`);
55
+ assert.ok(!('roadmap' in f.asleep), 'the folded Roadmap is no room to wake');
54
56
  assert.equal(f.asleep['board-room'], 'charter');
55
57
  // task 1005046: Economy and Ranks were the rooms legalize and legislation woke;
56
58
  // they are Primer chapters now, and the Primer never sleeps.
@@ -121,7 +123,7 @@ class El {
121
123
  }
122
124
 
123
125
  function makeHall() {
124
- const navIds = ['home', 'roadmap', 'goals', 'goal-map', 'work', 'board-room', 'collab', 'roster', 'profile'];
126
+ const navIds = ['home', 'goals', 'goal-map', 'work', 'board-room', 'collab', 'roster', 'profile'];
125
127
  const nav = navIds.map((id) => { const a = new El('a'); a.id = 'nav-' + id; a.className = 'nav-item'; return a; });
126
128
  const appBody = new El('div');
127
129
  const topbar = new El('header'); topbar.id = 'app-topbar';
@@ -0,0 +1,88 @@
1
+ // tests/hall_goals_band.mjs — Roadmap folds into Goals (task 1005051, owner
2
+ // decision 2026-11-03, idea 1001315 item 4).
3
+ //
4
+ // Since one version is built at a time (task 1004821) the Roadmap page mostly
5
+ // repeated Goals. Goals now opens with the version's progress and the timeline:
6
+ //
7
+ // * THE BAND. goals.html carries #rm-root at the top of its list view, with
8
+ // roadmap.css, roadmap-timeline.js (still the drawing) and roadmap.js.
9
+ // * OLD LINKS LAND ON GOALS. /roadmap 302s to /goals (serve-internal.js
10
+ // RETIRED_TO_SECTION), query kept; the real middleware runs here.
11
+ // * NOTHING POINTS AT IT. No nav item, no Governing/Building tab, no founding
12
+ // room, and the Tasks board's "see the version" line names Goals.
13
+ //
14
+ // The band's own rendering (the timeline glue, a missing module, an undated
15
+ // version) is run in tests/hall_reading_world.mjs bootRoadmap.
16
+ //
17
+ // Run: node tests/hall_goals_band.mjs
18
+
19
+ import { test } from 'node:test';
20
+ import assert from 'node:assert/strict';
21
+ import fs from 'node:fs';
22
+ import http from 'node:http';
23
+ import path from 'node:path';
24
+ import { createRequire } from 'node:module';
25
+ import { fileURLToPath } from 'node:url';
26
+
27
+ const require = createRequire(import.meta.url);
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
+
32
+ async function withHall(fn) {
33
+ const express = require('express');
34
+ const { mountInternalSurfaces } = require('../src/bongos/serve-internal.js');
35
+ const app = express();
36
+ mountInternalSurfaces(app);
37
+ const server = app.listen(0, '127.0.0.1');
38
+ await new Promise((r) => server.once('listening', r));
39
+ const { port } = server.address();
40
+ const get = (p, host) => new Promise((resolve, reject) => {
41
+ http.get({ host: '127.0.0.1', port, path: p, headers: { host } }, (res) => {
42
+ res.resume();
43
+ res.on('end', () => resolve({ status: res.statusCode, location: res.headers.location || '' }));
44
+ }).on('error', reject);
45
+ });
46
+ try { await fn(get); } finally { server.close(); }
47
+ }
48
+
49
+ test('RUN: /roadmap 302s to /goals, in every spelling, with its query', async () => {
50
+ await withHall(async (get) => {
51
+ for (const form of ['/roadmap', '/roadmap/', '/roadmap.html']) {
52
+ const r = await get(form, 'builders.127.0.0.1');
53
+ assert.equal(r.status, 302, `${form} is a temporary redirect`);
54
+ assert.equal(r.location, '/goals', `${form} lands on Goals, with no stray fragment`);
55
+ }
56
+ assert.equal((await get('/roadmap?from=mail', 'builders.127.0.0.1')).location, '/goals?from=mail');
57
+ // the map it joined still carries its sections
58
+ assert.equal((await get('/sessions', 'builders.127.0.0.1')).location, '/watch#sessions');
59
+ });
60
+ });
61
+
62
+ test('Goals opens with the band, and loads what draws it', () => {
63
+ const html = read('goals.html');
64
+ const list = html.slice(html.indexOf('<section id="goals-list-view"'));
65
+ assert.ok(list.indexOf('id="rm-root"') > 0 && list.indexOf('id="rm-root"') < list.indexOf('class="scroll goals-panel"'),
66
+ 'the band sits above the goals list');
67
+ assert.match(list, /<div id="rm-root"><div class="hall-loading" role="status">Loading the version…<\/div><\/div>/, 'with a Loading… line');
68
+ assert.match(html, /href="\/builders\/roadmap\.css"/);
69
+ const i = (s) => html.indexOf(s);
70
+ assert.ok(i('/builders/hall-kit.js') < i('/builders/roadmap-timeline.js'), 'the kit before the timeline');
71
+ assert.ok(i('/builders/roadmap-timeline.js') < i('/builders/roadmap.js'), 'the timeline before the band');
72
+ assert.ok(!fs.existsSync(path.join(HALL, 'roadmap.html')), 'the page itself is gone');
73
+ });
74
+
75
+ test('the band draws the version and the timeline, and no goal summary of its own', () => {
76
+ const js = read('roadmap.js');
77
+ assert.match(js, /function versionCard\(v\)/);
78
+ assert.match(js, /html \+= timelineHtml\(all, Date\.now\(\)\)/);
79
+ assert.ok(!/\/goals\?version=|goalSummary|goalsBlock/.test(js), 'Goals already reads the goals; the band does not read them again');
80
+ });
81
+
82
+ test('nothing points at the Roadmap any more', () => {
83
+ assert.ok(!/href: '\/roadmap'/.test(read('shell.js')), 'no nav item');
84
+ assert.ok(!/'roadmap'/.test(read('craft-mode.js')), 'no craft tab');
85
+ assert.ok(!/'roadmap'/.test(fs.readFileSync(path.join(ROOT, 'modules/onboarding/founding-mode.js'), 'utf8')), 'no founding room');
86
+ assert.ok(!/href="\/roadmap"/.test(read('work.html')), 'the Tasks board names Goals');
87
+ assert.match(read('work.html'), /See <a href="\/goals">Goals<\/a> for the version's progress/);
88
+ });
@@ -353,9 +353,10 @@ test('the kit is loaded before every page that draws through it', () => {
353
353
  ['watch.html', 'watch.js'],
354
354
  ['profile.html', 'profile-charts.js'],
355
355
  // task 1003447: the roadmap's own series module reads the kit, and the page
356
- // reads that. Both edges of the chain are ordered here.
357
- ['roadmap.html', 'roadmap-timeline.js'],
358
- ['roadmap.html', 'roadmap.js'],
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'],
359
360
  ]) {
360
361
  // The loaded ORDER, read off the script tags — index.html's prose mentions
361
362
  // hall-render.js ~90 lines above where it loads it, so a substring search
@@ -291,8 +291,11 @@ test('sidebar groups read by audience, in order', () => {
291
291
  // default-off market-watch module is not enabled (this boot reports no module state).
292
292
  // task 1005071: the project group split three ways. Work is the project's state;
293
293
  // Make is where things get made; Govern is the board's rooms.
294
- assert.deepEqual(nav[0].items.map((i) => i.id), ['home', 'work', 'goals', 'goal-map', 'roadmap', 'blockers', 'thinking'],
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'],
295
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');
296
299
  assert.deepEqual(nav[1].items.map((i) => i.id), ['studio', 'market-watch'], 'Make = where things get made');
297
300
  // task 1004685 (the living-city prototype, round-3 board 6): 'my-agents' joins You —
298
301
  // how your own Claude works for you, the personal half of "the city steers
@@ -859,7 +862,7 @@ test('POSITION: ?as=nobody views the hall with no seat, and can only hide', asyn
859
862
  assert.equal(b.navHidden('my-agents'), false);
860
863
  // a xenos stand-in stays a xenos: the pretend rank never rises above the real one
861
864
  const x = await seated({ builder: { id: '9', rank: 'xenos' }, positions: block([lead('planning', 'Planning Lead')]), search: '?as=nobody' });
862
- assert.equal(x.navHidden('roadmap'), true, 'a member page stays hidden from a real xenos');
865
+ assert.equal(x.navHidden('goal-map'), true, 'a member page stays hidden from a real xenos');
863
866
  });
864
867
 
865
868
  test('My agents draws its own glyph and lands on a real page', () => {
@@ -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
- '/builders/roadmap': 'requireBuilderPage',
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', () => {
@@ -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
- const roadmapHtml = read('roadmap.html');
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 puts its three helpers on the body, scoped to the page', () => {
110
- const body = rule(roadmapCss, 'body[data-page="roadmap"]');
111
- assert.ok(body, 'roadmap.css declares its tokens on body[data-page="roadmap"]');
112
- for (const t of ['--rm-plan', '--rm-terra-weak']) {
113
- assert.match(body, new RegExp(`${t}\\s*:`), `${t} survived the move to body`);
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], ['roadmap.html', roadmapHtml]];
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('roadmap\'s two sub-24px controls clear the floor, on both axes', () => {
422
- const t = rule(roadmapCss, '.rm-goal__t');
423
- assert.ok(t, '.rm-goal__t has a rule');
424
- assert.match(t, /min-height:\s*24px/, 'measured 20px: a 13px line box in a 7px row');
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-hero/, 'but the hero still renders');
711
+ assert.match(html, /rm-card--feat/, 'but the version still renders');
713
712
  assert.doesNotMatch(html, /NaN|undefined/);
714
713
  });
715
714
 
@@ -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
- assert.ok(model.nav.length >= 25, `expected the shell's nav, found ${model.nav.length} items`);
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
  }
@@ -98,8 +98,10 @@ test('the hall-preview fixture carries every atom /me resolves', () => {
98
98
  // task 1005046: Ranks, Economy and Diagrams left Docs (one reading room, the Primer);
99
99
  // task 1005049: Sessions and Autobongos left the sidebar (tabs on Watch);
100
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
+ // task 1005050: Accomplishments left it (folded into Profile; its hashes redirect);
102
+ // task 1005051: Roadmap left it (folded into Goals; /roadmap redirects).
103
+ // task 1005070: the remembered nav (the rail paints before /me).
104
+ const SHELL_SHA = 'dbbe6f2eb49a65c3';
103
105
 
104
106
  test('shell.js has not changed without its cache-bust being bumped', () => {
105
107
  const actual = crypto.createHash('sha256').update(SHELL).digest('hex').slice(0, 16);
@@ -1,58 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>Roadmap — {{worldName}}</title>
7
- <meta name="description" content="How the whole build is doing — the program at a glance, then each version — {{worldName}}.">
8
- <link rel="icon" href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text x='16' y='24' font-size='24' text-anchor='middle' font-family='sans-serif'>⌂</text></svg>">
9
- <link rel="preconnect" href="https://fonts.googleapis.com">
10
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
- <link rel="stylesheet" href="/builders/style.css?v=2026-07-24-deck1">
12
- <link rel="stylesheet" href="/builders/roadmap.css">
13
- </head>
14
- <body data-page="roadmap" data-page-title="Roadmap">
15
-
16
- <div class="app">
17
- <aside class="app-sidebar" id="app-sidebar" aria-label="Primary"></aside>
18
- <div class="app-scrim" id="app-scrim" hidden></div>
19
- <div class="app-body">
20
- <header class="app-topbar" id="app-topbar"></header>
21
- <div class="app-content">
22
- <!-- The page had no <h1> at all before task 1003306, so it failed
23
- one-header-main-h1 on every render. The deck takes the BOARD voice,
24
- not the reading room's: it is a working surface whose figures are the
25
- thing to look at, and its own name is the quietest thing on it. -->
26
- <div class="page-head page-head--board">
27
- <h1 class="page-head__h">Roadmap</h1>
28
- <p class="page-head__sub" id="roadmap-sub">Loading the roadmap…</p>
29
- </div>
30
-
31
- <main id="hall-main">
32
- <div id="rm-root">
33
- <div class="hall-loading hall-loading--page" role="status">Loading the roadmap…</div>
34
- </div>
35
- </main>
36
-
37
- <footer class="app-foot" id="app-foot"></footer>
38
- </div>
39
- </div>
40
- </div>
41
-
42
- <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
43
-
44
- <script src="/builders/dom-utils.js?v=2026-11-05-numbers"></script>
45
- <script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
46
- <script src="/builders/craft-mode.js?v=2026-11-05-numbers"></script>
47
- <script src="/builders/shell.js?v=2026-11-05-numbers"></script>
48
- <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
49
- <script src="/builders/palette.js" defer></script>
50
- <!-- The chart kit and the timeline's series builders (task 1003447). Both MUST
51
- precede roadmap.js: the page reads window.OTBKit / window.OTBRoadmapTimeline
52
- at render time, so loading either after would be a blank section in
53
- production and a green unit suite. tests/hall_kit_charts.mjs pins the order. -->
54
- <script src="/builders/hall-kit.js"></script>
55
- <script src="/builders/roadmap-timeline.js"></script>
56
- <script src="/builders/roadmap.js?v=2026-09-03-timeline"></script>
57
- </body>
58
- </html>
@@ -1,31 +0,0 @@
1
- {
2
- "_": "The hall's roadmap deck through modules/ui-design/kit/render.js (task 1003306). The deck is the hall's densest figure surface — a hero band, four status groups and a summarised goal layer per version — so its floors are the 24px one (the goal rows are links in a tight list) and horizontal overflow at 320.",
3
- "page": "/builders/roadmap",
4
- "surface": "hall-ui",
5
- "stub": { "prefix": "/builders/" },
6
- "modeQuery": false,
7
- "_feeds": "The kit's stub carries the public-landing fixtures, not the hall's versions/goals feeds, so the deck renders its EMPTY state here and the feed 404s are declared, never hidden. The populated deck is the hall-preview harness's job: node scripts/hall-preview/server.js --port 4612 --fixture-me, then this same file with --base-in/--base-out pointed at it.",
8
- "ignoreRequests": [
9
- "/api/(gds|bongos)/me$",
10
- "/api/(gds|bongos)/me/",
11
- "/api/(gds|bongos)/versions",
12
- "/api/(gds|bongos)/goals",
13
- "/api/(gds|bongos)/tasks",
14
- "/api/(gds|bongos)/inbox/",
15
- "/api/(gds|bongos)/public/"
16
- ],
17
- "ignoreConsole": [
18
- "/api/(gds|bongos)/",
19
- "\\b401\\b",
20
- "\\b404\\b",
21
- "Unauthorized",
22
- "Not Found",
23
- "load failed",
24
- "roadmap"
25
- ],
26
- "states": {
27
- "in": { "auth": true, "expect": { "visible": ["#rm-root"] } },
28
- "out": { "auth": false }
29
- },
30
- "reducedMotion": { "auth": true }
31
- }