@bongos/core 1.19.448 → 1.19.449
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 +55 -25
- package/docs/copy-inventory.md +60 -49
- package/docs/copy-registry.json +249 -117
- package/docs/module-api-changelog.md +2 -0
- package/modules/hall-ui/public/idea.css +138 -71
- package/modules/hall-ui/public/idea.html +13 -3
- package/modules/hall-ui/public/idea.js +17 -4
- package/modules/hall-ui/public/idea.states.json +14 -0
- package/modules/hall-ui/public/ideas.css +148 -107
- package/modules/hall-ui/public/ideas.html +15 -5
- package/modules/hall-ui/public/ideas.states.json +15 -0
- package/modules/hall-ui/public/record.css +324 -0
- package/modules/hall-ui/public/style.css +173 -39
- package/modules/hall-ui/public/task.css +71 -0
- package/modules/hall-ui/public/task.html +15 -32
- package/modules/hall-ui/public/task.js +142 -39
- package/modules/hall-ui/public/task.states.json +14 -0
- package/modules/hall-ui/public/work.css +4 -15
- package/modules/ui-design/kit/serve.js +7 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/hall-preview/server.js +5 -0
- package/src/module-api.js +1 -1
- package/tests/hall_audit.mjs +4 -1
- package/tests/hall_record_world.mjs +412 -0
|
@@ -9,35 +9,11 @@
|
|
|
9
9
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
10
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
11
|
<link rel="stylesheet" href="/builders/style.css?v=2026-07-03-roadmap1">
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
.task-detail__back { font-size: 13px; color: var(--ink-soft); text-decoration: none; display: inline-block; margin-bottom: 10px; }
|
|
19
|
-
.task-detail__back:hover { color: var(--ink); text-decoration: underline; }
|
|
20
|
-
|
|
21
|
-
.task-status-pill {
|
|
22
|
-
display: inline-flex; align-items: center; height: 20px; padding: 0 10px;
|
|
23
|
-
border-radius: 999px; font-size: 12px; font-weight: 600;
|
|
24
|
-
background: var(--surface-2); border: 1px solid var(--border); color: var(--ink-soft);
|
|
25
|
-
text-transform: capitalize;
|
|
26
|
-
}
|
|
27
|
-
.task-status-pill--ready,
|
|
28
|
-
.task-status-pill--shipped,
|
|
29
|
-
.task-status-pill--confirmed { background: var(--ok-weak); color: var(--ok); border-color: color-mix(in srgb, var(--ok) 30%, transparent); }
|
|
30
|
-
.task-status-pill--active,
|
|
31
|
-
.task-status-pill--completed { background: var(--accent-weak); color: var(--accent-text); border-color: color-mix(in srgb, var(--accent) 30%, transparent); }
|
|
32
|
-
.task-status-pill--blocked,
|
|
33
|
-
.task-status-pill--backlog { background: var(--warn-weak); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 30%, transparent); }
|
|
34
|
-
|
|
35
|
-
.task-detail__claim {
|
|
36
|
-
font-size: 13px; color: var(--ink-soft); margin: 0 0 12px;
|
|
37
|
-
display: flex; flex-wrap: wrap; gap: 4px 8px; align-items: baseline;
|
|
38
|
-
}
|
|
39
|
-
.task-detail__claim strong { color: var(--ink); font-weight: 600; }
|
|
40
|
-
</style>
|
|
12
|
+
<!-- The record archetype (shared with idea.html) and this page's own two
|
|
13
|
+
shapes. Promoted out of an inline <style> by task 1003305 — the block was
|
|
14
|
+
the hall's last substantive one and sat at its hall_audit ratchet. -->
|
|
15
|
+
<link rel="stylesheet" href="/builders/record.css">
|
|
16
|
+
<link rel="stylesheet" href="/builders/task.css">
|
|
41
17
|
</head>
|
|
42
18
|
<body data-page="task" data-page-title="Task">
|
|
43
19
|
|
|
@@ -51,14 +27,21 @@
|
|
|
51
27
|
<!-- task 2175: the breadcrumb sits in the page head, above the card, so a
|
|
52
28
|
deep-linked visitor (this page is deliberately not in the nav) has a
|
|
53
29
|
way back before the task body has loaded. -->
|
|
54
|
-
<
|
|
55
|
-
|
|
30
|
+
<div class="rec-crumb">
|
|
31
|
+
<a class="rec-back" href="/work">
|
|
32
|
+
<svg class="rec-back__ic" viewBox="0 0 16 16" aria-hidden="true"><path d="M9.4 3.6 5 8l4.4 4.4"/><path d="M5 8h7.2"/></svg>Back to tasks</a>
|
|
33
|
+
<p class="rec-sub" id="task-sub">Loading task</p>
|
|
34
|
+
</div>
|
|
56
35
|
</div>
|
|
57
36
|
|
|
58
37
|
<main id="hall-main">
|
|
59
38
|
<section class="scroll" id="task-scroll" aria-labelledby="task-h">
|
|
60
39
|
<div id="task-body">
|
|
61
|
-
|
|
40
|
+
<!-- The h1 is present in EVERY state, including this one: the
|
|
41
|
+
one-header-main-h1 floor measures the rendered DOM, and a page
|
|
42
|
+
whose heading only appears once a fetch lands has none for the
|
|
43
|
+
whole of the load, the not-found and the error renders. -->
|
|
44
|
+
<h1 class="rec-big" id="task-h">Loading the task</h1>
|
|
62
45
|
</div>
|
|
63
46
|
</section>
|
|
64
47
|
</main>
|
|
@@ -141,12 +141,77 @@
|
|
|
141
141
|
return `${Math.floor(sec / 86400)}d ago`;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
// The status vocabulary, which is the ONE sanctioned exception to the world's
|
|
145
|
+
// Fifteen Rule (DESIGN.md §Reuse) — a lifecycle state is verification state, so
|
|
146
|
+
// it keeps green/amber/red and never borrows the accent. The accent is for what
|
|
147
|
+
// is pressable or live, and a status pill is neither.
|
|
148
|
+
//
|
|
149
|
+
// `backlog` is DELIBERATELY absent, and that is a change: the retired inline
|
|
150
|
+
// block painted it amber, identically to `blocked`. Those are not the same
|
|
151
|
+
// fact. Amber says "something is wrong here", which is true of a task blocked
|
|
152
|
+
// behind an unshipped dependency and false of one simply not scheduled yet, and
|
|
153
|
+
// conflating them made every unscheduled task read as a problem. Backlog takes
|
|
154
|
+
// the neutral pill; the lifecycle rail's first beat carries the distinction in
|
|
155
|
+
// words ("not ready to be claimed yet" vs "waiting on another task").
|
|
156
|
+
const PILL_TONE = {
|
|
157
|
+
shipped: 'ok', confirmed: 'ok',
|
|
158
|
+
active: 'info', completed: 'info', ready: 'info',
|
|
159
|
+
blocked: 'warn',
|
|
160
|
+
};
|
|
161
|
+
function statusPill(status, extraClass) {
|
|
162
|
+
const s = String(status || 'unknown');
|
|
163
|
+
const tone = PILL_TONE[s];
|
|
164
|
+
return `<span class="rec-pill${tone ? ` rec-pill--${tone}` : ''}${extraClass ? ` ${extraClass}` : ''}">${escapeHtml(s)}</span>`;
|
|
165
|
+
}
|
|
166
|
+
|
|
144
167
|
function depList(kindWord, items) {
|
|
145
168
|
const rows = items.map((d) => {
|
|
146
|
-
const status = d.status ?
|
|
147
|
-
return `<li><a class="
|
|
169
|
+
const status = d.status ? statusPill(d.status, 'rec-pill--sm') : '';
|
|
170
|
+
return `<li><a class="rec-dep" href="${hallBase()}/task/${escapeHtml(String(d.id))}">` +
|
|
171
|
+
`<b class="rec-dep__id">${escapeHtml(String(d.id))}</b>${escapeHtml(d.title || 'task')}</a>${status}</li>`;
|
|
148
172
|
}).join('');
|
|
149
|
-
return `<
|
|
173
|
+
return `<section><p class="rec-sec__h">${escapeHtml(kindWord)}</p><ul class="rec-deplist">${rows}</ul></section>`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// The lifecycle rail: five beats, exactly one lit. Six GDS statuses fold into
|
|
177
|
+
// five because backlog / blocked / ready all mean "no builder holds it yet";
|
|
178
|
+
// the first beat's caption is what says WHICH of the three it is, so the rail
|
|
179
|
+
// never implies a claim that has not happened.
|
|
180
|
+
const RAIL_AT = { active: 1, completed: 2, confirmed: 3, shipped: 4 };
|
|
181
|
+
const CLAIM_CAPTION = {
|
|
182
|
+
backlog: 'not ready to be claimed yet',
|
|
183
|
+
blocked: 'waiting on another task',
|
|
184
|
+
ready: 'waiting for a builder',
|
|
185
|
+
};
|
|
186
|
+
function lifecycleRail(t, credits) {
|
|
187
|
+
const at = RAIL_AT[t.status] ?? 0;
|
|
188
|
+
const landed = t.status === 'shipped' && credits != null;
|
|
189
|
+
const beats = [
|
|
190
|
+
['Claim', CLAIM_CAPTION[t.status] || 'one builder, one worktree'],
|
|
191
|
+
['Build', 'the code and the docs'],
|
|
192
|
+
['Grade', 'the panel reads the diff'],
|
|
193
|
+
['Ship', 'merged, then deployed'],
|
|
194
|
+
['Credit', landed ? `${credits} ${window.OTBBranding?.taskCreditLabel?.(t) ?? curLabel()} landed` : 'credits land on the merge'],
|
|
195
|
+
];
|
|
196
|
+
const steps = beats.map(([name, caption], i) =>
|
|
197
|
+
`<li class="tk-step"${i === at ? ' aria-current="step"' : ''}>` +
|
|
198
|
+
`<div class="tk-step__dot"></div><span class="tk-step__n">${escapeHtml(name)}</span>` +
|
|
199
|
+
`<span class="tk-step__c">${escapeHtml(caption)}</span></li>`).join('');
|
|
200
|
+
return `<ol class="tk-rail" aria-label="Lifecycle">${steps}</ol>`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// The figure band — v2's stat-tile grid, the reference for any figure row the
|
|
204
|
+
// hall renders. Only figures the record actually CARRIES are drawn: most tasks
|
|
205
|
+
// have no recorded actual, and a band of dashes reads as a broken record
|
|
206
|
+
// rather than an honest one. Under two figures there is no band at all.
|
|
207
|
+
function figureBand(figs) {
|
|
208
|
+
const present = figs.filter((f) => f && f[1] != null && f[1] !== '');
|
|
209
|
+
if (present.length < 2) return '';
|
|
210
|
+
const cells = present.map(([label, value, unit]) =>
|
|
211
|
+
`<div class="rec-fig"><span class="rec-fig__l">${escapeHtml(label)}</span>` +
|
|
212
|
+
`<span class="rec-fig__v">${escapeHtml(String(value))}` +
|
|
213
|
+
`${unit ? `<u class="rec-fig__u">${escapeHtml(unit)}</u>` : ''}</span></div>`).join('');
|
|
214
|
+
return `<div class="rec-figs">${cells}</div>`;
|
|
150
215
|
}
|
|
151
216
|
|
|
152
217
|
// task 1003109 (goal 1000072, "Human legibility") — the ship-time visual.
|
|
@@ -164,11 +229,11 @@
|
|
|
164
229
|
if (!/^\/api\/(gds|bongos)\/task-visuals\/task-\d+-[0-9a-f]{16}\.(png|jpg|gif|webp)$/.test(url)) return '';
|
|
165
230
|
const alt = String(t.visual_alt || `Visual for task ${t.id}`);
|
|
166
231
|
const caption = t.visual_alt
|
|
167
|
-
? `<figcaption class="
|
|
232
|
+
? `<figcaption class="tk-visual__caption">${escapeHtml(t.visual_alt)}</figcaption>`
|
|
168
233
|
: '';
|
|
169
|
-
return `<figure class="
|
|
234
|
+
return `<figure class="tk-visual">` +
|
|
170
235
|
`<a href="${escapeHtml(url)}" target="_blank" rel="noopener">` +
|
|
171
|
-
`<img class="
|
|
236
|
+
`<img class="tk-visual__img" src="${escapeHtml(url)}" alt="${escapeHtml(alt)}" loading="lazy">` +
|
|
172
237
|
`</a>${caption}</figure>`;
|
|
173
238
|
}
|
|
174
239
|
|
|
@@ -196,29 +261,33 @@
|
|
|
196
261
|
function renderTask(t, lastClaim, viewer, goal) {
|
|
197
262
|
document.title = `#${t.id} — ${t.title || 'Task'} — ${document.title.split(' — ').pop()}`;
|
|
198
263
|
const sub = $('#task-sub');
|
|
199
|
-
if (sub) sub.textContent = `Task
|
|
264
|
+
if (sub) sub.textContent = `Task ${t.id}`;
|
|
200
265
|
|
|
201
266
|
const parts = [];
|
|
202
|
-
parts.push(`<p class="
|
|
203
|
-
parts.push(`<h1 class="
|
|
267
|
+
parts.push(`<p class="rec-kind">Task ${statusPill(t.status)}</p>`);
|
|
268
|
+
parts.push(`<h1 class="rec-title" id="task-h"><span class="rec-title__id">${escapeHtml(String(t.id))}</span>${escapeHtml(t.title || 'Untitled task')}</h1>`);
|
|
204
269
|
|
|
270
|
+
// The facts that place the task, in one line: what it belongs to, what KIND
|
|
271
|
+
// of work it is, and who is allowed to take it. The figures moved out of this
|
|
272
|
+
// run into the band below — a credit total set as one more comma-separated
|
|
273
|
+
// word is the thing the round's stat-tile reference exists to fix.
|
|
205
274
|
const meta = [];
|
|
206
|
-
if (t.version_id) meta.push(escapeHtml(t.version_id));
|
|
275
|
+
if (t.version_id) meta.push(`<b>${escapeHtml(t.version_id)}</b>`);
|
|
207
276
|
if (t.kind) meta.push(escapeHtml(t.kind));
|
|
208
277
|
if (t.discipline && t.discipline !== 'unclassified') meta.push(escapeHtml(t.discipline));
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
parts.push(`<p class="
|
|
278
|
+
// rankText() answers "any rank" for an open task, which read as
|
|
279
|
+
// "min rank any rank" once the label was prefixed. An open floor is not a
|
|
280
|
+
// minimum, so it is said as the absence of one.
|
|
281
|
+
const floor = rankText(t.requires_rank);
|
|
282
|
+
meta.push(escapeHtml(floor === 'any rank' ? 'open to any rank' : `min rank ${floor}`));
|
|
283
|
+
parts.push(`<p class="rec-meta">${meta.join(' · ')}</p>`);
|
|
215
284
|
|
|
216
285
|
// task 1002955: when was this written? The panel showed a shipped date but
|
|
217
286
|
// never a created one, so an untouched task carried no age at all. Absolute
|
|
218
287
|
// date first (the panel has the room the card chip doesn't), age after it.
|
|
219
288
|
if (t.created_at) {
|
|
220
289
|
const age = fmtRel(t.created_at);
|
|
221
|
-
parts.push(`<p class="
|
|
290
|
+
parts.push(`<p class="rec-note">Created ${escapeHtml(fmtDate(t.created_at))}${age ? `, ${escapeHtml(age)}` : ''}.</p>`);
|
|
222
291
|
}
|
|
223
292
|
|
|
224
293
|
if (t.goal_id) {
|
|
@@ -227,24 +296,35 @@
|
|
|
227
296
|
// read "Goal #1000072", which tells a person nothing they didn't already
|
|
228
297
|
// know, and the chip answers "what kind of work is this" without opening
|
|
229
298
|
// the goal. Marked inherited because a task carries no category of its own.
|
|
230
|
-
const goalName = goal && goal.title ?
|
|
299
|
+
const goalName = goal && goal.title ? `, ${escapeHtml(goal.title)}` : '';
|
|
231
300
|
const catChip = window.OTB.categoryChip(goal && goal.category, { inherited: true });
|
|
232
|
-
parts.push(`<p class="
|
|
301
|
+
parts.push(`<p class="rec-goal">Goal <a href="/goals#/goal/${encodeURIComponent(t.goal_id)}">${escapeHtml(String(t.goal_id))}${goalName}</a>${catChip}</p>`);
|
|
233
302
|
}
|
|
234
303
|
|
|
304
|
+
const creditVal = window.OTBBranding?.taskCreditValue?.(t) ?? t.credits_reward;
|
|
305
|
+
parts.push(figureBand([
|
|
306
|
+
[window.OTBBranding?.taskCreditLabel?.(t) ?? curLabel(), creditVal],
|
|
307
|
+
['Estimated', t.est_minutes, 'm'],
|
|
308
|
+
['Actual', t.actual_minutes, 'm'],
|
|
309
|
+
['Priority', t.priority == null ? null : 'P' + t.priority],
|
|
310
|
+
]));
|
|
311
|
+
parts.push(lifecycleRail(t, creditVal));
|
|
312
|
+
|
|
235
313
|
// Claim history (task 751 — best-effort: the API surfaces the most recent
|
|
236
314
|
// claim holder, not the full ledger of every past claim).
|
|
237
315
|
if (lastClaim) {
|
|
238
316
|
const who = escapeHtml(lastClaim.github_login || 'a builder');
|
|
239
317
|
const when = lastClaim.claimed_at ? fmtDate(lastClaim.claimed_at, { time: true }) : '';
|
|
240
318
|
const state = lastClaim.released_at
|
|
241
|
-
? (lastClaim.outcome ? `released
|
|
319
|
+
? (lastClaim.outcome ? `released, outcome ${escapeHtml(lastClaim.outcome)}` : 'released')
|
|
242
320
|
: 'holding this claim';
|
|
243
|
-
parts.push(`<p class="
|
|
321
|
+
parts.push(`<section class="rec-sec"><p class="rec-sec__h">Claim</p>` +
|
|
322
|
+
`<p class="rec-body">Last claimed by <b>${who}</b>${when ? ' on ' + escapeHtml(when) : ''}. ${state}.` +
|
|
323
|
+
` The task feed carries the most recent claim only, never the full ledger.</p></section>`);
|
|
244
324
|
}
|
|
245
325
|
|
|
246
326
|
if (t.value_summary) {
|
|
247
|
-
parts.push(`<p class="
|
|
327
|
+
parts.push(`<section class="rec-sec"><p class="rec-sec__h">Value</p><p class="rec-body">${escapeHtml(t.value_summary)}</p></section>`);
|
|
248
328
|
}
|
|
249
329
|
|
|
250
330
|
// task 1003109: the ship-time VISUAL — the same claim as the value summary in
|
|
@@ -252,23 +332,29 @@
|
|
|
252
332
|
// isn't. Absent on almost every task, and that is not an omission: a task with
|
|
253
333
|
// no visual must never look incomplete, so nothing is drawn at all when there
|
|
254
334
|
// is none — no placeholder, no empty frame, no "no visual" line.
|
|
255
|
-
if (t.visual_url) parts.push(taskVisualHtml(t));
|
|
335
|
+
if (t.visual_url) parts.push(`<section class="rec-sec">${taskVisualHtml(t)}</section>`);
|
|
256
336
|
|
|
257
337
|
if (t.description) {
|
|
258
|
-
parts.push(`<
|
|
338
|
+
parts.push(`<section class="rec-sec"><p class="rec-sec__h">Description</p><p class="rec-desc">${escapeHtml(t.description)}</p></section>`);
|
|
259
339
|
}
|
|
260
340
|
|
|
261
341
|
const touches = Array.isArray(t.touches) ? t.touches : [];
|
|
262
|
-
if (touches.length)
|
|
342
|
+
if (touches.length) {
|
|
343
|
+
const files = touches.map((f) => `<li>${escapeHtml(f)}</li>`).join('');
|
|
344
|
+
parts.push(`<section class="rec-sec"><p class="rec-sec__h">Touches <em>${touches.length} ${touches.length === 1 ? 'file' : 'files'}</em></p>` +
|
|
345
|
+
`<ul class="rec-files">${files}</ul></section>`);
|
|
346
|
+
}
|
|
263
347
|
|
|
264
348
|
const deps = Array.isArray(t.dependencies) ? t.dependencies : [];
|
|
265
349
|
const dependents = Array.isArray(t.dependents) ? t.dependents : [];
|
|
266
|
-
if (deps.length
|
|
267
|
-
|
|
350
|
+
if (deps.length || dependents.length) {
|
|
351
|
+
parts.push(`<div class="rec-sec rec-depcols">${deps.length ? depList('Blocked by', deps) : ''}${dependents.length ? depList('Blocks', dependents) : ''}</div>`);
|
|
352
|
+
}
|
|
268
353
|
|
|
269
354
|
if (t.shipped_at) {
|
|
270
|
-
const by = t.shipped_by ? ` by builder
|
|
271
|
-
parts.push(`<p class="
|
|
355
|
+
const by = t.shipped_by ? ` by builder ${escapeHtml(String(t.shipped_by))}` : '';
|
|
356
|
+
parts.push(`<section class="rec-sec"><p class="rec-sec__h">Shipped</p>` +
|
|
357
|
+
`<p class="rec-body">Shipped ${escapeHtml(fmtDate(t.shipped_at))}${by}, ${escapeHtml(fmtRel(t.shipped_at))}.</p></section>`);
|
|
272
358
|
}
|
|
273
359
|
|
|
274
360
|
// task 759: lifecycle action from the browser (no /builder-* CLI). The server
|
|
@@ -287,17 +373,25 @@
|
|
|
287
373
|
} else if (t.status === 'completed') {
|
|
288
374
|
lifecycle = rank === 'archon'
|
|
289
375
|
? `<button class="td-btn td-btn--primary" type="button" id="confirm-btn">Confirm</button>`
|
|
290
|
-
: `<p class="
|
|
376
|
+
: `<p class="rec-actnote">Awaiting the grader, or a confirmation, before it can ship.</p>`;
|
|
377
|
+
} else if (t.status === 'shipped') {
|
|
378
|
+
// A shipped task has no lifecycle control left, so the row says so in words
|
|
379
|
+
// rather than leaving a gap a reader has to interpret. Nothing here is
|
|
380
|
+
// pressable-or-live, so nothing here is warm — the One Warm Thing Rule.
|
|
381
|
+
lifecycle = `<p class="rec-actnote">This task has shipped and its credits have landed. Neither Ship it nor Confirm is available.</p>`;
|
|
291
382
|
}
|
|
292
383
|
|
|
293
384
|
// Copy prompt (the "ship link") — the same claim/build/ship prompt the Work
|
|
294
385
|
// Board hands an agent. Purely client-side (clipboard write, no server call).
|
|
295
386
|
parts.push(`
|
|
296
|
-
<
|
|
297
|
-
|
|
298
|
-
<
|
|
299
|
-
|
|
300
|
-
|
|
387
|
+
<section class="rec-sec">
|
|
388
|
+
<p class="rec-sec__h">Actions</p>
|
|
389
|
+
<div class="rec-actions">
|
|
390
|
+
${lifecycle}
|
|
391
|
+
<button class="td-btn" type="button" id="copy-prompt-btn">Copy prompt</button>
|
|
392
|
+
<a class="td-btn" href="${hallBase()}/work">Open the work board</a>
|
|
393
|
+
</div>
|
|
394
|
+
</section>
|
|
301
395
|
`);
|
|
302
396
|
|
|
303
397
|
const body = $('#task-body');
|
|
@@ -331,11 +425,16 @@
|
|
|
331
425
|
}
|
|
332
426
|
}
|
|
333
427
|
|
|
428
|
+
// The two honest failures. Both keep the <h1> — the empty and error renders are
|
|
429
|
+
// renders like any other, and a page whose only heading lives on the happy path
|
|
430
|
+
// has none in the states a reader is most likely to hit.
|
|
334
431
|
function renderNotFound(id) {
|
|
335
432
|
const body = $('#task-body');
|
|
336
433
|
if (body) body.innerHTML = `
|
|
337
|
-
<p class="
|
|
338
|
-
<
|
|
434
|
+
<p class="rec-kind">Task ${statusPill('no such task')}</p>
|
|
435
|
+
<h1 class="rec-big" id="task-h">${escapeHtml(String(id))}, no such task</h1>
|
|
436
|
+
<p class="rec-body">Tasks in the ledger are numbered separately from GitHub pull requests, so this may be a PR number rather than a task. The work board lists everything that does exist.</p>
|
|
437
|
+
<div class="rec-actions rec-sec"><a class="td-btn" href="${hallBase()}/work">Open the work board</a></div>
|
|
339
438
|
`;
|
|
340
439
|
const sub = $('#task-sub');
|
|
341
440
|
if (sub) sub.textContent = 'Not found';
|
|
@@ -344,9 +443,13 @@
|
|
|
344
443
|
function renderError(id) {
|
|
345
444
|
const body = $('#task-body');
|
|
346
445
|
if (body) body.innerHTML = `
|
|
347
|
-
<p class="
|
|
348
|
-
<
|
|
446
|
+
<p class="rec-kind">Task ${statusPill('feed unavailable')}</p>
|
|
447
|
+
<h1 class="rec-big" id="task-h">Could not load ${escapeHtml(String(id))}</h1>
|
|
448
|
+
<p class="rec-body">The task feed did not answer. Nothing on the record has changed, only the read failed. Try again in a moment, or open the work board.</p>
|
|
449
|
+
<div class="rec-actions rec-sec"><a class="td-btn" href="${hallBase()}/work">Open the work board</a></div>
|
|
349
450
|
`;
|
|
451
|
+
const sub = $('#task-sub');
|
|
452
|
+
if (sub) sub.textContent = 'Could not load';
|
|
350
453
|
}
|
|
351
454
|
|
|
352
455
|
async function load() {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_": "The hall's TASK RECORD through modules/ui-design/kit/render.js (task 1003305). The record's own controls — the dependency links, the lifecycle buttons, the figure band, the way back — only exist once a task has LOADED, so the floors here are run against the hall-preview harness rather than the kit's own stub, which carries the public-landing feeds and not the hall's:\n\n node scripts/hall-preview/server.js --port 4612 --fixture-me\n node modules/ui-design/kit/render.js --states modules/hall-ui/public/task.states.json --base-in http://builders.localhost:4612 --base-out http://builders.localhost:4612\n\nRun against the kit's own stubs instead and every state renders `no such task`, which is a real state and is the `missing` one below — just not the one the re-skin changed.",
|
|
3
|
+
"page": "/task/2166",
|
|
4
|
+
"surface": "hall-ui",
|
|
5
|
+
"stub": { "prefix": "/builders/" },
|
|
6
|
+
"modeQuery": false,
|
|
7
|
+
"ignoreRequests": ["/api/(gds|bongos)/"],
|
|
8
|
+
"ignoreConsole": ["/api/(gds|bongos)/", "\b401\b", "\b404\b", "Unauthorized", "Not Found", "load failed"],
|
|
9
|
+
"states": {
|
|
10
|
+
"record": { "auth": true, "actions": [["wait", 600]], "expect": { "visible": ["#task-body", "#copy-prompt-btn"] } },
|
|
11
|
+
"missing": { "auth": true, "url": "/task/1003999", "actions": [["wait", 600]], "expect": { "visible": ["#task-h"] } }
|
|
12
|
+
},
|
|
13
|
+
"reducedMotion": { "auth": true, "actions": [["wait", 600]] }
|
|
14
|
+
}
|
|
@@ -16,21 +16,10 @@
|
|
|
16
16
|
|
|
17
17
|
goals.html LOADS THIS SHEET TOO — a change here lands on both pages. */
|
|
18
18
|
|
|
19
|
-
/*
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
.page-head--board .page-head__h {
|
|
24
|
-
margin: 0 0 6px;
|
|
25
|
-
font-family: var(--label-face);
|
|
26
|
-
font-size: 14px;
|
|
27
|
-
font-weight: 400;
|
|
28
|
-
letter-spacing: var(--label-space);
|
|
29
|
-
text-transform: var(--label-case);
|
|
30
|
-
color: var(--ink);
|
|
31
|
-
text-shadow: var(--type-halo);
|
|
32
|
-
}
|
|
33
|
-
.page-head--board .page-head__sub { font-size: 14px; max-width: var(--measure); }
|
|
19
|
+
/* `.page-head--board` MOVED to style.css (task 1003305) when the ideas composer
|
|
20
|
+
became its third consumer: a modifier on the shell's own .page-head is shell
|
|
21
|
+
chrome, and a page sheet that only work.html and goals.html load cannot serve
|
|
22
|
+
a page that loads neither. Same rule, one home. */
|
|
34
23
|
|
|
35
24
|
.kbd {
|
|
36
25
|
display: inline-block;
|
|
@@ -288,7 +288,13 @@ const server = http.createServer((req, res) => {
|
|
|
288
288
|
const under = p.startsWith(PREFIX) ? p.slice(PREFIX.length) : (PREFIX !== '/' && (p === PREFIX.slice(0, -1)) ? '' : p.replace(/^\//, ''));
|
|
289
289
|
const asset = fileUnder(DIR, under);
|
|
290
290
|
if (asset && !asset.endsWith('.html')) return sendFile(asset, res);
|
|
291
|
-
const
|
|
291
|
+
const under2 = under.replace(/\/$/, '').replace(/\.html$/, '');
|
|
292
|
+
// A record page is `<page>/<id>` — the hall serves /builders/task/2166 and
|
|
293
|
+
// /builders/idea/12 from task.html and idea.html (serve-internal.js's two
|
|
294
|
+
// TASK_DETAIL_PAGE_RE / IDEA_DETAIL_PAGE_RE routes). Without this the states
|
|
295
|
+
// for those pages could only be rendered at a URL production never serves, and
|
|
296
|
+
// a page script that reads its id out of location.pathname would see none.
|
|
297
|
+
const name = under2 === '' ? 'index' : (fs.existsSync(path.join(DIR, under2 + '.html')) ? under2 : under2.replace(/\/[^/]+$/, ''));
|
|
292
298
|
if (!name.includes('/') && fs.existsSync(path.join(DIR, name + '.html'))) {
|
|
293
299
|
try { res.writeHead(200, { 'content-type': MIME['.html'], 'cache-control': 'no-store' }); return res.end(page(name + '.html').html); }
|
|
294
300
|
catch (e) { res.writeHead(500, { 'content-type': 'text/plain' }); return res.end('render failed: ' + e.stack); }
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.449",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.449",
|
|
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.19.
|
|
3
|
+
"version": "1.19.449",
|
|
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",
|
|
@@ -222,7 +222,12 @@ function sendPage(res, name) {
|
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
app.get('/', (req, res, next) => { if (!sendPage(res, 'index.html')) next(); });
|
|
225
|
+
// The two RECORD pages are the hall's only two-segment page routes
|
|
226
|
+
// (serve-internal.js TASK_DETAIL_PAGE_RE / IDEA_DETAIL_PAGE_RE). /idea/:id was
|
|
227
|
+
// missing here, so the idea record could not be previewed at all — the harness
|
|
228
|
+
// fell through to the real gate and 302'd to sign-in (task 1003305).
|
|
225
229
|
app.get(/^\/task\/\d+$/, (req, res, next) => { if (!sendPage(res, 'task.html')) next(); });
|
|
230
|
+
app.get(/^\/idea\/\d+$/, (req, res, next) => { if (!sendPage(res, 'idea.html')) next(); });
|
|
226
231
|
app.get(/^\/([a-z-]+)$/, (req, res, next) => {
|
|
227
232
|
if (!sendPage(res, `${req.params[0]}.html`)) next();
|
|
228
233
|
});
|
package/src/module-api.js
CHANGED
|
@@ -33,7 +33,7 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
33
33
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
34
34
|
// the entry to that file. Look for a version's history there, not here.
|
|
35
35
|
// ---------------------------------------------------------------------------
|
|
36
|
-
const CORE_VERSION = '1.19.
|
|
36
|
+
const CORE_VERSION = '1.19.449'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
37
37
|
|
|
38
38
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
39
39
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
package/tests/hall_audit.mjs
CHANGED
|
@@ -29,7 +29,10 @@ const pages = () => fs.readdirSync(HALL).filter((f) => f.endsWith('.html'));
|
|
|
29
29
|
// rather than "predates the program" — knowing WHY is what tells the next
|
|
30
30
|
// reader whether promoting it would actually fix anything.
|
|
31
31
|
const INLINE_STYLE_BUDGET = {
|
|
32
|
-
|
|
32
|
+
// (task.html's 27-line task-751 block was the last substantive inline style in
|
|
33
|
+
// the hall. Task 1003305 promoted it to task.css + record.css when the re-skin
|
|
34
|
+
// needed room the ratchet would not give, so the exception is gone with it —
|
|
35
|
+
// task.html now carries none and passes on the default.)
|
|
33
36
|
// (my-projects + scouting were carried here as standalone surfaces until task
|
|
34
37
|
// 1002868 adopted them into the shell; their exceptions are gone with them.)
|
|
35
38
|
// Genuinely page-local, and already token-derived. One line over the default;
|