@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.
Files changed (92) hide show
  1. package/.bongos-core.json +108 -103
  2. package/docs/copy-inventory.md +115 -165
  3. package/docs/copy-registry.json +230 -781
  4. package/docs/file-map.md +1 -1
  5. package/docs/module-api-changelog.md +4 -0
  6. package/docs/onboarding/primer.md +1 -1
  7. package/docs/page-inventory.json +10 -38
  8. package/docs/page-readings.json +528 -627
  9. package/modules/hall-ui/public/ai-studio.html +4 -4
  10. package/modules/hall-ui/public/atlas.html +4 -4
  11. package/modules/hall-ui/public/blockers.html +4 -4
  12. package/modules/hall-ui/public/board-room.html +4 -4
  13. package/modules/hall-ui/public/builders.js +22 -505
  14. package/modules/hall-ui/public/city.html +4 -4
  15. package/modules/hall-ui/public/collab.html +4 -4
  16. package/modules/hall-ui/public/commands.html +4 -4
  17. package/modules/hall-ui/public/copy-desk.html +4 -4
  18. package/modules/hall-ui/public/craft-mode.js +2 -2
  19. package/modules/hall-ui/public/deploy.html +4 -4
  20. package/modules/hall-ui/public/dom-utils.js +2 -1
  21. package/modules/hall-ui/public/goal-map.html +4 -4
  22. package/modules/hall-ui/public/goals.html +16 -4
  23. package/modules/hall-ui/public/government.html +4 -4
  24. package/modules/hall-ui/public/hall-render.js +23 -524
  25. package/modules/hall-ui/public/hall-widgets.js +11 -109
  26. package/modules/hall-ui/public/idea.html +4 -4
  27. package/modules/hall-ui/public/ideas.html +4 -4
  28. package/modules/hall-ui/public/index.html +6 -19
  29. package/modules/hall-ui/public/index.states.json +0 -33
  30. package/modules/hall-ui/public/market-watch-item.html +4 -4
  31. package/modules/hall-ui/public/market-watch-rules.html +4 -4
  32. package/modules/hall-ui/public/market-watch.html +4 -4
  33. package/modules/hall-ui/public/modules.html +4 -4
  34. package/modules/hall-ui/public/money.html +4 -4
  35. package/modules/hall-ui/public/my-agents.html +4 -4
  36. package/modules/hall-ui/public/palette.js +7 -6
  37. package/modules/hall-ui/public/primer.html +4 -4
  38. package/modules/hall-ui/public/profile-own.js +639 -0
  39. package/modules/hall-ui/public/profile.css +2 -1
  40. package/modules/hall-ui/public/profile.html +48 -5
  41. package/modules/hall-ui/public/profile.js +27 -2
  42. package/modules/hall-ui/public/profile.states.json +18 -0
  43. package/modules/hall-ui/public/project-settings.html +4 -4
  44. package/modules/hall-ui/public/roadmap.css +7 -138
  45. package/modules/hall-ui/public/roadmap.js +35 -293
  46. package/modules/hall-ui/public/roster.html +4 -4
  47. package/modules/hall-ui/public/settings.html +4 -4
  48. package/modules/hall-ui/public/shell-trail.js +2 -1
  49. package/modules/hall-ui/public/shell.js +31 -28
  50. package/modules/hall-ui/public/street.html +4 -4
  51. package/modules/hall-ui/public/studio.html +4 -4
  52. package/modules/hall-ui/public/task.html +4 -4
  53. package/modules/hall-ui/public/thinking.html +4 -4
  54. package/modules/hall-ui/public/tweak-editor.html +4 -4
  55. package/modules/hall-ui/public/watch.html +4 -4
  56. package/modules/hall-ui/public/work.html +5 -5
  57. package/modules/onboarding/founding-mode.js +2 -1
  58. package/package-lock.json +2 -2
  59. package/package.json +1 -1
  60. package/release-notes.json +12 -0
  61. package/scripts/bongos/gen-hall-sitemap.js +1 -0
  62. package/src/bongos/serve-internal.js +6 -6
  63. package/src/module-api.js +1 -1
  64. package/tests/craft_mode_nav.mjs +2 -1
  65. package/tests/curve_controls_delegation.mjs +9 -8
  66. package/tests/curve_kind_overlay.mjs +5 -5
  67. package/tests/founding_mode.mjs +5 -3
  68. package/tests/hall_acclaim_vote_control.mjs +5 -3
  69. package/tests/hall_audit.mjs +10 -6
  70. package/tests/hall_board_world.mjs +40 -26
  71. package/tests/hall_builders_page_pass.mjs +14 -5
  72. package/tests/hall_goals_band.mjs +88 -0
  73. package/tests/hall_home_ships_bounded.mjs +10 -21
  74. package/tests/hall_kit_charts.mjs +7 -5
  75. package/tests/hall_loading.mjs +2 -1
  76. package/tests/hall_nav.mjs +41 -39
  77. package/tests/hall_numbers_page.mjs +100 -0
  78. package/tests/hall_page_gate_map.mjs +5 -1
  79. package/tests/hall_palette.mjs +19 -24
  80. package/tests/hall_profile_world.mjs +7 -2
  81. package/tests/hall_reading_room.mjs +3 -2
  82. package/tests/hall_reading_world.mjs +17 -18
  83. package/tests/hall_sitemap.mjs +2 -1
  84. package/tests/helpers.mjs +8 -2
  85. package/tests/leaderboard_roles.mjs +1 -1
  86. package/tests/module_contributions.mjs +6 -4
  87. package/tests/nav_permission_atoms.mjs +4 -2
  88. package/tests/page_tweak_artist.mjs +2 -1
  89. package/tests/rank_tier_single_source.mjs +3 -0
  90. package/tests/task_visuals.mjs +3 -1
  91. package/modules/hall-ui/public/roadmap.html +0 -58
  92. package/modules/hall-ui/public/roadmap.states.json +0 -31
