@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
|
@@ -1,29 +1,23 @@
|
|
|
1
|
-
// roadmap.js — the
|
|
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
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
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 +
|
|
16
|
-
// /public/versions (started/shipped dates
|
|
17
|
-
//
|
|
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
|
|
46
|
-
|
|
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
|
-
//
|
|
101
|
-
// the
|
|
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
|
|
153
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
414
|
-
|
|
415
|
-
|
|
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
|
-
|
|
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 (
|
|
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('[
|
|
205
|
+
console.error('[goals] version band failed:', err);
|
|
463
206
|
const root = $('#rm-root');
|
|
464
|
-
if (root) root.innerHTML =
|
|
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-
|
|
60
|
-
<script src="/builders/shell-trail.js?v=2026-
|
|
61
|
-
<script src="/builders/craft-mode.js?v=2026-11-05-
|
|
62
|
-
<script src="/builders/shell.js?v=2026-11-05-
|
|
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-
|
|
395
|
-
<script src="/builders/shell-trail.js?v=2026-
|
|
396
|
-
<script src="/builders/craft-mode.js?v=2026-11-05-
|
|
397
|
-
<script src="/builders/shell.js?v=2026-11-05-
|
|
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
|
-
|
|
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
|
|
17
|
-
// #
|
|
18
|
-
//
|
|
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
|
-
|
|
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).
|
|
40
|
-
//
|
|
41
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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
|
|
292
|
-
//
|
|
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
|
|
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-
|
|
232
|
-
<script src="/builders/shell-trail.js?v=2026-
|
|
233
|
-
<script src="/builders/craft-mode.js?v=2026-11-05-
|
|
234
|
-
<script src="/builders/shell.js?v=2026-11-05-
|
|
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-
|
|
226
|
-
<script src="/builders/shell-trail.js?v=2026-
|
|
227
|
-
<script src="/builders/craft-mode.js?v=2026-11-05-
|
|
228
|
-
<script src="/builders/shell.js?v=2026-11-05-
|
|
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-
|
|
60
|
-
<script src="/builders/shell-trail.js?v=2026-
|
|
61
|
-
<script src="/builders/craft-mode.js?v=2026-11-05-
|
|
62
|
-
<script src="/builders/shell.js?v=2026-11-05-
|
|
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
|