@bongos/core 1.19.1041 → 1.19.1043
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 +127 -87
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +6 -0
- package/clients/bongos-client/index.cjs +6 -0
- package/clients/bongos-client/index.d.ts +8 -1
- package/clients/bongos-client/index.mjs +6 -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 +109 -7
- package/docs/api-reference.md +9 -2
- package/docs/copy-inventory.md +79 -59
- package/docs/copy-registry.json +248 -68
- package/docs/file-map.md +1 -1
- package/docs/module-api-changelog.md +12 -0
- package/docs/modules-contract.md +4 -1
- 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/style.css +4 -0
- package/modules/hall-ui/public/task.css +6 -0
- package/modules/hall-ui/public/task.html +2 -2
- package/modules/hall-ui/public/task.js +17 -0
- 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/hall-ui/records/record-family.md +2 -0
- package/modules/npm-release/module.json +8 -2
- package/modules/npm-release/public/task-widget.js +79 -0
- package/modules/npm-release/public/work.js +8 -3
- package/modules/npm-release/routes/task-where.js +48 -0
- package/modules/npm-release/work.js +78 -11
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +16 -0
- package/scripts/gds/exec-path-guard.js +1 -1
- package/scripts/gds/fitness.js +7 -0
- package/scripts/gds/main-audit.js +204 -7
- package/scripts/gds/seam-wiring-guard.js +4 -2
- package/scripts/hall-preview/server.js +14 -1
- 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 +35 -4
- package/src/bongos/software-update.js +127 -0
- package/src/module-api.js +10 -1
- package/src/module-loader/loader.js +13 -3
- package/tests/core_upgrade_owner_door.mjs +27 -18
- package/tests/hall_record_world.mjs +23 -1
- package/tests/helpers.mjs +92 -0
- package/tests/main_audit_attestation.mjs +238 -0
- package/tests/module_api.mjs +1 -0
- package/tests/module_loader.mjs +27 -0
- package/tests/nav_permission_atoms.mjs +1 -1
- package/tests/npm_release_page.mjs +11 -1
- package/tests/npm_release_where.mjs +323 -0
- package/tests/npm_release_work.mjs +9 -94
- package/tests/software_update.mjs +449 -0
|
@@ -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
|
|
@@ -3151,6 +3151,10 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
|
|
|
3151
3151
|
min-width: 0;
|
|
3152
3152
|
max-width: 100%;
|
|
3153
3153
|
}
|
|
3154
|
+
/* The 24px floor (task 1004301 found it failing): at 13px the base field's padding left a
|
|
3155
|
+
number input 21px tall beside a select that clears it — the task page's "Wait for task"
|
|
3156
|
+
box. Sized to sit level with its select. */
|
|
3157
|
+
.td-field input { min-height: 26px; box-sizing: border-box; }
|
|
3154
3158
|
.td-form .td-actions, .td-form-hint, .td-form .td-action-note { grid-column: 1 / -1; }
|
|
3155
3159
|
.td-form .td-actions { margin: 4px 0 0; }
|
|
3156
3160
|
.td-form-hint { font-size: 12.5px; color: var(--ink-soft); margin: 0; }
|
|
@@ -139,3 +139,9 @@
|
|
|
139
139
|
.tk-axis:nth-child(3) { border-left: 0; padding-left: 0; }
|
|
140
140
|
.tk-lrow { grid-template-columns: 1fr; gap: 6px; }
|
|
141
141
|
}
|
|
142
|
+
|
|
143
|
+
/* The module slot under the rail (task 1004301): a module's task-widget.js may write one
|
|
144
|
+
line of its own here (the npm-release module says which version carries the task).
|
|
145
|
+
Empty unless a module fills it, and then it takes no room at all. */
|
|
146
|
+
.tk-contrib { margin: var(--sp-md) 0 0; }
|
|
147
|
+
.tk-contrib:empty { display: none; }
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
|
|
58
58
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
59
59
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
60
|
-
<script src="/builders/shell.js?v=2026-09-26-
|
|
60
|
+
<script src="/builders/shell.js?v=2026-09-26-swupdate"></script>
|
|
61
61
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
62
62
|
<script src="/builders/palette.js" defer></script>
|
|
63
63
|
<!-- shared frontend toolkit (window.OTB) — loaded FIRST so task.js can pull from it. -->
|
|
@@ -69,6 +69,6 @@
|
|
|
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
71
|
<script src="/builders/hall-visual.js?v=2026-09-16-visual1" defer></script>
|
|
72
|
-
<script src="/builders/task.js?v=2026-
|
|
72
|
+
<script src="/builders/task.js?v=2026-09-26-taskwhere" defer></script>
|
|
73
73
|
</body>
|
|
74
74
|
</html>
|
|
@@ -634,6 +634,11 @@
|
|
|
634
634
|
['Priority', t.priority == null ? null : 'P' + t.priority],
|
|
635
635
|
]));
|
|
636
636
|
parts.push(lifecycleRail(t, creditVal));
|
|
637
|
+
// A slot a MODULE may fill (task 1004301): the module's public/task-widget.js is
|
|
638
|
+
// injected just before this script and writes into it on `otb:task-shown` below. The
|
|
639
|
+
// page itself names no module and draws nothing here — with none enabled it stays
|
|
640
|
+
// empty, and `:empty` in task.css collapses it.
|
|
641
|
+
parts.push('<div class="tk-contrib" id="task-contrib" aria-live="polite"></div>');
|
|
637
642
|
|
|
638
643
|
// Claim history (task 751). The most recent holder leads; since task 1003437
|
|
639
644
|
// the read also carries the FULL claim ledger (?include=claims), so a task
|
|
@@ -806,6 +811,7 @@
|
|
|
806
811
|
|
|
807
812
|
const body = $('#task-body');
|
|
808
813
|
if (body) body.innerHTML = parts.join('');
|
|
814
|
+
announceShown(t);
|
|
809
815
|
|
|
810
816
|
const copyBtn = $('#copy-prompt-btn');
|
|
811
817
|
if (copyBtn) {
|
|
@@ -958,6 +964,17 @@
|
|
|
958
964
|
// The two honest failures. Both keep the <h1> — the empty and error renders are
|
|
959
965
|
// renders like any other, and a page whose only heading lives on the happy path
|
|
960
966
|
// has none in the states a reader is most likely to hit.
|
|
967
|
+
// Tell any module widget which task is on screen (task 1004301). Fired after EVERY
|
|
968
|
+
// render, because render() rebuilds #task-body wholesale and the slot a widget filled a
|
|
969
|
+
// moment ago is gone. The last task shown is also left on window.OTBTaskShown for a
|
|
970
|
+
// widget that loads after the first announcement. Only the id and the status travel:
|
|
971
|
+
// a widget that needs more asks its own module's route, which applies its own gate.
|
|
972
|
+
function announceShown(t) {
|
|
973
|
+
const detail = { id: String(t.id), status: t.status || null };
|
|
974
|
+
window.OTBTaskShown = detail;
|
|
975
|
+
if (typeof CustomEvent === 'function') document.dispatchEvent(new CustomEvent('otb:task-shown', { detail }));
|
|
976
|
+
}
|
|
977
|
+
|
|
961
978
|
function renderNotFound(id) {
|
|
962
979
|
const body = $('#task-body');
|
|
963
980
|
if (body) body.innerHTML = `
|
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
|
|
163
163
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
164
164
|
chrome exists before the page script runs. -->
|
|
165
|
-
<script src="/builders/shell.js?v=2026-09-26-
|
|
165
|
+
<script src="/builders/shell.js?v=2026-09-26-swupdate"></script>
|
|
166
166
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
167
167
|
<script src="/builders/palette.js" defer></script>
|
|
168
168
|
<!-- shared frontend toolkit (window.OTB) — loaded before watch.js, which pulls from it. -->
|
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
|
|
172
172
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
173
173
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
174
|
-
<script src="/builders/shell.js?v=2026-09-26-
|
|
174
|
+
<script src="/builders/shell.js?v=2026-09-26-swupdate"></script>
|
|
175
175
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
176
176
|
<script src="/builders/palette.js" defer></script>
|
|
177
177
|
<!-- shared frontend toolkit (window.OTB) — loaded FIRST so work.js can pull from it. -->
|
|
@@ -17,3 +17,12 @@ Also fixed here, as the board task handed forward: the two sub-24px links on Hom
|
|
|
17
17
|
- **Honest empties, one line each.** Signed out (or a 401 mid-way) the grid is the same permission sentence the activity ledgers use; an empty record is "No works shipped yet"; a failed read says so. Never three empty cards.
|
|
18
18
|
- **The Works key, and its public fallback.** Works is the full server-paged ledger from `/builders/:id/activity?section=tasks` (any signed-in viewer, any builder); signed out, the page's PUBLIC `recent_ships` stand in with a sign-in line, so a stranger still sees the last few. The Overview no longer carries a works strip — that removes the duplicate the DONE-WHEN named.
|
|
19
19
|
- **A lookback control (owner request, task 1003489) scopes every chart** — 30 days / 90 days / 1 year / All time, one segmented row above the grid (the dataviz date-range-first rule). The cumulative charts clip their axis to the window and ENTER it at the running total, not at zero (`clipToWindow`); the craft mix counts within it; the pace chart passes the window to `/analytics/builder/:id?days=`. Remembered per viewer; changing it re-draws from the rows already in hand — no re-fetch. (A chart LIBRARY was floated by the owner and deliberately NOT taken here — the hall ships none, the hand-drawn charts satisfy the ask, and the swap is its own idea to weigh CSP/weight/theming/table-twin against.)
|
|
20
|
+
|
|
21
|
+
**Settings gained a sixth group, Software update (task 1004296)** — every project's running core, the newest release its update channel lets it take, and what each version in between brings, like a phone's Software Update screen. `settings-software-update.js` renders it from `GET /api/bongos/software-update`; the reading is `src/bongos/software-update.js` over the core's registry reader.
|
|
22
|
+
|
|
23
|
+
- **It is read when its group opens, not with the page.** An update's notes come out of the newer package's download, and most visits to Settings are for something else, so `activateGroup` in `settings.js` announces each switch as a `settings:group` event and the panel loads on its own group, once.
|
|
24
|
+
- **The Update button leads to the door; it is not the door.** The act is the deploy door's two steps and auto-rollback (ADR 0293): `/deploy` in the platform's hall, the hub's `/deploy?project=` for a hosted project (ADR 0339 D3), and a `bongos upgrade` sentence where there is no door at all. A second copy of that flow here would be a second place for its safety rules to drift. The button is drawn only when the server says the viewer holds `core.pin.move` — cosmetic, the door re-checks.
|
|
25
|
+
- **Everyone reads it.** The version and what's new are for every builder (the owner's ask); the route discloses nothing the public `/version` and npm do not already publish.
|
|
26
|
+
- **"Up to date" is said only from an answer.** A failed registry read, an unnamed running version and unread release notes each render as what they are; unread notes are never drawn as a version with no changes.
|
|
27
|
+
- **The notes are the soft ink, underlined, on the platform.** There every entry links to its task, and a list of accent-coloured sentences read as a warning. Elsewhere the ids belong to another project's ledger, so the entries are plain text.
|
|
28
|
+
- **Kit:** `settings.states.json` `update`, ALL CLEAN at 1440/390/320 × dark/light against the harness with the fixture `software-update.json` (a live reading for 1.19.1033). Tests: `tests/software_update.mjs`.
|
|
@@ -11,3 +11,5 @@
|
|
|
11
11
|
**The task record's grade panel, session log and claim ledger (task 1003437).** Two net-new regions the owner approved at the composition round (direction doc §6 decision 4) sit between Shipped and Actions on `task.html`. Four rules carry them: all three ride the ONE task read as opt-in siblings — `GET /tasks/:id?include=last_claim,grade,session_logs,claims`; the two new reads (`getTaskSessionLogs`, `getTaskClaims` in `modules/lifecycle/db-claim-reads.js`) are keyed by `task_id`, newest first, **rank never returned**; absent → **NOTHING** (the ship-visual precedent), except a grade row whose panel was `unavailable`, which is data and says so in one line; and verdicts and outcomes wear the fixed verification vocabulary through `.rec-pill--ok/warn/danger`, **never the accent**. Pinned in `tests/hall_record_world.mjs` (page) and `tests/task_detail_includes.mjs` (route + reads).
|
|
12
12
|
|
|
13
13
|
**The way into and out of a task record (task 1003468, ADR 0229).** Since 1759/1869 a to-do card's BODY toggles selection so a builder can bulk-claim — which is why the owner could not open a card, the only door being a 26px ⤢ glyph nobody found. Now **the title is the door**: `.task__link` on both surfaces that render the card (`work.js`, `goals-render.js`), bought up to the 24px floor with the `.scroll__lede a` recipe; both click handlers let it through; the glyph is gone. **The link carries where it was opened from** — `OTBTaskDetail.href(id)` appends `?from=<path+query+hash>` (a query param so the origin survives a reload and keeps a goal's `#/goal/N`, which a referrer drops) — and `task.js` resolves the exit once (`?from=` → same-origin referrer → the work board), points the crumb **before** the fetch so the way back exists in every state, and refuses anything that is not a path on this host. **Never write a bare `/task/N` link from a list again — call `OTBTaskDetail.href(id)`.** Pinned by §10 of `tests/hall_record_world.mjs` and §7 of `tests/hall_board_world.mjs`.
|
|
14
|
+
|
|
15
|
+
**The task page carries a module slot (task 1004301).** Under the lifecycle rail sits `#task-contrib`, empty and collapsed (`.tk-contrib:empty`) unless a module fills it, and `task.js` announces every render as `otb:task-shown` with `{ id, status }` (also left on `window.OTBTaskShown` for a late widget). The page names no module: the server injects each enabled module's `public/task-widget.js` just before `task.js` (the loader's uiSections seam, `hallWidgetScripts({ file: 'task-widget.js' })`), so both run deferred in order and the listener exists before the first announcement. Render rebuilds `#task-body` wholesale, which is why the announcement is per render, not per load. The first filler is npm-release's line — "In 1.19.1036 · running here · not released to other projects yet", or "Done, but not merged" for a stranded ship. Tests: `tests/hall_record_world.mjs` (slot + announcement), `tests/npm_release_where.mjs` (the widget and the seam).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"key": "npm-release",
|
|
3
3
|
"title": "npm release",
|
|
4
|
-
"description": "The deploy page for a project that publishes an npm package from its GitHub repo — where every recently shipped task has got to (not merged, merged, in a version, live, released) and each version's list of work, read from the release notes the package itself carries (task 1002622). Built for the Bongos hall, which publishes @bongos/core; off everywhere else.",
|
|
4
|
+
"description": "The deploy page for a project that publishes an npm package from its GitHub repo — where every recently shipped task has got to (not merged, merged, in a version, live, released) and each version's list of work, read from the release notes the package itself carries (task 1002622) — and the same line on a task's own page (task 1004301). Built for the Bongos hall, which publishes @bongos/core; off everywhere else.",
|
|
5
5
|
"coreVersion": "^1.19.0",
|
|
6
6
|
"default": false,
|
|
7
7
|
"author": "Cloud Bongos",
|
|
@@ -12,7 +12,13 @@
|
|
|
12
12
|
"status": "core-maintained"
|
|
13
13
|
},
|
|
14
14
|
"contributes": {
|
|
15
|
-
"routes": [
|
|
15
|
+
"routes": [
|
|
16
|
+
"work",
|
|
17
|
+
"task-where"
|
|
18
|
+
],
|
|
19
|
+
"uiSections": [
|
|
20
|
+
"task-where"
|
|
21
|
+
]
|
|
16
22
|
},
|
|
17
23
|
"provides": [],
|
|
18
24
|
"consumes": []
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// modules/npm-release/public/task-widget.js — the line a task's own page shows about where
|
|
2
|
+
// its code is (task 1004301): "In 1.19.1036 · running here · not released yet", or that a
|
|
3
|
+
// task marked done never reached main — which is how a stranded ship becomes visible to the
|
|
4
|
+
// person who shipped it.
|
|
5
|
+
//
|
|
6
|
+
// HOW IT GETS THERE. The core's task page names no module. It renders an empty slot
|
|
7
|
+
// (#task-contrib) and announces each render as `otb:task-shown`; the server injects this file
|
|
8
|
+
// just before task.js because the module declares a uiSection and ships task-widget.js
|
|
9
|
+
// (src/module-loader/loader.js hallWidgetScripts, file: 'task-widget.js'). Module off: this
|
|
10
|
+
// file is never injected, its asset 404s, and the slot stays empty.
|
|
11
|
+
//
|
|
12
|
+
// THE PAGE KNOWS NOTHING (deploy.js's rule, kept). The stage and version are the server's
|
|
13
|
+
// reading; a partial reading says so; any failure draws no line rather than a wrong one.
|
|
14
|
+
|
|
15
|
+
(() => {
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const API = '/api/bongos';
|
|
19
|
+
const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
|
20
|
+
const client = window.BongosClient && window.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false });
|
|
21
|
+
const DONE = ['completed', 'confirmed', 'shipped'];
|
|
22
|
+
|
|
23
|
+
// One reading per task per page load: the page re-renders on every action, and each
|
|
24
|
+
// render re-announces, but where the code is does not change between clicks. Bounded —
|
|
25
|
+
// a page shows one task, and the overlay can walk to a few more; nothing needs more than
|
|
26
|
+
// the last handful.
|
|
27
|
+
const cache = new Map();
|
|
28
|
+
const CACHE_MAX = 8;
|
|
29
|
+
|
|
30
|
+
// The line for each stage, in the deploy page's own words (its stage names), shortened to
|
|
31
|
+
// read as one sentence under a task's lifecycle rail.
|
|
32
|
+
const REST = {
|
|
33
|
+
unversioned: () => 'Merged, waiting for the next published version.',
|
|
34
|
+
not_live: (w) => `published, not running here yet${w.offered ? ' · already offered to other projects' : ''}.`,
|
|
35
|
+
live_unreleased: () => 'running here · not released to other projects yet.',
|
|
36
|
+
released: (w) => `${w.live ? 'running here · ' : ''}released to other projects.`,
|
|
37
|
+
// Said with the reason a builder would recognise, the way "Done, but not merged" is: this
|
|
38
|
+
// is most often work that changed no code, and only sometimes a merge that lost its name.
|
|
39
|
+
no_version: () => 'Shipped, though no published version lists it — usually a change that needed no code, like a scope edit or a check, and now and then a merge whose note did not name this task.',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function lineHtml(w) {
|
|
43
|
+
if (w.stage === 'not_merged') {
|
|
44
|
+
return '<p class="rec-meta nr-where nr-where--stranded"><b>Done, but not merged.</b> The ledger marks this task done, and its code is not on main yet — a ship that has not landed.</p>';
|
|
45
|
+
}
|
|
46
|
+
const rest = REST[w.stage];
|
|
47
|
+
if (!rest) return '';
|
|
48
|
+
const lead = w.version ? `In <b>${esc(w.version)}</b> · ` : '';
|
|
49
|
+
const partial = w.partial ? ' (a partial reading: the registry or the release notes could not be read)' : '';
|
|
50
|
+
return `<p class="rec-meta nr-where">${lead}${esc(rest(w) + partial)}</p>`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function reading(id) {
|
|
54
|
+
if (cache.has(id)) return cache.get(id);
|
|
55
|
+
const p = (async () => {
|
|
56
|
+
if (!client) return null;
|
|
57
|
+
const res = await client.request('GET', `${API}/npm-release/task/${encodeURIComponent(id)}`);
|
|
58
|
+
return res && res.status === 200 && res.data ? res.data : null;
|
|
59
|
+
})().catch(() => null);
|
|
60
|
+
cache.set(id, p);
|
|
61
|
+
while (cache.size > CACHE_MAX) cache.delete(cache.keys().next().value);
|
|
62
|
+
return p;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function show(detail) {
|
|
66
|
+
if (!detail || !/^\d{1,12}$/.test(String(detail.id || ''))) return;
|
|
67
|
+
if (!DONE.includes(detail.status)) return; // nothing to place until it is done
|
|
68
|
+
const w = await reading(String(detail.id));
|
|
69
|
+
// The page may have re-rendered while the read was in flight: write into the slot that
|
|
70
|
+
// exists NOW, and only for the task it is still showing.
|
|
71
|
+
const slot = document.getElementById('task-contrib');
|
|
72
|
+
const shown = window.OTBTaskShown;
|
|
73
|
+
if (!slot || !w || (shown && String(shown.id) !== String(detail.id))) return;
|
|
74
|
+
slot.innerHTML = lineHtml(w);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
document.addEventListener('otb:task-shown', (e) => { show(e && e.detail); });
|
|
78
|
+
if (window.OTBTaskShown) show(window.OTBTaskShown);
|
|
79
|
+
})();
|
|
@@ -64,8 +64,13 @@
|
|
|
64
64
|
|
|
65
65
|
// The title IS the link, the hall's own row idiom (oversight.css `a.ov-row__title`),
|
|
66
66
|
// which is what buys it the 24px floor and the row's ink rather than link colour.
|
|
67
|
+
// Voice rule V4 (docs/voice.md): a dash is banned in QUOTED data — and a task title and a
|
|
68
|
+
// release-note line are both handed over by the ledger — so it becomes a comma at render,
|
|
69
|
+
// the way goal-inbox.js and the public feed's cleanTitle() do. Never by editing the data.
|
|
70
|
+
const unDash = (raw) => String(raw == null ? '' : raw).replace(/\s*[—–]\s*/g, ', ').replace(/,\s*,/g, ',').trim();
|
|
71
|
+
|
|
67
72
|
function taskTitle(id, title) {
|
|
68
|
-
return `<a class="ov-row__title" href="/task/${encodeURIComponent(id)}">${escapeHtml(title || `Task ${id}`)}</a>`;
|
|
73
|
+
return `<a class="ov-row__title" href="/task/${encodeURIComponent(id)}">${escapeHtml(unDash(title) || `Task ${id}`)}</a>`;
|
|
69
74
|
}
|
|
70
75
|
|
|
71
76
|
function taskRow(t, { offeredInNote = false } = {}) {
|
|
@@ -151,8 +156,8 @@
|
|
|
151
156
|
const shown = expanded.has(key) ? v.entries : v.entries.slice(0, ENTRIES_SHOWN);
|
|
152
157
|
body = `<ul class="ov-rows">${shown.map((e) => `<li class="ov-row ov-row--dense">
|
|
153
158
|
<div class="ov-row__main">
|
|
154
|
-
${e.id ? taskTitle(e.id, e.title) : `<span class="ov-row__title">${escapeHtml(e.text)}</span>`}
|
|
155
|
-
${e.id ? `<p class="ov-row__sub">${escapeHtml(e.text)}</p>` : ''}
|
|
159
|
+
${e.id ? taskTitle(e.id, e.title) : `<span class="ov-row__title">${escapeHtml(unDash(e.text))}</span>`}
|
|
160
|
+
${e.id ? `<p class="ov-row__sub">${escapeHtml(unDash(e.text))}</p>` : ''}
|
|
156
161
|
</div>
|
|
157
162
|
</li>`).join('')}</ul>${moreButton(key, v.entries.length, shown.length)}`;
|
|
158
163
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// modules/npm-release/routes/task-where.js — GET /npm-release/task/:id, the one line a
|
|
4
|
+
// task's own page shows about where its code is (task 1004301): which version carries it,
|
|
5
|
+
// whether this hall runs that version, whether it is released — or that a task marked done
|
|
6
|
+
// never reached main. See readTaskWhere in ../work.js for the reading.
|
|
7
|
+
//
|
|
8
|
+
// ANY SIGNED-IN BUILDER, NOT THE DEPLOY PAGE'S ATOM. Every builder can open a task page, so
|
|
9
|
+
// the question the task asked was what this line may show them. It shows nothing that is
|
|
10
|
+
// not already public: the version naming the task is in the package's own release notes on
|
|
11
|
+
// npm, the core this hall runs is what the public GET /version reports, and "released" is
|
|
12
|
+
// the registry's `latest` label. The deploy page stays on `core.pin.move` for what it adds
|
|
13
|
+
// beyond that — the ledger of upgrades, the door. requireBuilder rather than nothing, like
|
|
14
|
+
// the task page's own read, so the line is never a signed-out probe.
|
|
15
|
+
//
|
|
16
|
+
// READ-ONLY.
|
|
17
|
+
|
|
18
|
+
const express = require('express');
|
|
19
|
+
const api = require('../../../src/module-api');
|
|
20
|
+
const { readTaskWhere } = require('../work');
|
|
21
|
+
|
|
22
|
+
const log = api.logger('npm-release');
|
|
23
|
+
|
|
24
|
+
module.exports = function buildNpmReleaseTaskWhereRouter({ read = readTaskWhere } = {}) {
|
|
25
|
+
const router = express.Router();
|
|
26
|
+
|
|
27
|
+
router.get(
|
|
28
|
+
'/npm-release/task/:id',
|
|
29
|
+
api.requireBuilder,
|
|
30
|
+
async (req, res) => {
|
|
31
|
+
const id = String((req.params && req.params.id) || '');
|
|
32
|
+
if (!/^\d{1,12}$/.test(id)) {
|
|
33
|
+
res.status(400).json({ error: { code: 'bad_task_id', message: 'A task id is a number.' } });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const where = await read({ pool: api.pool, id });
|
|
38
|
+
if (!where) { res.status(404).json({ error: { code: 'task_not_found', message: `No task ${id}.` } }); return; }
|
|
39
|
+
res.json(where);
|
|
40
|
+
} catch (e) {
|
|
41
|
+
log.error(`npm-release task-where read failed: ${e && e.message}`);
|
|
42
|
+
res.status(500).json({ error: { code: 'task_where_failed', message: 'Could not read where this task\'s code is right now.' } });
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
return router;
|
|
48
|
+
};
|