@@ -1,29 +1,23 @@
1
- // roadmap.js — the Roadmap page, "Command deck" layout (task 1002426, owner-
2
- // approved redesign from the mockup review; supersedes the flat progress-bar
3
- // stack introduced in task 1757).
1
+ // roadmap.js — the version band at the top of the Goals page (task 1005051).
4
2
  //
5
- // Shape: a program-level hero (whole-build rollup) → versions GROUPED BY their
6
- // real status (planning=Staged · building=In flight · frozen=Parked ·
7
- // shipped=Shipped), with the dominant in-flight version featured full-width. The
8
- // goal layer is SUMMARIZED per version — a status bar (achieved / in progress /
9
- // not started) plus only the goals active right now — instead of nesting every
10
- // goal + criterion inline (BONGOS-V1 alone carries ~30 goals; the old flat list
11
- // was the big-picture killer). The full per-version goal list, inline join/leave,
12
- // and Archon drag-to-reorder now live on the Goals page, which "See all N goals"
13
- // links to.
3
+ // It was the Roadmap page, the "command deck" (task 1002426): a program hero,
4
+ // every version grouped by status, and a summarized goal layer per version. Since
5
+ // the owner's call of one version at a time (task 1004821) that page mostly
6
+ // repeated Goals, so on 2026-11-03 (idea 1001315, item 4) the owner folded it in:
7
+ // Goals now OPENS with the version's progress and the timeline, and /roadmap
8
+ // redirects to /goals. The goal summaries did not come along — the goals are the
9
+ // page right below — and neither did the staged / parked / shipped cards; the
10
+ // timeline still draws every version that ran.
14
11
  //
15
- // Reads (all GET): /public/progress (lifecycle rollup + counts + %, public),
16
- // /public/versions (started/shipped dates, public), and — for every
17
- // non-shipped version only — /goals?version=X + /goals/:id (per-goal criteria to
18
- // split in-progress from not-started). Shipped versions render as a pill strip
19
- // with no goal fetch. No writes. Server page gate: SIGNEDIN_BUILDERS_PAGE_RE.
12
+ // Reads (all GET): /public/progress (lifecycle rollup + counts + %),
13
+ // /public/versions (started/shipped dates) and /versions/focus. No goal reads —
14
+ // Goals already makes them. No writes. The page gate is Goals' own.
20
15
  (() => {
21
16
  'use strict';
22
17
 
23
18
  const API = '/api/bongos';
24
19
  const api = window.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false });
25
20
  const { escapeHtml, $, fmtNum } = window.OTB;
26
- const toast = (msg) => window.OTB.toast(msg, { duration: 2200, clear: true });
27
21
 
28
22
  async function getJSON(path) {
29
23
  const res = await api.request('GET', path);
@@ -42,12 +36,8 @@
42
36
  planning: { label: 'Staged', cls: 'planning' },
43
37
  frozen: { label: 'Parked', cls: 'frozen' },
44
38
  };
45
- // The instance's Goals page (root-relative — resolves on the builders host).
46
- const GOALS_URL = '/goals';
47
- const goalDeepLink = (id) => `/goals#/goal/${encodeURIComponent(String(id))}`;
48
- // The TASK half of the roadmap's click-through (task 1002440). A goal title has
49
- // always deep-linked (goalDeepLink above), but the progress block — "X of Y
50
- // tasks" + the lifecycle bar — was inert, so the roadmap could show you how much
39
+ // The version's click-through to its tasks (task 1002440): the progress block —
40
+ // "X of Y tasks" + the lifecycle bar — was inert, so it could show you how much
51
41
  // work a version had and give you no way to go look at it. The Tasks board already
52
42
  // restores this filter from the query string (restoreUrlFilter('f-version') in
53
43
  // work.js), so the link lands pre-filtered with NO server or work.js change.
@@ -69,26 +59,8 @@
69
59
  <span class="rm-lbar__s rm-lbar__s--co" style="width:${pctOf(l.co, t)}%"></span>
