@bongos/core 1.19.1040 → 1.19.1042
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 +110 -80
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +4 -0
- package/clients/bongos-client/index.cjs +4 -0
- package/clients/bongos-client/index.d.ts +6 -1
- package/clients/bongos-client/index.mjs +4 -0
- package/docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md +14 -0
- package/docs/adr/0339-a-projects-owner-upgrades-it-and-a-hosted-project-hands-off-to-the-hub-door.md +1 -0
- package/docs/adr/0340-every-hall-takes-its-update-from-settings-and-deploy-is-the-platform-halls-page.md +64 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +64 -7
- package/docs/api-reference.md +7 -1
- package/docs/copy-inventory.md +41 -21
- package/docs/copy-registry.json +207 -27
- package/docs/file-map.md +1 -1
- package/docs/module-api-changelog.md +12 -0
- package/modules/grading/grader-subagent.js +7 -0
- package/modules/hall-ui/public/atlas.html +1 -1
- package/modules/hall-ui/public/blockers.html +1 -1
- package/modules/hall-ui/public/board-room.html +1 -1
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/copy-desk.html +1 -1
- package/modules/hall-ui/public/deploy.html +1 -1
- package/modules/hall-ui/public/diagrams.html +1 -1
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/fleet.html +1 -1
- package/modules/hall-ui/public/gate.html +1 -1
- package/modules/hall-ui/public/goals.html +1 -1
- package/modules/hall-ui/public/government.html +1 -1
- package/modules/hall-ui/public/idea.html +1 -1
- package/modules/hall-ui/public/ideas.html +1 -1
- package/modules/hall-ui/public/index.html +1 -1
- package/modules/hall-ui/public/modules.html +1 -1
- package/modules/hall-ui/public/palette.js +1 -0
- package/modules/hall-ui/public/people.html +1 -1
- package/modules/hall-ui/public/primer.html +1 -1
- package/modules/hall-ui/public/profile.html +1 -1
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/ranks.html +1 -1
- package/modules/hall-ui/public/roadmap.html +1 -1
- package/modules/hall-ui/public/roster.html +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings-software-update.js +194 -0
- package/modules/hall-ui/public/settings.css +39 -0
- package/modules/hall-ui/public/settings.html +24 -2
- package/modules/hall-ui/public/settings.js +5 -0
- package/modules/hall-ui/public/settings.states.json +2 -1
- package/modules/hall-ui/public/shell.js +25 -39
- package/modules/hall-ui/public/studio.html +1 -1
- package/modules/hall-ui/public/task.html +1 -1
- package/modules/hall-ui/public/watch.html +1 -1
- package/modules/hall-ui/public/work.html +1 -1
- package/modules/hall-ui/records/panel-family.md +9 -0
- package/modules/npm-release/public/work.js +8 -3
- package/modules/npm-release/work.js +8 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +16 -0
- package/scripts/gds/fitness.js +7 -0
- package/scripts/gds/main-audit.js +204 -7
- package/{modules/npm-release/registry.js → src/bongos/package-registry.js} +14 -7
- package/src/bongos/routes/core-update.js +65 -3
- package/src/bongos/serve-internal.js +17 -0
- package/src/bongos/software-update.js +127 -0
- package/src/module-api.js +10 -1
- package/tests/core_upgrade_owner_door.mjs +27 -18
- package/tests/grade_model_provider.mjs +1 -1
- package/tests/grader_reviewer_isolation.mjs +68 -0
- package/tests/helpers.mjs +92 -0
- package/tests/main_audit_attestation.mjs +238 -0
- package/tests/module_api.mjs +1 -0
- package/tests/nav_permission_atoms.mjs +1 -1
- package/tests/npm_release_page.mjs +11 -1
- package/tests/npm_release_work.mjs +9 -94
- package/tests/software_update.mjs +449 -0
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
<div id="gate-modal-root"></div>
|
|
77
77
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
78
78
|
|
|
79
|
-
<script src="/builders/shell.js?v=2026-09-26-
|
|
79
|
+
<script src="/builders/shell.js?v=2026-09-26-swupdate"></script>
|
|
80
80
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
81
81
|
<script src="/builders/palette.js" defer></script>
|
|
82
82
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
@@ -235,7 +235,7 @@
|
|
|
235
235
|
|
|
236
236
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
237
237
|
|
|
238
|
-
<script src="/builders/shell.js?v=2026-09-26-
|
|
238
|
+
<script src="/builders/shell.js?v=2026-09-26-swupdate"></script>
|
|
239
239
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
240
240
|
<script src="/builders/palette.js" defer></script>
|
|
241
241
|
<script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
|
|
@@ -134,7 +134,7 @@
|
|
|
134
134
|
|
|
135
135
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
136
136
|
|
|
137
|
-
<script src="/builders/shell.js?v=2026-09-26-
|
|
137
|
+
<script src="/builders/shell.js?v=2026-09-26-swupdate"></script>
|
|
138
138
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
139
139
|
<script src="/builders/palette.js" defer></script>
|
|
140
140
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
|
|
53
53
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
54
54
|
|
|
55
|
-
<script src="/builders/shell.js?v=2026-09-26-
|
|
55
|
+
<script src="/builders/shell.js?v=2026-09-26-swupdate"></script>
|
|
56
56
|
<script src="/builders/palette.js" defer></script>
|
|
57
57
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib" defer></script>
|
|
58
58
|
<!-- task-detail.js still owns the #/idea/N and #/blocker/N overlay, so a ref
|
|
@@ -225,7 +225,7 @@
|
|
|
225
225
|
|
|
226
226
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
227
227
|
|
|
228
|
-
<script src="/builders/shell.js?v=2026-09-26-
|
|
228
|
+
<script src="/builders/shell.js?v=2026-09-26-swupdate"></script>
|
|
229
229
|
<script src="/builders/palette.js" defer></script>
|
|
230
230
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
231
231
|
<!-- task 1003069 (R08) — the quick-filing form's routed result links a task
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
<script src="/builders/brand-apply.js?v=2026-06-27-r75" defer></script>
|
|
103
103
|
<!-- The app shell (sidebar + top bar + view router). Dependency-free; loaded
|
|
104
104
|
BEFORE builders.js so the chrome ids exist when events are wired. -->
|
|
105
|
-
<script src="/builders/shell.js?v=2026-09-26-
|
|
105
|
+
<script src="/builders/shell.js?v=2026-09-26-swupdate" defer></script>
|
|
106
106
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
107
107
|
<script src="/builders/palette.js" defer></script>
|
|
108
108
|
<!-- task 2166: the shared component kit (window.OTBKit) — pager/ledger/rail.
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
|
|
81
81
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
82
82
|
|
|
83
|
-
<script src="/builders/shell.js?v=2026-09-26-
|
|
83
|
+
<script src="/builders/shell.js?v=2026-09-26-swupdate"></script>
|
|
84
84
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
85
85
|
<script src="/builders/palette.js" defer></script>
|
|
86
86
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
|
|
67
67
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
68
68
|
chrome exists before the page script runs. -->
|
|
69
|
-
<script src="/builders/shell.js?v=2026-09-26-
|
|
69
|
+
<script src="/builders/shell.js?v=2026-09-26-swupdate"></script>
|
|
70
70
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
71
71
|
<script src="/builders/palette.js" defer></script>
|
|
72
72
|
<!-- shared frontend toolkit (window.OTB — rank labels, number formats) + the
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
|
|
116
116
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
117
117
|
|
|
118
|
-
<script src="/builders/shell.js?v=2026-09-26-
|
|
118
|
+
<script src="/builders/shell.js?v=2026-09-26-swupdate"></script>
|
|
119
119
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
120
120
|
<script src="/builders/palette.js" defer></script>
|
|
121
121
|
<!-- the shared markdown reader (window.OTBMdReader) — primer.js renders through
|
|
@@ -187,7 +187,7 @@
|
|
|
187
187
|
|
|
188
188
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
189
189
|
chrome exists before the page script runs. -->
|
|
190
|
-
<script src="/builders/shell.js?v=2026-09-26-
|
|
190
|
+
<script src="/builders/shell.js?v=2026-09-26-swupdate"></script>
|
|
191
191
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
192
192
|
<script src="/builders/palette.js" defer></script>
|
|
193
193
|
<!-- shared frontend toolkit (window.OTB) — loaded before profile.js, which pulls from it. -->
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
|
|
59
59
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
60
60
|
|
|
61
|
-
<script src="/builders/shell.js?v=2026-09-26-
|
|
61
|
+
<script src="/builders/shell.js?v=2026-09-26-swupdate"></script>
|
|
62
62
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
63
63
|
<script src="/builders/palette.js" defer></script>
|
|
64
64
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
|
|
69
69
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
70
70
|
chrome exists before the page script runs. -->
|
|
71
|
-
<script src="/builders/shell.js?v=2026-09-26-
|
|
71
|
+
<script src="/builders/shell.js?v=2026-09-26-swupdate"></script>
|
|
72
72
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
73
73
|
<script src="/builders/palette.js" defer></script>
|
|
74
74
|
<!-- shared frontend toolkit (window.OTB) — loaded before ranks.js, which pulls from it. -->
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
43
43
|
|
|
44
|
-
<script src="/builders/shell.js?v=2026-09-26-
|
|
44
|
+
<script src="/builders/shell.js?v=2026-09-26-swupdate"></script>
|
|
45
45
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
46
46
|
<script src="/builders/palette.js" defer></script>
|
|
47
47
|
<script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
|
|
51
51
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
52
52
|
chrome exists before the page script runs. -->
|
|
53
|
-
<script src="/builders/shell.js?v=2026-09-26-
|
|
53
|
+
<script src="/builders/shell.js?v=2026-09-26-swupdate"></script>
|
|
54
54
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
55
55
|
<script src="/builders/palette.js" defer></script>
|
|
56
56
|
<!-- shared frontend toolkit (window.OTB — rank labels, number formats) + the
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
|
|
100
100
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
101
101
|
chrome exists before the page script runs. -->
|
|
102
|
-
<script src="/builders/shell.js?v=2026-09-26-
|
|
102
|
+
<script src="/builders/shell.js?v=2026-09-26-swupdate"></script>
|
|
103
103
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
104
104
|
<script src="/builders/palette.js" defer></script>
|
|
105
105
|
<!-- shared frontend toolkit (window.OTB) — loaded BEFORE sessions.js so it can pull from it. -->
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// modules/hall-ui/public/settings-software-update.js — Settings → Software update
|
|
2
|
+
// (task 1004296).
|
|
3
|
+
//
|
|
4
|
+
// The owner, 2026-09-25: "each builders hall settings has a version description where they
|
|
5
|
+
// can pull the latest release. Just like iOS does." So this panel says three things and
|
|
6
|
+
// offers one: the version this project runs; either "up to date" or the newest release its
|
|
7
|
+
// update channel lets it take; what each version in between brings — and, for the people
|
|
8
|
+
// the upgrade door lets through, an Update button.
|
|
9
|
+
//
|
|
10
|
+
// THE BUTTON LEADS TO THE DOOR; IT IS NOT THE DOOR. Taking an update is the deploy door's
|
|
11
|
+
// two steps (see exactly what moves, then confirm that version) with its health check and
|
|
12
|
+
// auto-rollback (ADR 0293), and on a hosted project that door is on the hub (ADR 0339 D3).
|
|
13
|
+
// A second copy of that flow here would be a second place for its safety rules to drift, so
|
|
14
|
+
// the server names where this project's door is (`door`) and the button goes there.
|
|
15
|
+
//
|
|
16
|
+
// THE PAGE KNOWS NOTHING (deploy.js's rule, kept). Every version here is the server's reading
|
|
17
|
+
// with the time it was taken. "Up to date" is drawn only from an ok reading with nothing
|
|
18
|
+
// offered; a reading that failed says so, and release notes that could not be read are
|
|
19
|
+
// reported as unread, never drawn as a version with no changes.
|
|
20
|
+
//
|
|
21
|
+
// READ WHEN ITS GROUP OPENS, not with the page: an update's notes come out of the newer
|
|
22
|
+
// package's download (src/bongos/package-registry.js), and most visits to Settings are for
|
|
23
|
+
// something else. settings.js announces each group switch as a `settings:group` event.
|
|
24
|
+
//
|
|
25
|
+
// Its own file, like the sessions and interaction panels: settings.js is against the
|
|
26
|
+
// 1500-line ratchet, and this panel shares nothing with it but the toolkit globals.
|
|
27
|
+
|
|
28
|
+
(() => {
|
|
29
|
+
'use strict';
|
|
30
|
+
|
|
31
|
+
const API = '/api/bongos';
|
|
32
|
+
const GROUP = 'update';
|
|
33
|
+
const { escapeHtml } = window.OTB;
|
|
34
|
+
const byId = (id) => document.getElementById(id);
|
|
35
|
+
const api = window.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false });
|
|
36
|
+
|
|
37
|
+
// Versions drawn open before "Show every version": enough to read the recent work
|
|
38
|
+
// without a project that is a hundred versions behind burying the button.
|
|
39
|
+
const VERSIONS_OPEN = 5;
|
|
40
|
+
|
|
41
|
+
let asked = false;
|
|
42
|
+
|
|
43
|
+
function ago(iso) {
|
|
44
|
+
const then = iso ? new Date(iso).getTime() : NaN;
|
|
45
|
+
if (!Number.isFinite(then)) return null;
|
|
46
|
+
const mins = Math.max(0, Math.round((Date.now() - then) / 60000));
|
|
47
|
+
if (mins < 1) return 'just now';
|
|
48
|
+
if (mins < 60) return `${mins} minute${mins === 1 ? '' : 's'} ago`;
|
|
49
|
+
const hours = Math.round(mins / 60);
|
|
50
|
+
if (hours < 48) return `${hours} hour${hours === 1 ? '' : 's'} ago`;
|
|
51
|
+
const days = Math.round(hours / 24);
|
|
52
|
+
return `${days} day${days === 1 ? '' : 's'} ago`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const mono = (v) => `<span class="su-ver">${escapeHtml(String(v))}</span>`;
|
|
56
|
+
// Voice rule V4 (docs/voice.md): a dash is banned in QUOTED data — and a release-note line
|
|
57
|
+
// is a task's ship summary, handed over by the ledger — so it becomes a comma at render,
|
|
58
|
+
// the way goal-inbox.js and the public feed's cleanTitle() do. Never by editing the data.
|
|
59
|
+
const unDash = (raw) => String(raw == null ? '' : raw).replace(/\s*[—–]\s*/g, ', ').replace(/,\s*,/g, ',').trim();
|
|
60
|
+
|
|
61
|
+
function row(label, desc, ctl) {
|
|
62
|
+
return `<div class="prow">
|
|
63
|
+
<div class="prow__main"><span class="prow__label">${label}</span>${desc ? `<span class="prow__desc">${desc}</span>` : ''}</div>
|
|
64
|
+
${ctl ? `<div class="prow__ctl">${ctl}</div>` : ''}
|
|
65
|
+
</div>`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Where the Update button goes, and what it says it will do. Only a plain same-origin path
|
|
69
|
+
// or an http(s) URL is ever put in an href; anything else draws no button.
|
|
70
|
+
function doorHtml(data, offered) {
|
|
71
|
+
const door = data.door || {};
|
|
72
|
+
if (!data.can_update) {
|
|
73
|
+
return '<p class="panel-hint su-door">Only the people allowed to upgrade this project see an Update button here.</p>';
|
|
74
|
+
}
|
|
75
|
+
if (door.kind === 'cli') {
|
|
76
|
+
return '<p class="panel-hint su-door">This project is upgraded on the server that runs it: <code>bongos upgrade</code> there takes this version, with the same health check and rollback.</p>';
|
|
77
|
+
}
|
|
78
|
+
const url = String(door.url || '');
|
|
79
|
+
const sameOrigin = /^\/[a-z0-9/_-]*$/i.test(url);
|
|
80
|
+
if (!sameOrigin && !/^https?:\/\/[^\s"'<>]+$/i.test(url)) return '';
|
|
81
|
+
let where = '';
|
|
82
|
+
if (!sameOrigin) {
|
|
83
|
+
try { where = ` on ${new URL(url).host}`; } catch (e) { return ''; }
|
|
84
|
+
}
|
|
85
|
+
return `<div class="row-end su-door">
|
|
86
|
+
<a class="pbtn pbtn--save" id="su-update" href="${escapeHtml(url)}">Update to ${escapeHtml(offered)}${sameOrigin ? '' : ' ↗'}</a>
|
|
87
|
+
<span class="panel-hint">Opens this project's upgrade${escapeHtml(where)}: you see what would move, confirm it, and it installs, checks itself, and rolls back if the project does not come back healthy.</span>
|
|
88
|
+
</div>`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// One entry: its text, and — only in the hall whose ledger the ids belong to, which is
|
|
92
|
+
// the one that runs the door itself — a link to that task.
|
|
93
|
+
function entryHtml(e, linkTasks) {
|
|
94
|
+
const text = escapeHtml(unDash(e.text));
|
|
95
|
+
return linkTasks && e.id ? `<li><a href="/task/${escapeHtml(e.id)}">${text}</a></li>` : `<li>${text}</li>`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function versionHtml(v, linkTasks, notesRead) {
|
|
99
|
+
const when = ago(v.published_at);
|
|
100
|
+
let body;
|
|
101
|
+
if (!notesRead) body = '<p class="prow__desc">Its notes could not be read.</p>';
|
|
102
|
+
else if (v.entries === null) body = '<p class="prow__desc">No notes were packed for this version.</p>';
|
|
103
|
+
else if (!v.entries.length) body = '<p class="prow__desc">No changes are listed for this version.</p>';
|
|
104
|
+
else body = `<ul class="su-entries">${v.entries.map((e) => entryHtml(e, linkTasks)).join('')}</ul>`;
|
|
105
|
+
return `<li class="su-version">
|
|
106
|
+
<span class="prow__label">${mono(v.version)}${when ? `<span class="prow__tag">published ${escapeHtml(when)}</span>` : ''}</span>
|
|
107
|
+
${body}
|
|
108
|
+
</li>`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function whatsNewHtml(w, linkTasks) {
|
|
112
|
+
if (!w) return '';
|
|
113
|
+
const n = w.count;
|
|
114
|
+
const head = `<h3 class="su-h" id="su-new-h">What's new in ${n === 1 ? 'this version' : `these ${escapeHtml(String(n))} versions`}</h3>`;
|
|
115
|
+
const warn = w.ok ? '' : `<p class="panel-status panel-status--danger" role="status">The release notes could not be read: ${escapeHtml(w.error || 'unknown error')}. The versions are still listed.</p>`;
|
|
116
|
+
const items = w.versions.map((v, i) => versionHtml(v, linkTasks, w.ok).replace('<li class="su-version">',
|
|
117
|
+
i < VERSIONS_OPEN ? '<li class="su-version">' : '<li class="su-version" hidden>'));
|
|
118
|
+
const hiddenCount = Math.max(0, w.versions.length - VERSIONS_OPEN);
|
|
119
|
+
const more = hiddenCount ? `<button type="button" class="pbtn pbtn--quiet" id="su-more">Show ${escapeHtml(String(hiddenCount))} more version${hiddenCount === 1 ? '' : 's'}</button>` : '';
|
|
120
|
+
const beyond = w.more ? `<p class="panel-hint">…and ${escapeHtml(String(w.more))} older version${w.more === 1 ? '' : 's'}, also in this update.</p>` : '';
|
|
121
|
+
return `${head}${warn}<ol class="su-versions" aria-labelledby="su-new-h">${items.join('')}</ol>${more ? `<div class="row-end">${more}</div>` : ''}${beyond}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function render(data) {
|
|
125
|
+
const body = byId('su-body');
|
|
126
|
+
const note = byId('su-note');
|
|
127
|
+
if (!body) return;
|
|
128
|
+
const u = (data && data.update) || {};
|
|
129
|
+
const checked = ago(u.checked_at);
|
|
130
|
+
const notes = [];
|
|
131
|
+
if (checked) notes.push(`Checked ${checked}.`);
|
|
132
|
+
|
|
133
|
+
if (u.reading !== 'ok') {
|
|
134
|
+
body.innerHTML = row('This project runs', u.running ? mono(u.running) : 'a version it could not name')
|
|
135
|
+
+ `<p class="panel-status panel-status--danger" role="status">Could not check for a newer version: ${escapeHtml(u.error || 'no answer')}.</p>`;
|
|
136
|
+
if (note) note.textContent = notes.join(' ');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let html = row('This project runs', mono(u.running));
|
|
141
|
+
if (!u.offered) {
|
|
142
|
+
html += row('Newest for this project', `${mono(u.running)} — this project is up to date.`, '<span class="su-pill su-pill--ok">Up to date</span>');
|
|
143
|
+
} else {
|
|
144
|
+
const when = ago(u.offered_published_at);
|
|
145
|
+
html += row('Newest for this project', `${mono(u.offered)}${when ? ` — published ${escapeHtml(when)}` : ''}`, '<span class="su-pill">Update ready</span>');
|
|
146
|
+
html += doorHtml(data, u.offered);
|
|
147
|
+
html += whatsNewHtml(u.whats_new, (data.door || {}).kind === 'local');
|
|
148
|
+
}
|
|
149
|
+
// A newer line this project's channel does not take is a fact worth one sentence — it
|
|
150
|
+
// explains why the newest number on the registry is not the one offered.
|
|
151
|
+
if (u.newest && u.newest !== (u.offered || u.running) && String(u.newest).split('.')[1] !== String(u.running).split('.')[1]) {
|
|
152
|
+
notes.push(`${u.newest} is also published; it starts a new line, and this project takes updates within ${String(u.running).split('.').slice(0, 2).join('.')} on its own.`);
|
|
153
|
+
}
|
|
154
|
+
body.innerHTML = html;
|
|
155
|
+
if (note) note.textContent = notes.join(' ');
|
|
156
|
+
|
|
157
|
+
const more = byId('su-more');
|
|
158
|
+
if (more) {
|
|
159
|
+
more.addEventListener('click', () => {
|
|
160
|
+
for (const li of body.querySelectorAll('.su-version[hidden]')) li.hidden = false;
|
|
161
|
+
more.parentNode.remove();
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function load() {
|
|
167
|
+
if (asked) return;
|
|
168
|
+
asked = true;
|
|
169
|
+
const body = byId('su-body');
|
|
170
|
+
if (body) body.innerHTML = '<p class="panel-hint">Checking for a newer version…</p>';
|
|
171
|
+
try {
|
|
172
|
+
const res = await api.request('GET', `${API}/software-update`);
|
|
173
|
+
if (res.status === 401) { window.location.assign(`${API}/auth/web/start`); return; }
|
|
174
|
+
if (!res.ok || !res.data) throw new Error((res.data && res.data.error && res.data.error.message) || `HTTP ${res.status}`);
|
|
175
|
+
render(res.data);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
// Its own failure, like every section here: this panel says it could not check, and
|
|
178
|
+
// the rest of Settings is untouched. Asking again is allowed.
|
|
179
|
+
asked = false;
|
|
180
|
+
if (body) body.innerHTML = `<p class="panel-status panel-status--danger" role="status">Could not check for updates: ${escapeHtml(err.message || 'unknown error')}.</p>`;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function boot() {
|
|
185
|
+
const section = byId('software-update-scroll');
|
|
186
|
+
if (!section) return;
|
|
187
|
+
section.hidden = false;
|
|
188
|
+
const groups = byId('settings-groups');
|
|
189
|
+
if (groups && groups.dataset && groups.dataset.active === GROUP) load();
|
|
190
|
+
document.addEventListener('settings:group', (e) => { if (e && e.detail && e.detail.group === GROUP) load(); });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
boot();
|
|
194
|
+
})();
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
.settings-groups[data-active="access"] > [data-group="access"],
|
|
27
27
|
.settings-groups[data-active="preferences"] > [data-group="preferences"],
|
|
28
28
|
.settings-groups[data-active="sound"] > [data-group="sound"],
|
|
29
|
+
.settings-groups[data-active="update"] > [data-group="update"],
|
|
29
30
|
.settings-groups[data-active="extensions"] > [data-group="extensions"] { display: block; }
|
|
30
31
|
.settings-groups > [data-group][hidden] { display: none !important; }
|
|
31
32
|
.settings-groups .scroll { margin: 0 0 var(--sp-sm); }
|
|
@@ -212,3 +213,41 @@ select.voices__select { min-width: 140px; }
|
|
|
212
213
|
font-family: inherit;
|
|
213
214
|
opacity: 0.92;
|
|
214
215
|
}
|
|
216
|
+
|
|
217
|
+
/* ---- Software update (task 1004296) -------------------------------------- */
|
|
218
|
+
/* A version is read digit by digit and compared against another, so it takes the mono
|
|
219
|
+
face. The state pill borrows project-settings' shape: a word in its own colour with a
|
|
220
|
+
hairline of the same, so "up to date" and "update ready" differ by more than hue. */
|
|
221
|
+
.su-ver { font-family: var(--font-mono); font-size: 13.5px; color: var(--ink); }
|
|
222
|
+
.su-pill {
|
|
223
|
+
display: inline-block;
|
|
224
|
+
padding: 1px 8px;
|
|
225
|
+
border: var(--line) solid currentColor;
|
|
226
|
+
border-radius: var(--radius-pill);
|
|
227
|
+
font-size: 11.5px;
|
|
228
|
+
font-weight: 600;
|
|
229
|
+
white-space: nowrap;
|
|
230
|
+
color: var(--accent-ink);
|
|
231
|
+
}
|
|
232
|
+
.su-pill--ok { color: var(--ok); }
|
|
233
|
+
.su-door { margin-top: var(--sp-sm); }
|
|
234
|
+
.su-door .panel-hint { flex: 1 1 18rem; min-width: 0; }
|
|
235
|
+
.su-h { margin: var(--sp-md) 0 0; font-size: 14.5px; font-weight: 600; color: var(--ink); }
|
|
236
|
+
.su-versions { list-style: none; margin: var(--sp-xs) 0 0; padding: 0; }
|
|
237
|
+
.su-version { padding: var(--sp-sm) 0; border-top: var(--line) solid var(--rule-soft); }
|
|
238
|
+
.su-version > .prow__label { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px; }
|
|
239
|
+
.su-entries { margin: var(--sp-hair) 0 0; padding-left: 1.15rem; max-width: var(--measure); font-size: 13.5px; color: var(--ink-soft); }
|
|
240
|
+
.su-entries li + li { margin-top: 2px; }
|
|
241
|
+
/* a task title is a link, and a link owes the 24px floor. Block, so a two-line note keeps
|
|
242
|
+
its bullet on its first line; the soft ink with an underline, because on the platform
|
|
243
|
+
every note is a link and a list of accent-coloured sentences reads as a warning. */
|
|
244
|
+
.su-entries a {
|
|
245
|
+
display: block;
|
|
246
|
+
min-height: 24px;
|
|
247
|
+
padding: 2px 0;
|
|
248
|
+
color: var(--ink-soft);
|
|
249
|
+
text-decoration: underline;
|
|
250
|
+
text-decoration-color: color-mix(in srgb, var(--ink-soft) 45%, transparent);
|
|
251
|
+
text-underline-offset: 3px;
|
|
252
|
+
}
|
|
253
|
+
.su-entries a:hover { color: var(--ink); text-decoration-color: currentColor; }
|
|
@@ -395,6 +395,26 @@
|
|
|
395
395
|
<p class="section-note">Changes save at once. Your new choice plays the next time that event fires.</p>
|
|
396
396
|
</section>
|
|
397
397
|
|
|
398
|
+
<!-- ============ group: update ============ -->
|
|
399
|
+
|
|
400
|
+
<!-- SOFTWARE UPDATE — this project's version, the newest release its update
|
|
401
|
+
channel allows, and what the versions between them carry (task 1004296).
|
|
402
|
+
Like a phone's Software Update screen: every builder reads it; the Update
|
|
403
|
+
button is drawn only for those the upgrade door lets through, and it LEADS
|
|
404
|
+
to that door (the two-step confirm and auto-rollback of ADR 0293) — the
|
|
405
|
+
act never happens on this page. Rendered by settings-software-update.js
|
|
406
|
+
from GET /api/bongos/software-update, read only when this group opens:
|
|
407
|
+
an update's notes come out of a package download. -->
|
|
408
|
+
<section class="scroll" id="software-update-scroll" aria-labelledby="software-update-h" data-group="update" hidden>
|
|
409
|
+
<h2 id="software-update-h" class="scroll__h">Software update</h2>
|
|
410
|
+
<p class="scroll__lede">
|
|
411
|
+
Which version of Cloud Bongos this project runs, whether a newer one is
|
|
412
|
+
ready for it, and what each newer version brings.
|
|
413
|
+
</p>
|
|
414
|
+
<div id="su-body" aria-live="polite"></div>
|
|
415
|
+
<p class="section-note" id="su-note"></p>
|
|
416
|
+
</section>
|
|
417
|
+
|
|
398
418
|
<!-- ============ group: extensions ============ -->
|
|
399
419
|
|
|
400
420
|
<!-- Module-contributed panels (BV1.R42, ADR 0083). settings.js appends a
|
|
@@ -416,7 +436,7 @@
|
|
|
416
436
|
|
|
417
437
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
418
438
|
chrome exists before the page scripts run. -->
|
|
419
|
-
<script src="/builders/shell.js?v=2026-09-26-
|
|
439
|
+
<script src="/builders/shell.js?v=2026-09-26-swupdate"></script>
|
|
420
440
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
421
441
|
<script src="/builders/palette.js" defer></script>
|
|
422
442
|
<!-- shared frontend toolkit (window.OTB) — loaded before consuming IIFEs. -->
|
|
@@ -426,7 +446,7 @@
|
|
|
426
446
|
<!-- shared record overlay (#/idea|blocker/N) — every hall page carries it; this
|
|
427
447
|
page was the one gap (task 2172). -->
|
|
428
448
|
<script src="/builders/task-detail.js?v=2026-07-03-taskdetail"></script>
|
|
429
|
-
<script src="/builders/settings.js?v=2026-09-
|
|
449
|
+
<script src="/builders/settings.js?v=2026-09-26-update"></script>
|
|
430
450
|
<!-- task 1003341: the Active sessions panel. Its own file rather than more of
|
|
431
451
|
settings.js, which sits close to the 1500-line size ratchet. -->
|
|
432
452
|
<script src="/builders/settings-sessions.js?v=2026-08-29-sessions"></script>
|
|
@@ -439,5 +459,7 @@
|
|
|
439
459
|
the panel grew from one switch to a rung picker with a derived default, and
|
|
440
460
|
settings.js is against the 1500-line ratchet. -->
|
|
441
461
|
<script src="/builders/settings-render.js?v=2026-09-16-render-levels"></script>
|
|
462
|
+
<!-- task 1004296: Settings → Software update. Own file on the same precedent. -->
|
|
463
|
+
<script src="/builders/settings-software-update.js?v=2026-09-26-update"></script>
|
|
442
464
|
</body>
|
|
443
465
|
</html>
|
|
@@ -1207,6 +1207,8 @@
|
|
|
1207
1207
|
...(window.OTB.moduleOn('dev-box') ? [{ id: 'devbox', label: 'Dev box' }] : []),
|
|
1208
1208
|
{ id: 'preferences', label: 'Preferences' },
|
|
1209
1209
|
{ id: 'sound', label: 'Sound' },
|
|
1210
|
+
// task 1004296 — every project's version and its update; settings-software-update.js.
|
|
1211
|
+
{ id: 'update', label: 'Software update' },
|
|
1210
1212
|
];
|
|
1211
1213
|
const RAIL_GROUPS = RAIL_ITEMS.map((i) => i.id).concat('extensions');
|
|
1212
1214
|
let rail = null;
|
|
@@ -1230,6 +1232,7 @@
|
|
|
1230
1232
|
'voices': 'voices-scroll',
|
|
1231
1233
|
'sounds': 'sounds-scroll',
|
|
1232
1234
|
'event-sounds': 'event-sounds-scroll',
|
|
1235
|
+
'software-update': 'software-update-scroll',
|
|
1233
1236
|
};
|
|
1234
1237
|
|
|
1235
1238
|
function activateGroup(group, opts) {
|
|
@@ -1238,6 +1241,8 @@
|
|
|
1238
1241
|
if (!groups) return;
|
|
1239
1242
|
groups.dataset.active = group;
|
|
1240
1243
|
rail?.activate(group);
|
|
1244
|
+
// A panel whose read is expensive loads when its group opens, not with the page.
|
|
1245
|
+
if (typeof CustomEvent === 'function') document.dispatchEvent(new CustomEvent('settings:group', { detail: { group } }));
|
|
1241
1246
|
if (!opts || opts.scroll !== false) window.scrollTo({ top: 0 });
|
|
1242
1247
|
}
|
|
1243
1248
|
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"account": { "auth": true, "actions": [["wait", 900]], "expect": { "visible": ["#settings-h1", "#display-name-input", "#craft-chips", "#privacy-rows"] } },
|
|
11
11
|
"access": { "auth": true, "url": "/settings#access", "actions": [["wait", 900]], "expect": { "visible": ["#cli-reissue-btn", "#sessions-list", "#artkey-card"] } },
|
|
12
12
|
"preferences": { "auth": true, "url": "/settings#preferences", "actions": [["wait", 900]], "expect": { "visible": ["#wander-rows", "#render-rows"] } },
|
|
13
|
-
"sound": { "auth": true, "url": "/settings#sound", "actions": [["wait", 900]], "expect": { "visible": ["#voices-table", "#sound-rows", "#event-sound-rows"] } }
|
|
13
|
+
"sound": { "auth": true, "url": "/settings#sound", "actions": [["wait", 900]], "expect": { "visible": ["#voices-table", "#sound-rows", "#event-sound-rows"] } },
|
|
14
|
+
"update": { "auth": true, "url": "/settings#software-update", "actions": [["wait", 1200]], "expect": { "visible": ["#software-update-h", "#su-body", "#su-update", ".su-versions"] } }
|
|
14
15
|
},
|
|
15
16
|
"reducedMotion": { "auth": true, "actions": [["wait", 900]] }
|
|
16
17
|
}
|
|
@@ -347,8 +347,9 @@
|
|
|
347
347
|
//
|
|
348
348
|
// THE DOOR IS THE PROVISIONING MODULE'S, and a hosted project does not run it, so
|
|
349
349
|
// there this link opened a page with nothing behind it (task 1004174). It is
|
|
350
|
-
// dropped where the module is off
|
|
351
|
-
//
|
|
350
|
+
// dropped where the module is off — and since task 1004296 it STAYS dropped: /deploy
|
|
351
|
+
// is the platform hall's page alone, and every other hall takes its update from
|
|
352
|
+
// Settings → Software update, whose Update button carries the hub hand-off.
|
|
352
353
|
{ id: 'deploy', label: 'Deploy', icon: 'admin', href: '/deploy', gate: 'archon', module: 'provisioning' },
|
|
353
354
|
// task 1001949: what each hosted project costs. 'government' mirrors the metic floor
|
|
354
355
|
// of `provisioning.fleet.manage`, the atom its page and its one read check (the
|
|
@@ -628,39 +629,22 @@
|
|
|
628
629
|
coreUpdateAsked = true;
|
|
629
630
|
api.request('GET', '/api/bongos/core-update').then(function (res) {
|
|
630
631
|
if (!res || res.status !== 200 || !res.data) return;
|
|
631
|
-
|
|
632
|
-
if (handoff) pointDeployAtHub(handoff);
|
|
633
|
-
if (res.data.update) renderCoreUpdateBanner(res.data.update, handoff);
|
|
632
|
+
if (res.data.update) renderCoreUpdateBanner(res.data.update);
|
|
634
633
|
}, function () { /* fail-soft: no banner is the correct answer to no answer */ });
|
|
635
634
|
}
|
|
636
635
|
|
|
637
|
-
//
|
|
638
|
-
//
|
|
639
|
-
//
|
|
640
|
-
//
|
|
641
|
-
//
|
|
642
|
-
|
|
643
|
-
function hubHandoff(url) {
|
|
644
|
-
if (moduleOn('provisioning')) return null;
|
|
645
|
-
if (typeof url !== 'string' || !/^https?:\/\/[^\s"'<>]+$/i.test(url)) return null;
|
|
646
|
-
return url;
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
// The rail's Deploy item is hidden on a hosted project (its module gate); this gives
|
|
650
|
-
// it back, aimed at the hub. Its rank gate still applies, exactly as on the platform.
|
|
651
|
-
function pointDeployAtHub(url) {
|
|
652
|
-
var item = document.getElementById('nav-deploy');
|
|
653
|
-
if (!item) return;
|
|
654
|
-
item.setAttribute('href', url);
|
|
655
|
-
if (item.dataset) delete item.dataset.module;
|
|
656
|
-
item.hidden = !gateAllows(item.dataset && item.dataset.gate);
|
|
657
|
-
}
|
|
636
|
+
// Where the banner's Upgrade goes (task 1004296). The platform's hall runs the deploy
|
|
637
|
+
// door itself, so there it is /deploy. Every other hall — a hosted project, a
|
|
638
|
+
// self-hosted instance — takes its update from Settings → Software update, which says
|
|
639
|
+
// what the update carries and, on a hosted project, hands the owner to the hub's door
|
|
640
|
+
// for this project (ADR 0339 D3). The banner no longer links off-site itself.
|
|
641
|
+
var SOFTWARE_UPDATE_HREF = '/settings#software-update';
|
|
658
642
|
|
|
659
643
|
// SAY NOTHING WHEN THERE IS NOTHING TO SAY. `behind` is false both when the box
|
|
660
644
|
// is current AND when the registry could not be read; `reading` separates them,
|
|
661
645
|
// and neither renders. A banner is a claim about being out of date, and a wrong
|
|
662
646
|
// one on a project that is actually current is worse than silence.
|
|
663
|
-
function renderCoreUpdateBanner(update
|
|
647
|
+
function renderCoreUpdateBanner(update) {
|
|
664
648
|
if (!update || update.reading !== 'ok' || !update.behind || !update.latest) return;
|
|
665
649
|
// A dismissal is remembered PER VERSION, not forever: the next release is new
|
|
666
650
|
// news and must re-announce itself, or the surface is dead after one click.
|
|
@@ -676,25 +660,27 @@
|
|
|
676
660
|
el.className = 'core-update-notice';
|
|
677
661
|
el.id = 'core-update-notice';
|
|
678
662
|
el.setAttribute('role', 'status');
|
|
679
|
-
// The action
|
|
680
|
-
//
|
|
681
|
-
//
|
|
682
|
-
//
|
|
683
|
-
// would 404. Task 1004046 is what gives those halls a real action.
|
|
663
|
+
// The action goes where the update can actually be taken. The deploy door is the
|
|
664
|
+
// provisioning module's, and a hosted project does NOT run that module (probed:
|
|
665
|
+
// /api/bongos/provisioning/health is 404 on a tenant, 200 on the platform) — so on
|
|
666
|
+
// those it goes to Settings → Software update (SOFTWARE_UPDATE_HREF above).
|
|
684
667
|
// NOT A LINK TO THE PAGE YOU ARE ALREADY ON (task 1004145). This reads as the action
|
|
685
668
|
// button — it is labelled with the verb and styled like one — but it is a plain
|
|
686
669
|
// navigation to /deploy. On /deploy itself that is a no-op, and the owner who
|
|
687
670
|
// pressed it there reasonably reported "I clicked update, but I didn't see anything
|
|
688
671
|
// happen": nothing had, and nothing had been asked for. The control is on that page
|
|
689
672
|
// already, so on it the banner informs and points, and does not pretend to act.
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
var
|
|
694
|
-
|
|
695
|
-
|
|
673
|
+
// The same rule on Settings → Software update: that panel IS the control there.
|
|
674
|
+
var here = window.location ? String(window.location.pathname || '').replace(/\/+$/, '') : '';
|
|
675
|
+
var onDeploy = here === '/deploy';
|
|
676
|
+
var onUpdatePanel = here === '/settings' && String(window.location.hash || '') === '#software-update';
|
|
677
|
+
var action = moduleOn('provisioning')
|
|
678
|
+
? (onDeploy
|
|
696
679
|
? '<span class="core-update-notice__hint">Use the control below to take it.</span>'
|
|
697
|
-
: '<a class="core-update-notice__action" href="/deploy">Upgrade</a>')
|
|
680
|
+
: '<a class="core-update-notice__action" href="/deploy">Upgrade</a>')
|
|
681
|
+
: (onUpdatePanel
|
|
682
|
+
? '<span class="core-update-notice__hint">Its details are below.</span>'
|
|
683
|
+
: '<a class="core-update-notice__action" href="' + SOFTWARE_UPDATE_HREF + '">Upgrade</a>');
|
|
698
684
|
el.innerHTML =
|
|
699
685
|
'<span>Core <strong>' + esc(update.latest) + '</strong> is available — this project runs ' +
|
|
700
686
|
esc(update.running || 'an unknown version') + '.</span>' +
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
|
|
67
67
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
68
68
|
|
|
69
|
-
<script src="/builders/shell.js?v=2026-09-26-
|
|
69
|
+
<script src="/builders/shell.js?v=2026-09-26-swupdate"></script>
|
|
70
70
|
<script src="/builders/palette.js" defer></script>
|
|
71
71
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
72
72
|
<!-- The kit, for its ONE renderer: the full-size ship-time visual figure
|