@bongos/core 1.19.790 → 1.19.792
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 +76 -41
- package/.claude/skills/builder-reauth/SKILL.md +5 -3
- package/README.md +1 -1
- package/docs/adr/0206-own-scoped-session-revocation-no-absolute-ceiling.md +2 -0
- package/docs/adr/0294-cli-sessions-do-not-expire.md +56 -0
- package/docs/adr/README.md +1 -0
- package/docs/copy-inventory.md +90 -90
- package/docs/copy-registry.json +109 -109
- package/docs/module-api-changelog.md +4 -0
- package/migrations/core_243_cli_sessions_never_expire.sql +58 -0
- package/modules/hall-ui/public/hall-kit.js +7 -65
- package/modules/hall-ui/public/hall-render.js +1 -1
- package/modules/hall-ui/public/hall-visual.js +85 -0
- package/modules/hall-ui/public/index.html +1 -0
- package/modules/hall-ui/public/profile.html +1 -0
- package/modules/hall-ui/public/profile.js +2 -2
- package/modules/hall-ui/public/settings.js +5 -2
- package/modules/hall-ui/public/studio.css +1 -1
- package/modules/hall-ui/public/studio.html +1 -0
- package/modules/hall-ui/public/studio.js +4 -3
- package/modules/hall-ui/public/style.css +0 -16
- package/modules/hall-ui/public/task.html +1 -0
- package/modules/hall-ui/public/task.js +10 -13
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/accessibility-replay.js +195 -0
- package/scripts/gds/box-auth-check.js +1 -1
- package/scripts/gds/cli-lib.js +2 -2
- package/scripts/gds/reauth.js +9 -7
- package/scripts/gds/run-unit-tests.js +7 -0
- package/src/bongos/auth-github.js +2 -0
- package/src/bongos/db-kernel.js +29 -11
- package/src/bongos/db.js +33 -24
- package/src/bongos/routes/auth.js +3 -2
- package/src/module-api.js +1 -1
- package/tests/accessibility_replay.mjs +201 -0
- package/tests/auth_session_expiry_db.mjs +183 -0
- package/tests/auth_session_ttl.mjs +124 -11
- package/tests/fixtures/accessibility-replay.json +93 -0
- package/tests/hall_studio_world.mjs +19 -11
- package/tests/task_visuals.mjs +91 -27
|
@@ -2054,5 +2054,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2054
2054
|
landed since 1.19.788 with no explicit bump. run 35159544354. (task 1002620)
|
|
2055
2055
|
1.19.790 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2056
2056
|
landed since 1.19.789 with no explicit bump. run 35163350575. (task 1002620)
|
|
2057
|
+
1.19.791 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2058
|
+
landed since 1.19.790 with no explicit bump. run 35165066743. (task 1002620)
|
|
2059
|
+
1.19.792 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2060
|
+
landed since 1.19.791 with no explicit bump. run 35166486577. (task 1002620)
|
|
2057
2061
|
---------------------------------------------------------------------------
|
|
2058
2062
|
```
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
-- core_243 — CLI sessions lose their idle expiry (task 1003956, ADR 0294).
|
|
2
|
+
--
|
|
3
|
+
-- WHAT CHANGED IN CODE. db-kernel.js createSession now records NO expiry when a
|
|
4
|
+
-- caller names none (every such caller is source='cli'), and lookupSession's
|
|
5
|
+
-- refresh-on-use rolls expires_at forward only for a row that already has one.
|
|
6
|
+
-- Both halves are forward-looking: they govern sessions minted or used AFTER the
|
|
7
|
+
-- deploy. Neither touches the dated rows already sitting in the table.
|
|
8
|
+
--
|
|
9
|
+
-- WHY THAT NEEDS A MIGRATION. Without this, the code change would be nearly
|
|
10
|
+
-- invisible on the day it ships. Every live CLI session carries an expires_at
|
|
11
|
+
-- from migration 035's backfill and from every refresh since, so each one would
|
|
12
|
+
-- still lapse on its own 24h clock exactly as before — and each builder would eat
|
|
13
|
+
-- one final forced re-auth to get a row the new default applies to. That is the
|
|
14
|
+
-- precise annoyance the task set out to remove, delivered once more on the way
|
|
15
|
+
-- out. Clearing the column here is what makes the fix arrive for people who are
|
|
16
|
+
-- already signed in.
|
|
17
|
+
--
|
|
18
|
+
-- DIRECTION OF THE GRACE. Migration 035 faced the mirror of this and reasoned the
|
|
19
|
+
-- same way: it backfilled a 24h runway specifically so introducing a TTL would
|
|
20
|
+
-- not kill the session running the migration. This is that courtesy inverted —
|
|
21
|
+
-- 035 pushed expiries out so nobody was signed out by a new timer; this removes
|
|
22
|
+
-- them so nobody is signed out by a timer already gone from the code. In both
|
|
23
|
+
-- cases the migration exists to keep a policy change off the backs of the people
|
|
24
|
+
-- holding live sessions when it lands.
|
|
25
|
+
--
|
|
26
|
+
-- WHY source='cli' AND NOT A BLANKET UPDATE. builder_sessions holds both classes
|
|
27
|
+
-- and only the CLI's expiry was asked for. A web row's expires_at is an explicit
|
|
28
|
+
-- 30-day value its own flow chose (auth-idp.js / auth-github.js pass a real date,
|
|
29
|
+
-- so the new NULL default never reaches them); nulling it here would silently
|
|
30
|
+
-- hand browser cookies an immortality nobody decided on, and the code would not
|
|
31
|
+
-- re-establish it — lookupSession only rolls an expiry it can already see, so a
|
|
32
|
+
-- web row cleared by mistake stays cleared forever. The filter is the whole
|
|
33
|
+
-- safety property of this statement.
|
|
34
|
+
--
|
|
35
|
+
-- WHY revoked_at IS NULL. A revoked row is dead by revoked_at, which lookupSession
|
|
36
|
+
-- checks before it ever reads expires_at, so clearing its expiry could not revive
|
|
37
|
+
-- it. It is excluded anyway: revoked rows are the audit trail of sessions somebody
|
|
38
|
+
-- deliberately killed, and rewriting their columns muddies what that record says.
|
|
39
|
+
-- Touch only what the change is actually about.
|
|
40
|
+
--
|
|
41
|
+
-- WHAT THIS DOES NOT WEAKEN. Revocation is untouched and remains the real remedy
|
|
42
|
+
-- (ADR 0206): POST /me/sessions/revoke and "Sign out everywhere" kill a leaked
|
|
43
|
+
-- bearer immediately, setBuilderRank sweeps every session on demotion, and
|
|
44
|
+
-- requireBuilder refuses a deactivated builder on the hot path regardless of any
|
|
45
|
+
-- live token. None of those consult expires_at.
|
|
46
|
+
--
|
|
47
|
+
-- Idempotent: re-running matches no rows the second time (expires_at is already
|
|
48
|
+
-- NULL) and is a no-op.
|
|
49
|
+
|
|
50
|
+
BEGIN;
|
|
51
|
+
|
|
52
|
+
UPDATE builder_sessions
|
|
53
|
+
SET expires_at = NULL
|
|
54
|
+
WHERE source = 'cli'
|
|
55
|
+
AND revoked_at IS NULL
|
|
56
|
+
AND expires_at IS NOT NULL;
|
|
57
|
+
|
|
58
|
+
COMMIT;
|
|
@@ -579,70 +579,14 @@
|
|
|
579
579
|
};
|
|
580
580
|
}
|
|
581
581
|
|
|
582
|
-
// ---- ship-time task visual (task
|
|
582
|
+
// ---- ship-time task visual — MOVED OUT (task 1003835) ---------------------
|
|
583
583
|
//
|
|
584
|
-
//
|
|
585
|
-
//
|
|
586
|
-
//
|
|
587
|
-
//
|
|
588
|
-
//
|
|
589
|
-
//
|
|
590
|
-
// Lives here because three separate feeds render it (Standing's ship ledger,
|
|
591
|
-
// a profile's recent ships, the status dashboard) and a fourth copy of an
|
|
592
|
-
// HTML-building helper is how the pre-kit drift started.
|
|
593
|
-
|
|
594
|
-
// Same-origin, SERVER-MINTED paths only. The URL arrives from our own API, but
|
|
595
|
-
// it lands in an href and a src, so it is matched literally against the shape
|
|
596
|
-
// modules/lifecycle/task-visuals.js mints rather than trusted. Anything else
|
|
597
|
-
// renders nothing at all.
|
|
598
|
-
const VISUAL_URL_RE = /^\/api\/(gds|bongos)\/task-visuals\/task-\d+-[0-9a-f]{16}\.(png|jpg|gif|webp)$/;
|
|
599
|
-
function isTaskVisualUrl(url) {
|
|
600
|
-
return VISUAL_URL_RE.test(String(url ?? ''));
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
// The compact thumbnail. Returns '' when the row carries no visual — a task
|
|
604
|
-
// without one must never look incomplete, so nothing is drawn (no placeholder).
|
|
605
|
-
function shipVisualThumbHtml(row) {
|
|
606
|
-
const url = String((row && row.visual_url) ?? '');
|
|
607
|
-
if (!isTaskVisualUrl(url)) return '';
|
|
608
|
-
const alt = String((row && row.visual_alt) || `Visual for task ${(row && row.id) ?? ''}`).trim();
|
|
609
|
-
return `<a href="${esc(url)}" target="_blank" rel="noopener" title="${esc(alt)}">` +
|
|
610
|
-
`<img class="ship-visual-thumb" src="${esc(url)}" alt="${esc(alt)}" loading="lazy"></a>`;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
// The FULL-SIZE form: the picture as the thing being looked at, with
|
|
614
|
-
// visual_alt as its caption. The thumbnail above is for a FEED, where the
|
|
615
|
-
// summary is one line in a list; this is for a surface whose subject IS the
|
|
616
|
-
// image — the task record, and the Studio's review card (task 1003829).
|
|
617
|
-
//
|
|
618
|
-
// It lives here so the Studio did not become the THIRD hand-rolled <figure>
|
|
619
|
-
// for this one image. Be precise about what that did and did not fix: the two
|
|
620
|
-
// that already existed — task.js's taskVisualHtml (.tk-visual) and
|
|
621
|
-
// task-detail.js's overlay (.td-visual) — are NOT converged onto this yet and
|
|
622
|
-
// are untouched by the task that added it (1003829). Neither page loads the
|
|
623
|
-
// kit, so migrating them means adding it to task.html, which is a call of its
|
|
624
|
-
// own. So: one canonical renderer exists and new callers take it; the two
|
|
625
|
-
// legacy copies are a known, named cleanup rather than a claim already met.
|
|
626
|
-
//
|
|
627
|
-
// `className` is the caller's BEM base, because the size is the surface's
|
|
628
|
-
// decision and not the kit's: the task record caps at 52vh inside a record
|
|
629
|
-
// column (.tk-visual), the Studio gives the plate the card's whole width.
|
|
630
|
-
// The markup, the URL guard and the escaping are the kit's and identical
|
|
631
|
-
// everywhere. Returns '' with no visual, like the thumbnail.
|
|
632
|
-
function taskVisualFigureHtml(row, { className = 'task-visual' } = {}) {
|
|
633
|
-
const url = String((row && row.visual_url) ?? '');
|
|
634
|
-
if (!isTaskVisualUrl(url)) return '';
|
|
635
|
-
const written = String((row && row.visual_alt) || '').trim();
|
|
636
|
-
const alt = written || `Visual for task ${(row && row.id) ?? ''}`;
|
|
637
|
-
const base = String(className).replace(/[^A-Za-z0-9_-]/g, '');
|
|
638
|
-
// The caption is only drawn when the shipper WROTE one — the fallback alt is
|
|
639
|
-
// a machine sentence for a screen reader, not a caption to show a reader.
|
|
640
|
-
const caption = written ? `<figcaption class="${base}__caption">${esc(written)}</figcaption>` : '';
|
|
641
|
-
return `<figure class="${base}">` +
|
|
642
|
-
`<a href="${esc(url)}" target="_blank" rel="noopener">` +
|
|
643
|
-
`<img class="${base}__img" src="${esc(url)}" alt="${esc(alt)}" loading="lazy">` +
|
|
644
|
-
`</a>${caption}</figure>`;
|
|
645
|
-
}
|
|
584
|
+
// isTaskVisualUrl / shipVisualThumbHtml / taskVisualFigureHtml now live in
|
|
585
|
+
// hall-visual.js. They left because the task record page renders a visual and
|
|
586
|
+
// needs nothing else in this file, so converging it here would have loaded 61KB
|
|
587
|
+
// on the hall's most-visited record page for one function. A page that draws a
|
|
588
|
+
// visual loads hall-visual.js; pages that only use the kit no longer carry the
|
|
589
|
+
// renderers at all. Callers read window.OTBVisual, not OTBKit.
|
|
646
590
|
|
|
647
591
|
// ---- charts (task 1003445) ------------------------------------------------
|
|
648
592
|
//
|
|
@@ -1218,8 +1162,6 @@
|
|
|
1218
1162
|
plotBox, plotWindow,
|
|
1219
1163
|
// states
|
|
1220
1164
|
emptyStateHtml, skeletonRows, collapsibleCard,
|
|
1221
|
-
// ship-time task visual (task 1003109; the full-size figure, task 1003829)
|
|
1222
|
-
isTaskVisualUrl, shipVisualThumbHtml, taskVisualFigureHtml,
|
|
1223
1165
|
// age (task 1002955)
|
|
1224
1166
|
fmtAge, fmtAbsDate, ageChipHtml, AGE_STALE_DAYS,
|
|
1225
1167
|
};
|
|
@@ -1100,7 +1100,7 @@
|
|
|
1100
1100
|
<td>
|
|
1101
1101
|
<div>${escapeHtml(s.title)}</div>
|
|
1102
1102
|
${s.value_summary ? `<div style="color:var(--ink-soft);font-size:0.88rem;font-style:italic;margin-top:2px">${escapeHtml(s.value_summary)}</div>` : ''}
|
|
1103
|
-
${s.visual_url ? `<div style="margin-top:6px">${window.
|
|
1103
|
+
${s.visual_url && window.OTBVisual ? `<div style="margin-top:6px">${window.OTBVisual.shipVisualThumbHtml(s)}</div>` : ''}
|
|
1104
1104
|
</td>
|
|
1105
1105
|
<td class="ledger__drachmae">${fmtNum(window.OTBBranding?.taskCreditValue?.(s) ?? s.credits_reward)}</td>
|
|
1106
1106
|
<td class="ledger__date">${fmtDate(s.shipped_at)}</td>
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// modules/hall-ui/public/hall-visual.js — the ship-time task visual, in one place
|
|
2
|
+
// (task 1003835; the renderers themselves are tasks 1003109 and 1003829).
|
|
3
|
+
//
|
|
4
|
+
// A satellite of hall-kit.js rather than a part of it, and that is the decision
|
|
5
|
+
// this file records. These three functions lived in the kit, which is 61KB of
|
|
6
|
+
// pager/ledger/filter/chart machinery. The task record page (task.html) renders a
|
|
7
|
+
// visual and needs NOTHING else the kit holds, so converging it onto the kit's
|
|
8
|
+
// renderer would have meant loading 61KB on the hall's most-visited record page
|
|
9
|
+
// for one function — a real cost, paid on the wrong page. The alternative was to
|
|
10
|
+
// let task.js keep its own copy, which is how the hall ended up with two.
|
|
11
|
+
//
|
|
12
|
+
// So the helpers moved OUT. Any page that renders a visual loads this file; the
|
|
13
|
+
// kit no longer carries them and the pages that only use the kit no longer pay
|
|
14
|
+
// for them. The owner's call (2026-09-16) was that the kit may grow satellites
|
|
15
|
+
// like this one.
|
|
16
|
+
//
|
|
17
|
+
// Contract, identical to the kit's own: dependency-free (its own esc(), no
|
|
18
|
+
// window.OTB use) so load order only needs "before the page script";
|
|
19
|
+
// node-require-safe, so tests/task_visuals.mjs can require it with no DOM;
|
|
20
|
+
// string-template builders returning HTML, no state, no render loop.
|
|
21
|
+
//
|
|
22
|
+
// CSS: each caller supplies its own BEM base, because SIZE is the surface's
|
|
23
|
+
// decision and not this file's — the task record caps at 52vh in a record column
|
|
24
|
+
// (.tk-visual, style.css/task.css), the Studio gives the plate the card's whole
|
|
25
|
+
// width (.studio-plate). The markup, the URL guard and the escaping are shared.
|
|
26
|
+
(() => {
|
|
27
|
+
'use strict';
|
|
28
|
+
|
|
29
|
+
// HTML-escape for text interpolated into innerHTML.
|
|
30
|
+
function esc(s) {
|
|
31
|
+
return String(s ?? '').replace(/[&<>"']/g, (c) => ({
|
|
32
|
+
'&': '&', '<': '<', '>': '>', '"': '"', "'": ''',
|
|
33
|
+
}[c]));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Same-origin, SERVER-MINTED paths only. The URL arrives from our own API, but
|
|
37
|
+
// it lands in an href and a src, so it is matched literally against the shape
|
|
38
|
+
// modules/lifecycle/task-visuals.js mints rather than trusted. Anything else
|
|
39
|
+
// renders nothing at all.
|
|
40
|
+
const VISUAL_URL_RE = /^\/api\/(gds|bongos)\/task-visuals\/task-\d+-[0-9a-f]{16}\.(png|jpg|gif|webp)$/;
|
|
41
|
+
function isTaskVisualUrl(url) {
|
|
42
|
+
return VISUAL_URL_RE.test(String(url ?? ''));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// The compact thumbnail, for a FEED — where the summary is one line in a list,
|
|
46
|
+
// so the image appears small and links to the full-size file. Three feeds draw
|
|
47
|
+
// it (the hall's ship ledger, a profile's recent ships, the status dashboard).
|
|
48
|
+
// Returns '' when the row carries no visual: a task without one must never look
|
|
49
|
+
// incomplete, so nothing is drawn and there is no placeholder.
|
|
50
|
+
function shipVisualThumbHtml(row) {
|
|
51
|
+
const url = String((row && row.visual_url) ?? '');
|
|
52
|
+
if (!isTaskVisualUrl(url)) return '';
|
|
53
|
+
const alt = String((row && row.visual_alt) || `Visual for task ${(row && row.id) ?? ''}`).trim();
|
|
54
|
+
return `<a href="${esc(url)}" target="_blank" rel="noopener" title="${esc(alt)}">` +
|
|
55
|
+
`<img class="ship-visual-thumb" src="${esc(url)}" alt="${esc(alt)}" loading="lazy"></a>`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// The FULL-SIZE form: the picture as the thing being looked at, with visual_alt
|
|
59
|
+
// as its caption. For a surface whose subject IS the image — the task record
|
|
60
|
+
// (.tk-visual) and the Studio's review card (.studio-plate).
|
|
61
|
+
//
|
|
62
|
+
// `className` is the caller's BEM base; it is sanitised because it reaches a
|
|
63
|
+
// class attribute. Returns '' with no visual, like the thumbnail.
|
|
64
|
+
function taskVisualFigureHtml(row, { className = 'task-visual' } = {}) {
|
|
65
|
+
const url = String((row && row.visual_url) ?? '');
|
|
66
|
+
if (!isTaskVisualUrl(url)) return '';
|
|
67
|
+
const written = String((row && row.visual_alt) || '').trim();
|
|
68
|
+
const alt = written || `Visual for task ${(row && row.id) ?? ''}`;
|
|
69
|
+
const base = String(className).replace(/[^A-Za-z0-9_-]/g, '');
|
|
70
|
+
// The caption is only drawn when the shipper WROTE one — the fallback alt is
|
|
71
|
+
// a machine sentence for a screen reader, not a caption to show a reader.
|
|
72
|
+
const caption = written ? `<figcaption class="${base}__caption">${esc(written)}</figcaption>` : '';
|
|
73
|
+
return `<figure class="${base}">` +
|
|
74
|
+
`<a href="${esc(url)}" target="_blank" rel="noopener">` +
|
|
75
|
+
`<img class="${base}__img" src="${esc(url)}" alt="${esc(alt)}" loading="lazy">` +
|
|
76
|
+
`</a>${caption}</figure>`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const api = { isTaskVisualUrl, shipVisualThumbHtml, taskVisualFigureHtml };
|
|
80
|
+
|
|
81
|
+
// Node-require-safe (the dom-utils.js / hall-kit.js pattern): assignment is the
|
|
82
|
+
// only eval-time side effect, and only when a window exists.
|
|
83
|
+
if (typeof window !== 'undefined') window.OTBVisual = api;
|
|
84
|
+
if (typeof module !== 'undefined' && module.exports) module.exports = api;
|
|
85
|
+
})();
|
|
@@ -110,6 +110,7 @@
|
|
|
110
110
|
<script src="/builders/box-panel.js?v=2026-06-14-domutils" defer></script>
|
|
111
111
|
<!-- task 2166: the shared component kit (window.OTBKit) — pager/ledger/rail.
|
|
112
112
|
Loaded BEFORE hall-widgets.js, whose skeletons call OTBKit.pagerBarHtml(). -->
|
|
113
|
+
<script src="/builders/hall-visual.js?v=2026-09-16-visual1" defer></script>
|
|
113
114
|
<script src="/builders/hall-kit.js" defer></script>
|
|
114
115
|
<!-- BV1.R75: the CORE hall widgets register into window.OTB's hall-widget registry.
|
|
115
116
|
Loaded BEFORE builders.js so the registry is populated when builders.js stamps
|
|
@@ -196,6 +196,7 @@
|
|
|
196
196
|
<!-- hall-kit (window.OTBKit) — profile.js renders ship-time task visuals through
|
|
197
197
|
its shared thumbnail helper (task 1003109) and builds the section rail with
|
|
198
198
|
it (task 1003489), so it must load first. -->
|
|
199
|
+
<script src="/builders/hall-visual.js?v=2026-09-16-visual1"></script>
|
|
199
200
|
<script src="/builders/hall-kit.js?v=2026-08-27-activity"></script>
|
|
200
201
|
<!-- Connections (section 6). Loaded BEFORE profile.js so window.OTBConnections
|
|
201
202
|
exists by the time renderProfile runs; profile.js guards on it anyway, so a
|
|
@@ -176,7 +176,7 @@
|
|
|
176
176
|
const summary = s.value_summary
|
|
177
177
|
? `<span style="display:block;margin-top:2px;font-size:12px;color:var(--ink-faint)">${escapeHtml(s.value_summary)}</span>`
|
|
178
178
|
: '';
|
|
179
|
-
const visual = window.
|
|
179
|
+
const visual = window.OTBVisual ? window.OTBVisual.shipVisualThumbHtml(s) : '';
|
|
180
180
|
return `<tr>
|
|
181
181
|
<td class="ledger__num">#${escapeHtml(String(s.id))}</td>
|
|
182
182
|
<td><a href="${taskHref(s.id)}">${escapeHtml(s.title || `Task ${s.id}`)}</a>${summary}${visual ? `<span style="display:block;margin-top:6px">${visual}</span>` : ''}</td>
|
|
@@ -473,7 +473,7 @@
|
|
|
473
473
|
const summary = t.value_summary
|
|
474
474
|
? `<span style="display:block;margin-top:2px;font-size:12px;color:var(--ink-faint)">${escapeHtml(t.value_summary)}</span>`
|
|
475
475
|
: '';
|
|
476
|
-
const visual =
|
|
476
|
+
const visual = window.OTBVisual ? window.OTBVisual.shipVisualThumbHtml(t) : '';
|
|
477
477
|
return `<tr>
|
|
478
478
|
<td class="ledger__num">#${escapeHtml(String(t.id))}</td>
|
|
479
479
|
<td><a href="${taskHref(t.id)}">${escapeHtml(t.title || `Task ${t.id}`)}</a>${summary}${visual ? `<span style="display:block;margin-top:6px">${visual}</span>` : ''}</td>
|
|
@@ -913,7 +913,10 @@
|
|
|
913
913
|
if (res.status === 401) { toAuth(); return; }
|
|
914
914
|
const body = (res.data || {});
|
|
915
915
|
if (!res.ok) throw new Error(body.message || `HTTP ${res.status}`);
|
|
916
|
-
|
|
916
|
+
// No expires_at is the NORMAL case for a CLI bearer since ADR 0294 — say so
|
|
917
|
+
// plainly. 'unknown' read as a gap in the page's knowledge; the absence of a
|
|
918
|
+
// date is the fact, and the revoke is what replaced the timer.
|
|
919
|
+
const expires = body.expires_at ? `Expires ${new Date(body.expires_at).toLocaleString()}` : 'Never expires';
|
|
917
920
|
const pasteCommand = body.paste_command || '';
|
|
918
921
|
// The secret is QUOTED (settings.css `.quote`): a 64-character token cannot
|
|
919
922
|
// be a line of prose, so it sits one tonal step down with its own foot. Same
|
|
@@ -936,7 +939,7 @@
|
|
|
936
939
|
copyBtn.id = 'cli-reissue-copy';
|
|
937
940
|
copyBtn.textContent = 'Copy';
|
|
938
941
|
const hint = el('span', 'settings-card__hint');
|
|
939
|
-
hint.textContent =
|
|
942
|
+
hint.textContent = `${expires} · revoke it any time under Active sessions.`;
|
|
940
943
|
const foot = el('div', 'quote__foot');
|
|
941
944
|
foot.append(copyBtn, hint);
|
|
942
945
|
const quote = el('div', 'quote');
|
|
@@ -155,7 +155,7 @@
|
|
|
155
155
|
IS the card: an artist is being asked to LOOK at something, so it is given the
|
|
156
156
|
card's full width and as much height as a screen can spare, not the 44px
|
|
157
157
|
thumbnail a ship feed uses. Markup comes from the kit's one renderer
|
|
158
|
-
(
|
|
158
|
+
(OTBVisual.taskVisualFigureHtml); only the size is this room's decision.
|
|
159
159
|
|
|
160
160
|
max-height in vh, and MEASURED at 1440x900 rather than chosen. The size an
|
|
161
161
|
artist can judge it at wins over a zero-scroll card — that is the task's own
|
|
@@ -72,6 +72,7 @@
|
|
|
72
72
|
<!-- The kit, for its ONE renderer: the full-size ship-time visual figure
|
|
73
73
|
(task 1003829). The copy desk — the artist's other room — already loads it,
|
|
74
74
|
and a fourth hand-rolled <figure> is what the kit exists to stop. -->
|
|
75
|
+
<script src="/builders/hall-visual.js?v=2026-09-16-visual1"></script>
|
|
75
76
|
<script src="/builders/hall-kit.js"></script>
|
|
76
77
|
<script src="/builders/studio.js?v=2026-09-10-studio2"></script>
|
|
77
78
|
</body>
|
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
// ---------------------------------------------------------------------------
|
|
133
133
|
|
|
134
134
|
// THE PLATE — the reviewed work's ship-time visual, at the size the card can
|
|
135
|
-
// give it. Through the
|
|
135
|
+
// give it. Through the one renderer (OTBVisual.taskVisualFigureHtml, which
|
|
136
136
|
// carries the server-minted-URL guard and the escaping) with the Studio's own
|
|
137
137
|
// class, because the size is this surface's decision: here the picture is the
|
|
138
138
|
// subject, not an illustration beside a record.
|
|
@@ -141,8 +141,9 @@
|
|
|
141
141
|
// read, or when the kit is absent — a review with no picture must read as a
|
|
142
142
|
// review, never as an empty frame (the degrade rule).
|
|
143
143
|
function plateHtml(p) {
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
const vis = window.OTBVisual;
|
|
145
|
+
if (!p.shipped || !vis || typeof vis.taskVisualFigureHtml !== 'function') return '';
|
|
146
|
+
return vis.taskVisualFigureHtml(p.shipped, { className: 'studio-plate' });
|
|
146
147
|
}
|
|
147
148
|
|
|
148
149
|
// The three weights, and the one field they share. Offered only to a viewer
|
|
@@ -2841,22 +2841,6 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
|
|
|
2841
2841
|
overflow-y: auto;
|
|
2842
2842
|
}
|
|
2843
2843
|
.td-touches { font-size: 12.5px; color: var(--ink-faint); font-family: var(--font-mono); word-break: break-word; margin: 0 0 12px; }
|
|
2844
|
-
/* task 1003109: the ship-time VISUAL, rendered directly under the value summary
|
|
2845
|
-
it restates. Sized to be READ (this exists so a non-engineer can see a change),
|
|
2846
|
-
capped so a tall screenshot can't push the rest of the record off the page, and
|
|
2847
|
-
never drawn at all when a task has no visual. */
|
|
2848
|
-
.td-visual { margin: 0 0 12px; }
|
|
2849
|
-
.td-visual__img {
|
|
2850
|
-
display: block;
|
|
2851
|
-
max-width: 100%;
|
|
2852
|
-
max-height: 52vh;
|
|
2853
|
-
width: auto;
|
|
2854
|
-
height: auto;
|
|
2855
|
-
border: var(--line) solid var(--rule-soft);
|
|
2856
|
-
border-radius: var(--radius-sm);
|
|
2857
|
-
background: var(--glass);
|
|
2858
|
-
}
|
|
2859
|
-
.td-visual__caption { font-size: 12.5px; color: var(--ink-faint); margin: 6px 0 0; }
|
|
2860
2844
|
/* The compact form: a thumbnail beside a value summary in a ship FEED, where the
|
|
2861
2845
|
summary is one line in a list and a full-width image would drown it. */
|
|
2862
2846
|
.ship-visual-thumb {
|
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
<script src="/builders/task-detail.js?v=2026-07-03-taskdetail2" defer></script>
|
|
69
69
|
<script src="/builders/brand-apply.js?v=2026-06-19-r56" defer></script>
|
|
70
70
|
<script src="/builders/md-reader.js" defer></script>
|
|
71
|
+
<script src="/builders/hall-visual.js?v=2026-09-16-visual1" defer></script>
|
|
71
72
|
<script src="/builders/task.js?v=2026-07-03-taskpage1" defer></script>
|
|
72
73
|
</body>
|
|
73
74
|
</html>
|
|
@@ -292,20 +292,17 @@
|
|
|
292
292
|
// a detail can be opened full-size. The caption (visual_alt) doubles as the alt
|
|
293
293
|
// text, which is why it is stored: an image with no alt tells a screen reader
|
|
294
294
|
// nothing, and "a picture of the change" is exactly the audience that needs it.
|
|
295
|
+
// The shared renderer (hall-visual.js), not a copy of it — task 1003835. This
|
|
296
|
+
// page hand-rolled the same <figure> until then; the markup it produced is what
|
|
297
|
+
// OTBVisual emits, so .tk-visual's rules are unchanged. `className` keeps the
|
|
298
|
+
// sizing this page's own CSS owns (52vh inside a record column).
|
|
299
|
+
//
|
|
300
|
+
// Guarded rather than assumed: hall-visual.js is a separate <script>, so a page
|
|
301
|
+
// that forgot it should render no picture, never a broken one.
|
|
295
302
|
function taskVisualHtml(t) {
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
// trusting: anything that is not the shape our serve route mints is dropped.
|
|
300
|
-
if (!/^\/api\/(gds|bongos)\/task-visuals\/task-\d+-[0-9a-f]{16}\.(png|jpg|gif|webp)$/.test(url)) return '';
|
|
301
|
-
const alt = String(t.visual_alt || `Visual for task ${t.id}`);
|
|
302
|
-
const caption = t.visual_alt
|
|
303
|
-
? `<figcaption class="tk-visual__caption">${escapeHtml(t.visual_alt)}</figcaption>`
|
|
304
|
-
: '';
|
|
305
|
-
return `<figure class="tk-visual">` +
|
|
306
|
-
`<a href="${escapeHtml(url)}" target="_blank" rel="noopener">` +
|
|
307
|
-
`<img class="tk-visual__img" src="${escapeHtml(url)}" alt="${escapeHtml(alt)}" loading="lazy">` +
|
|
308
|
-
`</a>${caption}</figure>`;
|
|
303
|
+
const vis = window.OTBVisual;
|
|
304
|
+
if (!vis || typeof vis.taskVisualFigureHtml !== 'function') return '';
|
|
305
|
+
return vis.taskVisualFigureHtml(t, { className: 'tk-visual' });
|
|
309
306
|
}
|
|
310
307
|
|
|
311
308
|
async function copyText(text) {
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.792",
|
|
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.792",
|
|
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.792",
|
|
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",
|