70
60
  <span class="rm-lbar__s rm-lbar__s--cm" style="width:${pctOf(l.cm, t)}%"></span></div>`;
71
61
  }
72
- function gbar(g) {
73
- return `<div class="rm-gbar" role="img" aria-label="${g.ach} achieved, ${g.prog} in progress, ${g.plan} not started of ${g.total} goals">
74
- <span class="rm-gbar__s rm-gbar__s--ach" style="width:${pctOf(g.ach, g.total)}%"></span>
75
- <span class="rm-gbar__s rm-gbar__s--prog" style="width:${pctOf(g.prog, g.total)}%"></span>
76
- <span class="rm-gbar__s rm-gbar__s--plan" style="width:${pctOf(g.plan, g.total)}%"></span></div>`;
77
- }
78
-
79
62
  const arrowSvg = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M13 6l6 6-6 6"/></svg>';
80
63
 
81
- // BV1.R15 (task 1003602, goal 1000086, ADR 0250 §3): "the overrides are
82
- // counted and shown on the version."
83
- //
84
- // RENDERS NOTHING AT ZERO, and that is the design rather than a shortcut. A
85
- // permanent "0 overrides" line is chrome a reader learns to skip, and the
86
- // moment it becomes "2 overrides" it would go on being skipped. Appearing only
87
- // when the gate was actually overridden makes the line itself the signal.
88
- //
89
- // It names no goals: which ones they are is one click away on the goals board
90
- // (they carry an "admitted" chip), and a roadmap card that grew a second list
91
- // stops being a roadmap.
92
64
  function admissionNote(v) {
93
65
  const n = Number(v && v.admissions) || 0;
94
66
  if (n === 0) return '';
@@ -97,49 +69,8 @@
97
69
  + `</p>`;
98
70
  }
99
71
 
100
- // The goals block that sits inside an in-flight / featured card. `active` are
101
- // the in-progress goals (sat>0), already sorted; we show up to 4 and let
102
- // "See all" carry the rest.
103
- function goalsBlock(v) {
104
- const g = v.goals;
105
- if (!g || g.total === 0) {
106
- return `<div class="rm-goals__frame"><div class="rm-goals__h"><span class="t">Goals</span></div>
107
- <p class="rm-gnone">No goals yet.</p></div>`;
108
- }
109
- const active = g.active.slice(0, 4);
110
- const rows = active.map((x) => {
111
- const ready = x.total > 0 && x.sat >= x.total;
112
- return `<div class="rm-goal">
113
- <span class="rm-goal__dot"></span>
114
- <a class="rm-goal__t" href="${goalDeepLink(x.id)}" title="Open goal #${escapeHtml(String(x.id))}">${escapeHtml(x.title || 'Untitled goal')}</a>
115
- ${ready ? '<span class="rm-goal__ready">criteria met</span>' : ''}
116
- <span class="rm-goal__meter"><i style="width:${pctOf(x.sat, x.total)}%"></i></span>
117
- <span class="rm-goal__n">${x.sat}/${x.total}</span>
118
- </div>`;
119
- }).join('');
120
- return `<div class="rm-goals__frame">
121
- <div class="rm-goals__h"><span class="t">Goals</span><span class="n">${g.total} total</span></div>
122
- ${gbar(g)}
123
- <div class="rm-gdist">
124
- <span><i class="rm-ach"></i><b>${g.ach}</b> achieved</span>
125
- <span><i class="rm-prog"></i><b>${g.prog}</b> in progress</span>
126
- <span><i class="rm-plan"></i><b>${g.plan}</b> not started</span>
127
- </div>
128
- ${active.length ? `<div class="rm-ginp"><div class="rm-ginp__cap">Active right now</div>${rows}</div>`
129
- : '<p class="rm-gnone" style="margin-top:12px">No goal has started yet.</p>'}
130
- ${admissionNote(v)}
131
- <a class="rm-gmore" href="${GOALS_URL}">See all ${g.total} goals ${arrowSvg}</a>
132
- </div>`;
133
- }
134
-
135
- // "View tasks ->" for a version's progress block. One helper, used by all three
136
- // task-bearing cards, so the label + a11y text cannot drift between them.
137
- //
138
- // GUARDED ON total > 0 deliberately: a staged version has no tasks yet, and a
139
- // link that filters the board to nothing is worse than no link — it reads as a
140
- // broken page rather than an empty backlog. stagedCard has no .rm-cardprog at
141
- // all, so it never calls this; the guard is for a building/parked version whose
142
- // task count is still zero.
72
+ // "View tasks ->" for a version's progress block. GUARDED ON total > 0: a link
73
+ // that filters the board to nothing reads as a broken page, not an empty backlog.
143
74
  function tasksMore(v) {
144
75
  const total = Number(v && v.total) || 0;
145
76
  if (total <= 0) return '';
@@ -149,8 +80,8 @@
149
80
 
150
81
  // The focus star (task 1004551): the project's default version, stored server-side.
151
82
  const focus = { id: null };
152
- // The star on each card only SHOWS the focus. Only one version is built at a
153
- // time, so the page asks nobody to pick one (owner, 2026-10-23, task 1004821).
83
+ // The star only SHOWS the focus. Only one version is built at a time, so the
84
+ // page asks nobody to pick one (owner, 2026-10-23, task 1004821).
154
85
  function starHtml(v) {
155
86
  const on = focus.id === v.id;
156
87
  const tip = on ? 'The focus version' : 'Not the focus version';
@@ -172,31 +103,10 @@
172
103
 
173
104
  // ---- card renderers -------------------------------------------------------
174
105
 
175
- function featCard(v) {
106
+ // One in-flight version: its phase, id and name, how far its tasks are, and
107
+ // the link to them on the Tasks board. The focus version leads.
108
+ function versionCard(v) {
176
109
  return `<article class="rm-card rm-card--feat">
177
- <div class="rm-featgrid">
178
- <div class="rm-featgrid__l">
179
- <div class="rm-feat-top">
180
- <div class="rt">
181
- <div class="rm-row">${phase(v)}<span class="rm-id">${escapeHtml(v.id)}</span>${starHtml(v)}</div>
182
- <h3 class="rm-name">${escapeHtml(v.name)}</h3>
183
- <p class="rm-desc rm-desc--clamp">${escapeHtml(v.desc)}</p>
184
- </div>
185
- ${ring(v)}
186
- </div>
187
- <div class="rm-cardprog">
188
- <div class="rm-count"><span><b>${fmtNum(v.shipped)}</b> of <b>${fmtNum(v.total)}</b> tasks</span><span>${fmtNum(v.criteria)} criteria · since ${ym(v.start)}</span></div>
189
- ${lbar(v)}
190
- ${tasksMore(v)}
191
- </div>
192
- </div>
193
- <div class="rm-goals">${goalsBlock(v)}</div>
194
- </div>
195
- </article>`;
196
- }
197
-
198
- function flightCard(v) {
199
- return `<article class="rm-card">
200
110
  <div class="rm-feat-top">
201
111
  <div class="rt">
202
112
  <div class="rm-row">${phase(v)}<span class="rm-id">${escapeHtml(v.id)}</span>${starHtml(v)}</div>
@@ -210,79 +120,10 @@
210
120
  ${lbar(v)}
211
121
  ${tasksMore(v)}
212
122
  </div>
213
- <div style="margin-top:13px">${goalsBlock(v)}</div>
214
- </article>`;
215
- }
216
-
217
- // Staged (planning) card — goals-forward. No task progress yet, so it leads
218
- // with the seeded plan instead of a lifecycle bar.
219
- function stagedCard(v) {
220
- const g = v.goals;
221
- const seeded = (g && g.all ? g.all : []).slice(0, 4);
222
- const rows = seeded.map((x) => `
223
- <div class="rm-goal"><span class="rm-goal__dot" style="background:var(--rm-plan)"></span>
224
- <a class="rm-goal__t" href="${goalDeepLink(x.id)}">${escapeHtml(x.title || 'Untitled goal')}</a>
225
- <span class="rm-goal__seed">seeded</span></div>`).join('');
226
- const totalGoals = g ? g.total : 0;
227
- const goalsFrame = totalGoals === 0
228
- ? `<div class="rm-goals__frame"><div class="rm-goals__h"><span class="t">Goals</span></div>
229
- <p class="rm-gnone">No goals seeded yet.</p></div>`
230
- : `<div class="rm-goals__frame">
231
- <div class="rm-goals__h"><span class="t">Goals</span><span class="n">${totalGoals} seeded</span></div>
232
- ${gbar(g)}
233
- <div class="rm-gdist">
234
- <span><i class="rm-ach"></i><b>${g.ach}</b> achieved</span>
235
- <span><i class="rm-prog"></i><b>${g.prog}</b> in progress</span>
236
- <span><i class="rm-plan"></i><b>${g.plan}</b> seeded</span>
237
- </div>
238
- <div class="rm-ginp"><div class="rm-ginp__cap">The seeded plan</div>${rows}</div>
239
- <a class="rm-gmore" href="${GOALS_URL}">See all ${totalGoals} goals ${arrowSvg}</a>
240
- </div>`;
241
- return `<article class="rm-card rm-card--staged" style="grid-column:1 / -1">
242
- <div class="rm-featgrid">
243
- <div class="rm-featgrid__l">
244
- <div class="rm-row">${phase(v)}<span class="rm-id">${escapeHtml(v.id)}</span>${starHtml(v)}</div>
245
- <h3 class="rm-name">${escapeHtml(v.name)}</h3>
246
- <p class="rm-desc rm-desc--clamp">${escapeHtml(v.desc)}</p>
247
- <div class="rm-staged-state">
248
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v4M9 6l3-3 3 3"/><rect x="4" y="9" width="16" height="12" rx="2"/></svg>
249
- <div><div class="s1">Not started</div>
250
- <div class="s2"><b>${totalGoals}</b> goals · <b>${fmtNum(v.criteria)}</b> criteria · no tasks yet</div></div>
251
- </div>
252
- </div>
253
- <div class="rm-goals">${goalsFrame}</div>
254
- </div>
255
- </article>`;
256
- }
257
-
258
- function parkedCard(v) {
259
- return `<article class="rm-card rm-card--parked" style="grid-column:1 / -1">
260
- <div class="rm-feat-top">
261
- <div class="rt">
262
- <div class="rm-row">${phase(v)}<span class="rm-id">${escapeHtml(v.id)}</span>${starHtml(v)}</div>
263
- <h3 class="rm-name">${escapeHtml(v.name)}</h3>
264
- <p class="rm-desc rm-desc--clamp">${escapeHtml(v.desc)}</p>
265
- </div>
266
- ${ring(v)}
267
- </div>
268
- <div class="rm-cardprog">
269
- <div class="rm-count"><span><b>${fmtNum(v.shipped)}</b> of <b>${fmtNum(v.total)}</b> tasks</span><span>${fmtNum(v.criteria)} criteria · started ${ym(v.start)}</span></div>
270
- ${lbar(v)}
271
- ${tasksMore(v)}
272
- </div>
273
- <div class="rm-parknote">
274
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="4" width="6" height="16" rx="1"/><rect x="14" y="4" width="6" height="16" rx="1"/></svg>
275
- <div>On hold. Nobody is claiming work here.</div>
276
- </div>
123
+ ${admissionNote(v)}
277
124
  </article>`;
278
125
  }
279
126
 
280
- // The program in time (task 1003447). The SERIES is roadmap-timeline.js's —
281
- // pure builders in their own file so a test runs the real arithmetic over the
282
- // real fixture — and the SECTION chrome is this page's, which is the same seam
283
- // the chart kit keeps everywhere: the module draws, the page frames. Missing
284
- // module or fewer than two started versions renders nothing at all, so the
285
- // deck reads exactly as it did before rather than growing an empty panel.
286
127
  function timelineHtml(all, now) {
287
128
  const mod = window.OTBRoadmapTimeline;
288
129
  const card = mod ? mod.timelineCardHtml(all, now) : null;
@@ -297,78 +138,6 @@
297
138
  </section>`;
298
139
  }
299
140
 
300
- function heroHtml(flight) {
301
- const A = flight.reduce((a, v) => ({
302
- total: a.total + v.total, shipped: a.shipped + v.shipped,
303
- sh: a.sh + v.life.sh, co: a.co + v.life.co, cm: a.cm + v.life.cm,
304
- }), { total: 0, shipped: 0, sh: 0, co: 0, cm: 0 });
305
- const G = flight.reduce((a, v) => ({
306
- total: a.total + (v.goals ? v.goals.total : 0), ach: a.ach + (v.goals ? v.goals.ach : 0),
307
- prog: a.prog + (v.goals ? v.goals.prog : 0), plan: a.plan + (v.goals ? v.goals.plan : 0),
308
- }), { total: 0, ach: 0, prog: 0, plan: 0 });
309
- const heroPct = Math.round(pctOf(A.shipped, A.total));
310
- const heroRem = A.total - A.shipped;
311
- const goalsRow = G.total > 0 ? `
312
- <div class="rm-hgoals">
313
- <span class="glab">Goals</span>
314
- <div class="rm-gbar">
315
- <span class="rm-gbar__s rm-gbar__s--ach" style="width:${pctOf(G.ach, G.total)}%"></span>
316
- <span class="rm-gbar__s rm-gbar__s--prog" style="width:${pctOf(G.prog, G.total)}%"></span>
317
- <span class="rm-gbar__s rm-gbar__s--plan" style="width:${pctOf(G.plan, G.total)}%"></span>
318
- </div>
319
- <span class="gleg"><b>${G.ach}</b> achieved · <b>${G.prog}</b> in progress · <b>${G.plan}</b> not started <span style="color:var(--ink-faint)">of ${G.total}</span></span>
320
- </div>` : '';
321
- return `<div class="rm-hero">
322
- <div class="rm-hero__top">
323
- <div>
324
- <div class="rm-big"><span class="n">${heroPct}</span><span class="u">%</span></div>
325
- <div class="rm-lab">of tasks shipped</div>
326
- </div>
327
- <div>
328
- <div class="rm-lbar rm-hbar" role="img" aria-label="${A.sh} shipped, ${A.co} confirmed, ${A.cm} completed, ${heroRem} remaining of ${A.total}">
329
- <span class="rm-lbar__s rm-lbar__s--sh" style="width:${pctOf(A.sh, A.total)}%"></span>
330
- <span class="rm-lbar__s rm-lbar__s--co" style="width:${pctOf(A.co, A.total)}%"></span>
331
- <span class="rm-lbar__s rm-lbar__s--cm" style="width:${pctOf(A.cm, A.total)}%"></span>
332
- </div>
333
- <div class="rm-stats">
334
- <div class="rm-stat"><span class="v">${flight.length}</span><span class="k">versions in flight</span></div>
335
- <div class="rm-stat"><span class="v sh">${fmtNum(A.sh)}</span><span class="k">shipped</span></div>
336
- <div class="rm-stat"><span class="v co">${fmtNum(A.co)}</span><span class="k">confirmed</span></div>
337
- <div class="rm-stat"><span class="v cm">${fmtNum(A.cm)}</span><span class="k">completed</span></div>
338
- <div class="rm-stat"><span class="v">${fmtNum(heroRem)}</span><span class="k">tasks remaining</span></div>
339
- </div>
340
- </div>
341
- </div>
342
- ${goalsRow}
343
- </div>`;
344
- }
345
-
346
- // ---- data ------------------------------------------------------------------
347
-
348
- // Enrich a version with its goal summary. `list` gives titles + status +
349
- // sort_order (1 request); `/goals/:id` gives per-goal criteria (needed to split
350
- // in-progress from not-started + to fill the "active now" list).
351
- async function goalSummary(versionId) {
352
- try {
353
- const list = await getJSON(`${API}/goals?version=${encodeURIComponent(versionId)}`);
354
- const goals = (list.goals || []).filter((g) => g.status !== 'archived');
355
- if (!goals.length) return { total: 0, ach: 0, prog: 0, plan: 0, active: [], all: [] };
356
- const details = await Promise.all(goals.map((g) => getJSON(`${API}/goals/${encodeURIComponent(g.id)}`).catch(() => null)));
357
- const enriched = goals.map((g, i) => {
358
- const d = details[i];
359
- const crit = d && Array.isArray(d.criteria) ? d.criteria : [];
360
- const sat = crit.filter((c) => c.satisfied).length;
361
- return { id: g.id, title: g.title, status: g.status, sort: Number(g.sort_order) || 0, sat, total: crit.length };
362
- }).sort((a, b) => a.sort - b.sort);
363
- const ach = enriched.filter((g) => g.status === 'achieved').length;
364
- const active = enriched.filter((g) => g.status !== 'achieved' && g.sat > 0);
365
- const plan = enriched.filter((g) => g.status !== 'achieved' && g.sat === 0).length;
366
- return { total: enriched.length, ach, prog: active.length, plan, active, all: enriched };
367
- } catch (_) {
368
- return { total: 0, ach: 0, prog: 0, plan: 0, active: [], all: [] };
369
- }
370
- }
371
-
372
141
  // Merge /public/progress (counts, %, lifecycle) with /public/versions (dates)
373
142
  // into one version model the renderers consume.
374
143
  function mergeVersions(progress, versions) {
@@ -403,6 +172,7 @@
403
172
 
404
173
  async function render() {
405
174
  const root = $('#rm-root');
175
+ if (!root) return;
406
176
  const [progress, versions] = await Promise.all([
407
177
  loadFocus(),
408
178
  getJSON(`${API}/public/progress`).then((d) => d.progress || []),
@@ -410,59 +180,31 @@
410
180
  ]).then(([, p, v]) => [p, v]);
411
181
  const all = mergeVersions(progress, versions);
412
182
 
413
- // Goal summaries only for versions we actually show goals on (everything but
414
- // shipped) — a real perf win over fetching goals for the whole history.
415
- const needGoals = all.filter((v) => v.status !== 'shipped');
416
- await Promise.all(needGoals.map(async (v) => { v.goals = await goalSummary(v.id); }));
417
-
418
- const flight = all.filter((v) => v.status === 'building').sort((a, b) => b.total - a.total);
419
- const staged = all.filter((v) => v.status === 'planning').sort((a, b) => b.total - a.total);
420
- const parked = all.filter((v) => v.status === 'frozen').sort((a, b) => b.pct - a.pct);
421
- const shipped = all.filter((v) => v.status === 'shipped')
422
- .sort((a, b) => (new Date(b.ship || 0)) - (new Date(a.ship || 0)));
183
+ // The focus version leads, then any other version in flight (most tasks first).
184
+ const flight = all.filter((v) => v.status === 'building')
185
+ .sort((a, b) => ((b.id === focus.id) - (a.id === focus.id)) || (b.total - a.total));
423
186
 
424
- let html = '';
187
+ let html = flight.length
188
+ ? sectionHtml('flight', 'In flight', flight.length, '', flight.length === 1
189
+ ? versionCard(flight[0])
190
+ : `<div class="rm-grid">${flight.map(versionCard).join('')}</div>`)
191
+ : '<p class="rm-gnone">No version is in flight right now.</p>';
425
192
 
426
- if (flight.length) {
427
- html += heroHtml(flight);
428
- const feat = flight[0]; // dominant in-flight version (most tasks)
429
- const rest = flight.slice(1);
430
- const inner = featCard(feat) + (rest.length ? `<div class="rm-grid" style="margin-top:16px">${rest.map(flightCard).join('')}</div>` : '');
431
- html += sectionHtml('flight', 'In flight', flight.length, '', inner);
432
- } else {
433
- html += `<div class="rm-hero"><div class="rm-lab" style="max-width:none">No version is in flight right now.</div></div>`;
434
- }
435
-
436
- // Directly under the hero: both are program-level readings, and this is the
437
- // one that answers "how fast" rather than "how much".
193
+ // Under the version: how fast, beside how much.
438
194
  html += timelineHtml(all, Date.now());
439
195
 
440
- if (staged.length) {
441
- html += sectionHtml('staged', 'Staged', staged.length, '', `<div class="rm-grid">${staged.map(stagedCard).join('')}</div>`);
442
- }
443
- if (parked.length) {
444
- html += sectionHtml('parked', 'Parked', parked.length, '', `<div class="rm-grid">${parked.map(parkedCard).join('')}</div>`);
445
- }
446
- if (shipped.length) {
447
- const pills = shipped.map((v) => `<a class="rm-shippill" href="${GOALS_URL}" title="${escapeHtml(v.name)}"><span class="ok">✓</span><span class="nm">${escapeHtml(v.id)}</span><span class="mt">${fmtNum(v.shipped)} tasks · ${ym(v.ship)}</span></a>`).join('');
448
- html += sectionHtml('shipped', 'Shipped', shipped.length, '', `<div class="rm-shipstrip">${pills}</div>`);
449
- }
450
-
451
- if (!all.length) html = `<div class="profile__placeholder">No versions declared yet.</div>`;
196
+ if (!all.length) html = '<p class="rm-gnone">No versions declared yet.</p>';
452
197
  root.innerHTML = html;
453
198
  }
454
199
 
455
200
  async function boot() {
456
- const sub = $('#roadmap-sub');
457
- if (sub) sub.textContent = 'Every version, and how far along it is.';
458
201
  try {
459
202
  await render();
460
203
  } catch (err) {
461
204
  if (err && err.message === 'redirecting to auth') return;
462
- console.error('[roadmap] load failed:', err);
205
+ console.error('[goals] version band failed:', err);
463
206
  const root = $('#rm-root');
464
- if (root) root.innerHTML = `<div class="profile__placeholder">The roadmap could not be loaded right now. Reload to try again.</div>`;
465
- toast('The roadmap failed to load. Reload to try again.');
207
+ if (root) root.innerHTML = '<p class="rm-gnone">The version\'s progress could not be loaded right now. Reload to try again.</p>';
466
208
  }
467
209
  }
468
210
 
@@ -56,10 +56,10 @@
56
56
 
57
57
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
58
58
  chrome exists before the page script runs. -->
59
- <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
60
- <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
61
- <script src="/builders/craft-mode.js?v=2026-11-05-oversight"></script>
62
- <script src="/builders/shell.js?v=2026-11-05-setup-gate"></script>
59
+ <script src="/builders/dom-utils.js?v=2026-11-05-numbers"></script>
60
+ <script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
61
+ <script src="/builders/craft-mode.js?v=2026-11-05-goals"></script>
62
+ <script src="/builders/shell.js?v=2026-11-05-goals"></script>
63
63
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
64
64
  <script src="/builders/palette.js" defer></script>
65
65
  <!-- the branding pack's currency label, loaded BEFORE roster.js. -->
@@ -391,10 +391,10 @@
391
391
 
392
392
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
393
393
  chrome exists before the page scripts run. -->
394
- <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
395
- <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
396
- <script src="/builders/craft-mode.js?v=2026-11-05-oversight"></script>
397
- <script src="/builders/shell.js?v=2026-11-05-setup-gate"></script>
394
+ <script src="/builders/dom-utils.js?v=2026-11-05-numbers"></script>
395
+ <script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
396
+ <script src="/builders/craft-mode.js?v=2026-11-05-goals"></script>
397
+ <script src="/builders/shell.js?v=2026-11-05-goals"></script>
398
398
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
399
399
  <script src="/builders/palette.js" defer></script>
400
400
  <!-- shared component kit (window.OTBKit) — section rail (task 2166). -->
@@ -78,7 +78,8 @@
78
78
  function backLabel(path, hash) {
79
79
  if (path === '/') {
80
80
  var m = /^#\/(task|idea|blocker)\/(\d+)/.exec(hash || '');
81
- return m ? m[1] + ' ' + m[2] : (/^#(standing|accomplishments)/.test(hash || '') ? 'Accomplishments' : 'Home');
81
+ // task 1005050: no Accomplishments view any more; its hashes redirect to /profile.
82
+ return m ? m[1] + ' ' + m[2] : 'Home';
82
83
  }
83
84
  var rec = /^\/(task|idea)\/(\d+)$/.exec(path);
84
85
  if (rec) return rec[1] + ' ' + rec[2];
@@ -13,9 +13,9 @@
13
13
  // (#app-sidebar / #app-topbar / #app-foot) every hall page carries;
14
14
  // - rank-gate nav items COSMETICALLY (server-side page gates remain the real
15
15
  // access control — serve-internal.js requireGovernmentPage etc.);
16
- // - on the index (body[data-page="home"]) route the two views (#home,
17
- // #standing) plus the #achievements alias into Standing, and REDIRECT the
18
- // retired #leaderboard hash to the /roster page (task 1003441). The
16
+ // - on the index (body[data-page="home"]) route the #home view, and REDIRECT
17
+ // retired hashes: #leaderboard to the /roster page (task 1003441), and the
18
+ // Accomplishments hashes to your own /profile (task 1005050). The
19
19
  // `#/task|idea|blocker/N` deep-link hashes belong to builders.js and are
20
20
  // deliberately ignored here;
21
21
  // - expose window.OTBShell.applyAccess(...) so builders.js (index) or the
@@ -34,23 +34,12 @@
34
34
  // task 1003441: 'leaderboard' left this list — the Roll is the /roster PAGE
35
35
  // now. Its hash is not an alias (there is no host view to land on) but a
36
36
  // REDIRECT, declared in VIEW_REDIRECTS below.
37
- var VIEWS = ['home', 'standing'];
37
+ // task 1005050: 'standing' (Accomplishments) left too — it folded into /profile.
38
+ var VIEWS = ['home'];
38
39
  // A hash that lands on a view, scrolled to a section (route() ignores any
39
- // other). #achievements (task 2174) and each rail section (task 2178, keys =
40
- // STANDING_GROUPS, held by tests/hall_palette.mjs). Task 1004826 renamed the
41
- // 'standing' view Accomplishments: readers get #accomplishments*; old #standing* still lands.
42
- var VIEW_ALIASES = {
43
- accomplishments: { view: 'standing', anchor: null },
44
- 'accomplishments-work': { view: 'standing', anchor: 'standing-work' },
45
- 'accomplishments-karma': { view: 'standing', anchor: 'standing-karma' },
46
- 'accomplishments-progress': { view: 'standing', anchor: 'standing-progress' },
47
- 'accomplishments-achievements': { view: 'standing', anchor: 'standing-achievements' },
48
- achievements: { view: 'standing', anchor: 'standing-achievements' },
49
- 'standing-work': { view: 'standing', anchor: 'standing-work' },
50
- 'standing-karma': { view: 'standing', anchor: 'standing-karma' },
51
- 'standing-progress': { view: 'standing', anchor: 'standing-progress' },
52
- 'standing-achievements': { view: 'standing', anchor: 'standing-achievements' },
53
- };
40
+ // other). Empty since task 1005050: every alias named the 'standing' view,
41
+ // and those hashes are REDIRECTS now (below).
42
+ var VIEW_ALIASES = Object.create(null);
54
43
  // A retired view whose content moved to its OWN PAGE, so there is no host
55
44
  // view to alias onto. Every existing #leaderboard link — bookmarks, the
56
45
  // Standing card's stat tiles, profile.js's prose link — lands on the roster
@@ -58,8 +47,24 @@
58
47
  // Object.create(null): the key is an attacker-controlled hash string, so a
59
48
  // plain object would answer for "constructor"/"toString" as well as the real
60
49
  // entries. Harmless with one literal key, wrong to leave as the pattern.
50
+ // task 1005050 (owner decision 2026-11-03, idea 1001315): Accomplishments
51
+ // folded into the Profile page, so every #accomplishments / #standing /
52
+ // #achievements link lands on your OWN profile (/profile with no id is
53
+ // yours) — its "Just you" screen, its Works, or the overview with your
54
+ // achievements, whichever the old section was.
61
55
  var VIEW_REDIRECTS = Object.assign(Object.create(null), {
62
56
  leaderboard: '/roster',
57
+ accomplishments: '/profile#yours',
58
+ 'accomplishments-work': '/profile#works',
59
+ 'accomplishments-karma': '/profile#yours',
60
+ 'accomplishments-progress': '/profile#yours',
61
+ 'accomplishments-achievements': '/profile',
62
+ achievements: '/profile',
63
+ standing: '/profile#yours',
64
+ 'standing-work': '/profile#works',
65
+ 'standing-karma': '/profile#yours',
66
+ 'standing-progress': '/profile#yours',
67
+ 'standing-achievements': '/profile',
63
68
  });
64
69
  // builders.js owns these hashes (deep-link overlay) — never route them.
65
70
  var DEEPLINK_RE = /^#\/(task|idea|blocker)\/(\d+)$/;
@@ -209,11 +214,11 @@
209
214
  // because the href does: Goals' own "see which goals wait on each other"
210
215
  // link and every bookmark land here unchanged.
211
216
  { id: 'goal-map', label: 'Sky', icon: 'sky', href: '/goal-map', gate: 'member' },
212
- { id: 'roadmap', label: 'Roadmap', icon: 'roadmap', href: '/roadmap', gate: 'member' },
217
+ // task 1005051: Roadmap folded into Goals (its version band); /roadmap redirects there.
213
218
  // task 1003455 (goal 1000089): the open-blocker queue — what's stuck on the
214
- // owner. Lives with the project's own state (Roadmap/Goals/Tasks), not You
219
+ // owner. Lives with the project's own state (Goals/Tasks), not You
215
220
  // or Community, because its subject is the project, not a person. Its one
216
- // data source (GET /blockers) is deliberately public, like Roadmap's — but
221
+ // data source (GET /blockers) is deliberately public, like the version band's — but
217
222
  // the NAV FLAG stays 'signed' on the roster precedent: an ungated
218
223
  // page can still gate cosmetically wider in the sidebar than the API it
219
224
  // fronts, showing the link to signed-in builders while a stranger who
@@ -288,11 +293,9 @@
288
293
  // call 2026-09-25, ADR 0337 D5). Cross-project discovery is the hub's, and
289
294
  // /people redirects to /roster.
290
295
  ] },
291
- // Your own numbers. task 2174 folded Achievements into Standing and this task
292
- // retires its nav entry; VIEW_ALIASES still routes #achievements here, so old
293
- // bookmarks and the stat tiles keep landing on the section.
296
+ // Your own numbers. task 1005050: Accomplishments folded into Profile — one
297
+ // page for your numbers — and its old links redirect there (VIEW_REDIRECTS).
294
298
  { group: 'You', gate: 'signed', items: [
295
- { id: 'standing', label: 'Accomplishments', icon: 'accomplishments', href: '/#accomplishments', view: 'standing', gate: 'signed' },
296
299
  // task 1467: the viewable, shareable builder profile. The nav link points at
297
300
  // the viewer's OWN profile (profile.js defaults to self when no ?id=). gate is
298
301
  // cosmetic only — the /profile PAGE is public (a portfolio others can open via
@@ -1241,8 +1244,8 @@
1241
1244
  }
1242
1245
 
1243
1246
  function route(opts) {
1244
- // The redirect runs BEFORE the view lookup: #leaderboard is no longer a
1245
- // view, so viewFromHash would return nothing and route() would leave the
1247
+ // The redirect runs BEFORE the view lookup: #leaderboard (and since task
1248
+ // 1005050 #accomplishments and #standing) is no longer a view, so viewFromHash would return nothing and route() would leave the
1246
1249
  // page on Home — a dead link that looks like it worked. replace() rather
1247
1250
  // than assign() so Back does not bounce the reader between the two.
1248
1251
  var redirect = IS_INDEX && VIEW_REDIRECTS[String(window.location.hash || "").replace(/^#/, "")];
@@ -228,10 +228,10 @@
228
228
  </div>
229
229
  </div>
230
230
 
231
- <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
232
- <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
233
- <script src="/builders/craft-mode.js?v=2026-11-05-oversight"></script>
234
- <script src="/builders/shell.js?v=2026-11-05-setup-gate"></script>
231
+ <script src="/builders/dom-utils.js?v=2026-11-05-numbers"></script>
232
+ <script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
233
+ <script src="/builders/craft-mode.js?v=2026-11-05-goals"></script>
234
+ <script src="/builders/shell.js?v=2026-11-05-goals"></script>
235
235
  <script src="/builders/palette.js" defer></script>
236
236
  <script src="/builders/gov-living.js?v=2026-10-09-positions1"></script>
237
237
  <script src="/builders/gov-feeds.js?v=2026-10-09-living3"></script>
@@ -222,10 +222,10 @@
222
222
 
223
223
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
224
224
 
225
- <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
226
- <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
227
- <script src="/builders/craft-mode.js?v=2026-11-05-oversight"></script>
228
- <script src="/builders/shell.js?v=2026-11-05-setup-gate"></script>
225
+ <script src="/builders/dom-utils.js?v=2026-11-05-numbers"></script>
226
+ <script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
227
+ <script src="/builders/craft-mode.js?v=2026-11-05-goals"></script>
228
+ <script src="/builders/shell.js?v=2026-11-05-goals"></script>
229
229
  <script src="/builders/palette.js" defer></script>
230
230
  <!-- The kit, for its ONE renderer: the full-size ship-time visual figure
231
231
  (task 1003829), which the older reviews still show. -->
@@ -56,10 +56,10 @@
56
56
 
57
57
  <!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
58
58
  time so the chrome exists before the deferred page scripts execute. -->
59
- <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
60
- <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
61
- <script src="/builders/craft-mode.js?v=2026-11-05-oversight"></script>
62
- <script src="/builders/shell.js?v=2026-11-05-setup-gate"></script>
59
+ <script src="/builders/dom-utils.js?v=2026-11-05-numbers"></script>
60
+ <script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
61
+ <script src="/builders/craft-mode.js?v=2026-11-05-goals"></script>
62
+ <script src="/builders/shell.js?v=2026-11-05-goals"></script>
63
63
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
64
64
  <script src="/builders/palette.js" defer></script>
65
65
  <!-- task-detail.js still owns the #/idea/N and #/blocker/N deep-link overlay