@bongos/core 1.24.24 → 1.24.26
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 +84 -69
- package/docs/copy-inventory.md +217 -212
- package/docs/copy-registry.json +298 -233
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-readings.json +855 -852
- package/modules/hall-ui/public/ai-studio.html +1 -1
- package/modules/hall-ui/public/blockers.html +1 -1
- package/modules/hall-ui/public/blockers.js +3 -1
- package/modules/hall-ui/public/board-room.html +2 -0
- package/modules/hall-ui/public/collab.html +3 -3
- package/modules/hall-ui/public/collab.js +4 -0
- package/modules/hall-ui/public/commands.html +2 -2
- package/modules/hall-ui/public/copy-desk.html +2 -2
- package/modules/hall-ui/public/criterion-uat-ui.js +1 -1
- package/modules/hall-ui/public/deploy.html +2 -0
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/fleet.html +2 -0
- package/modules/hall-ui/public/gate.html +2 -0
- package/modules/hall-ui/public/goals-page.js +1 -1
- package/modules/hall-ui/public/goals.css +3 -2
- package/modules/hall-ui/public/goals.html +2 -2
- package/modules/hall-ui/public/goals.js +1 -1
- package/modules/hall-ui/public/government.html +2 -0
- package/modules/hall-ui/public/hall-widgets.js +9 -9
- package/modules/hall-ui/public/ideas.html +2 -2
- package/modules/hall-ui/public/index.html +2 -0
- package/modules/hall-ui/public/lightbulb.js +1 -1
- package/modules/hall-ui/public/market-watch-item.html +1 -1
- package/modules/hall-ui/public/market-watch-rules.html +1 -1
- package/modules/hall-ui/public/market-watch-settings.html +2 -0
- package/modules/hall-ui/public/market-watch.html +1 -1
- package/modules/hall-ui/public/modules.html +1 -1
- package/modules/hall-ui/public/my-agents.html +2 -2
- package/modules/hall-ui/public/primer.html +1 -1
- package/modules/hall-ui/public/profile.html +5 -5
- package/modules/hall-ui/public/project-settings.html +2 -0
- package/modules/hall-ui/public/ranks.html +3 -3
- 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 +2 -0
- package/modules/hall-ui/public/settings-software-update.js +1 -1
- package/modules/hall-ui/public/settings-specialities.js +1 -1
- package/modules/hall-ui/public/settings.html +3 -1
- package/modules/hall-ui/public/settings.js +3 -0
- package/modules/hall-ui/public/studio.js +3 -3
- package/modules/hall-ui/public/style.css +90 -1
- package/modules/hall-ui/public/task-detail.js +2 -2
- package/modules/hall-ui/public/tweak-editor.html +1 -1
- package/modules/hall-ui/public/watch.html +2 -0
- package/modules/hall-ui/public/watch.js +1 -1
- package/modules/hall-ui/public/work.html +3 -3
- package/modules/hall-ui/records/loader.md +35 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +16 -0
- package/scripts/hall-preview/README.md +1 -0
- package/scripts/hall-preview/server.js +9 -0
- package/src/bongos/apex-hall-redirects.js +53 -0
- package/src/bongos/package-registry.js +3 -2
- package/src/bongos/routes/core-update.js +11 -3
- package/src/bongos/serve-internal.js +3 -18
- package/src/bongos/software-update.js +36 -16
- package/src/module-api.js +1 -1
- package/tests/hall_loading.mjs +153 -0
- package/tests/software_update.mjs +94 -3
|
@@ -1186,6 +1186,9 @@
|
|
|
1186
1186
|
function hideLayout(hide) {
|
|
1187
1187
|
const layout = document.getElementById('settings-layout');
|
|
1188
1188
|
if (layout) layout.hidden = !!hide;
|
|
1189
|
+
// Every path past /me decides the layout here, so the page loader stops here too (task 1005043).
|
|
1190
|
+
const loading = document.getElementById('settings-loading');
|
|
1191
|
+
if (loading) loading.hidden = true;
|
|
1189
1192
|
}
|
|
1190
1193
|
|
|
1191
1194
|
// ---- bootstrap -----------------------------------------------------------
|
|
@@ -755,7 +755,7 @@
|
|
|
755
755
|
|
|
756
756
|
function statusHtml() {
|
|
757
757
|
if (!home.pages) {
|
|
758
|
-
return
|
|
758
|
+
return home.pagesFailed ? '<p class="st-panel__line">The pages could not be read just now. Try again in a moment.</p>' : '<p class="hall-loading" role="status">Reading the pages…</p>';
|
|
759
759
|
}
|
|
760
760
|
const rows = lib.surfaceRows(home.pages, lib.landingHost(byId('hall-main') && byId('hall-main').getAttribute('data-apex'), window.location && window.location.host)).map((s) => `
|
|
761
761
|
<li class="st-surface">
|
|
@@ -804,7 +804,7 @@
|
|
|
804
804
|
|
|
805
805
|
function siteHtml() {
|
|
806
806
|
if (!home.site) {
|
|
807
|
-
return `<p class="st-panel__line">${
|
|
807
|
+
return home.siteFailed ? `<p class="st-panel__line">${SITE_FAILED}</p>` : '<p class="hall-loading" role="status">Reading the reviews…</p>';
|
|
808
808
|
}
|
|
809
809
|
if (!home.site.length) return `<p class="st-panel__line">${SITE_NONE}</p>`;
|
|
810
810
|
const rows = home.site.map((r) => `
|
|
@@ -1178,7 +1178,7 @@
|
|
|
1178
1178
|
}
|
|
1179
1179
|
|
|
1180
1180
|
function siteListPage() {
|
|
1181
|
-
if (!home.site) return `<p class="st-panel__line">${
|
|
1181
|
+
if (!home.site) return home.siteFailed ? `<p class="st-panel__line">${SITE_FAILED}</p>` : '<p class="hall-loading" role="status">Reading the reviews…</p>';
|
|
1182
1182
|
if (!home.site.length) return `<p class="st-panel__line">${SITE_NONE}</p>`;
|
|
1183
1183
|
return `<ul class="st-doc-list">${home.site.map((r) => `<li><a class="st-doc-list__row" href="${escapeHtml(siteHref(r.link))}">
|
|
1184
1184
|
<span class="st-doc-list__page">${escapeHtml(r.page_title || r.page_path || r.page_id)}</span>
|
|
@@ -4317,7 +4317,8 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
|
|
|
4317
4317
|
.empty-state__action { display: inline-block; margin-top: 6px; font-size: 13px; }
|
|
4318
4318
|
|
|
4319
4319
|
/* skeleton shimmer — standard-height placeholder rows while data loads, so
|
|
4320
|
-
cards don't jump; derives from the ink/bg tokens like everything else
|
|
4320
|
+
cards don't jump; derives from the ink/bg tokens like everything else. It
|
|
4321
|
+
breathes in the loader's beat (task 1005043; the keyframes sit with it). */
|
|
4321
4322
|
.kit-skel {
|
|
4322
4323
|
display: inline-block;
|
|
4323
4324
|
width: 60%;
|
|
@@ -4325,9 +4326,97 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
|
|
|
4325
4326
|
height: 12px;
|
|
4326
4327
|
border-radius: 2px;
|
|
4327
4328
|
background: var(--skeleton);
|
|
4329
|
+
animation: hall-loading-breathe .8s ease-in-out infinite alternate-reverse;
|
|
4328
4330
|
}
|
|
4329
4331
|
.kit-skel-row td { border-bottom: 1px solid var(--rule-soft); }
|
|
4330
4332
|
|
|
4333
|
+
/* ---- the loader (task 1005043) ----------------------------------------------
|
|
4334
|
+
One loader for everything a hall page waits on, drawn from the Bongos mark
|
|
4335
|
+
(shell.js's brand-mark SVG: the ring, the core and the accent dot). A page
|
|
4336
|
+
opts in by giving its existing placeholder the class; its own words stay as
|
|
4337
|
+
the caption, so no page gains markup. The ring and the dot turn together round
|
|
4338
|
+
the still core and the ink breathes, after the Vibe skeleton the owner pointed
|
|
4339
|
+
at (vibe.monday.com: 0.4 to 1 and back, 0.8s). Three sizes: the default for a
|
|
4340
|
+
panel, --page for a page's main area, --inline for a line in a list or card.
|
|
4341
|
+
|
|
4342
|
+
DESIGN.md holds it: CSS only, transform and filter only (the breath and the
|
|
4343
|
+
arrival are filter: opacity(), not opacity), every colour a var() or a mix of
|
|
4344
|
+
one; the black in the two masks is alpha, not paint. The 20s floor is for the
|
|
4345
|
+
ambient plates: a loader is the live thing on the page, so it keeps a loader's
|
|
4346
|
+
tempo and the dot spends the accent. Each turn eases out of and back into the
|
|
4347
|
+
logo's own pose, so the mark lands on the logo once per beat. It arrives after
|
|
4348
|
+
240ms, so a read that answers faster never shows it. Under the Kill Switch
|
|
4349
|
+
nothing moves and the rest frame is the logo itself. ---- */
|
|
4350
|
+
.hall-loading {
|
|
4351
|
+
--hl-size: 40px;
|
|
4352
|
+
--hl-pad: 28px;
|
|
4353
|
+
--hl-beat: 1.6s;
|
|
4354
|
+
position: relative;
|
|
4355
|
+
display: flex;
|
|
4356
|
+
flex-direction: column;
|
|
4357
|
+
align-items: center;
|
|
4358
|
+
gap: 12px;
|
|
4359
|
+
margin: 0;
|
|
4360
|
+
padding: var(--hl-pad) 0;
|
|
4361
|
+
font-size: 13.5px;
|
|
4362
|
+
color: var(--ink-faint);
|
|
4363
|
+
text-align: center;
|
|
4364
|
+
animation: hall-loading-arrive 500ms cubic-bezier(.16, 1, .3, 1) 240ms both;
|
|
4365
|
+
}
|
|
4366
|
+
.hall-loading::before,
|
|
4367
|
+
.hall-loading::after {
|
|
4368
|
+
content: "";
|
|
4369
|
+
display: block;
|
|
4370
|
+
flex: none;
|
|
4371
|
+
width: var(--hl-size);
|
|
4372
|
+
height: var(--hl-size);
|
|
4373
|
+
-webkit-mask: var(--hl-shape) center / contain no-repeat;
|
|
4374
|
+
mask: var(--hl-shape) center / contain no-repeat;
|
|
4375
|
+
animation: hall-loading-turn var(--hl-beat) cubic-bezier(.45, .05, .55, .95) infinite;
|
|
4376
|
+
}
|
|
4377
|
+
/* the ring and the core: one ink, breathing */
|
|
4378
|
+
.hall-loading::before {
|
|
4379
|
+
--hl-shape: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'%3E%3Cpath d='M137 31 A78 78 0 1 0 170 66' fill='none' stroke='black' stroke-width='15' stroke-linecap='round'/%3E%3Ccircle cx='100' cy='100' r='55'/%3E%3C/svg%3E");
|
|
4380
|
+
background: color-mix(in srgb, var(--ink) 30%, var(--bg-card));
|
|
4381
|
+
animation:
|
|
4382
|
+
hall-loading-turn var(--hl-beat) cubic-bezier(.45, .05, .55, .95) infinite,
|
|
4383
|
+
hall-loading-breathe calc(var(--hl-beat) / 2) ease-in-out infinite alternate-reverse;
|
|
4384
|
+
}
|
|
4385
|
+
/* the dot: the accent, laid exactly over the mark and turning with it */
|
|
4386
|
+
.hall-loading::after {
|
|
4387
|
+
--hl-shape: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'%3E%3Ccircle cx='156' cy='46' r='12'/%3E%3C/svg%3E");
|
|
4388
|
+
position: absolute;
|
|
4389
|
+
top: var(--hl-pad);
|
|
4390
|
+
left: calc(50% - var(--hl-size) / 2);
|
|
4391
|
+
background: var(--accent);
|
|
4392
|
+
}
|
|
4393
|
+
.hall-loading--page { --hl-size: 64px; --hl-pad: 72px; gap: 16px; }
|
|
4394
|
+
.hall-loading--inline {
|
|
4395
|
+
--hl-size: 16px;
|
|
4396
|
+
--hl-pad: 0px;
|
|
4397
|
+
display: inline-flex;
|
|
4398
|
+
flex-direction: row;
|
|
4399
|
+
gap: 8px;
|
|
4400
|
+
text-align: left;
|
|
4401
|
+
}
|
|
4402
|
+
.hall-loading--inline::after { top: calc(50% - var(--hl-size) / 2); left: 0; }
|
|
4403
|
+
/* --wait: a page whose every section stays [hidden] until it knows who is
|
|
4404
|
+
reading (the sealed/body pairs) puts one loader FIRST in its <main>. It shows
|
|
4405
|
+
only while nothing after it is showing, and steps aside the moment the page
|
|
4406
|
+
reveals anything (the body, the sealed notice, an error), so no page script
|
|
4407
|
+
has to remember to hide it. Without :has() it never shows: today's page. */
|
|
4408
|
+
/* --zones: the same, for a view made of always-present, empty zones that cards
|
|
4409
|
+
mount into (Home, Watch's panes): it shows while no showing zone after it
|
|
4410
|
+
holds a showing child. */
|
|
4411
|
+
.hall-loading--wait, .hall-loading--zones { display: none; }
|
|
4412
|
+
@supports selector(:has(a)) {
|
|
4413
|
+
.hall-loading--wait:not(:has(~ :not([hidden], script, template, style))) { display: flex; }
|
|
4414
|
+
.hall-loading--zones:not(:has(~ :not([hidden]) > :not([hidden], script, template, style))) { display: flex; }
|
|
4415
|
+
}
|
|
4416
|
+
@keyframes hall-loading-arrive { from { filter: opacity(0); transform: translateY(6px); } }
|
|
4417
|
+
@keyframes hall-loading-turn { to { transform: rotate(1turn); } }
|
|
4418
|
+
@keyframes hall-loading-breathe { from { filter: opacity(.45); } }
|
|
4419
|
+
|
|
4331
4420
|
/* collapsible card body — the Work-backlog <details> idiom, generalized */
|
|
4332
4421
|
.kit-collapse__summary {
|
|
4333
4422
|
cursor: pointer;
|
|
@@ -413,7 +413,7 @@
|
|
|
413
413
|
}
|
|
414
414
|
|
|
415
415
|
async function renderPromote() {
|
|
416
|
-
box.innerHTML = '<p class="
|
|
416
|
+
box.innerHTML = '<p class="hall-loading hall-loading--inline" role="status">Loading versions…</p>';
|
|
417
417
|
// Both dead ends offer the way back through the same delegated [data-go]
|
|
418
418
|
// handler the forms use — a builder must never be stranded mid-panel.
|
|
419
419
|
function bail(message) {
|
|
@@ -658,7 +658,7 @@
|
|
|
658
658
|
function bodyFor(state) {
|
|
659
659
|
var nodes = [];
|
|
660
660
|
var label = '#' + state.id;
|
|
661
|
-
if (state.loading) { nodes.push(h('p', '
|
|
661
|
+
if (state.loading) { nodes.push(h('p', 'hall-loading', 'Loading ' + label + '…')); return nodes; }
|
|
662
662
|
if (state.needsAuth) {
|
|
663
663
|
nodes.push(h('p', 'deeplink-card__title', label));
|
|
664
664
|
nodes.push(h('p', 'deeplink-card__note', 'Sign in to the Builders’ Hall to view this record.'));
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
<p class="tw-hint" id="tw-hint" hidden></p>
|
|
75
75
|
<div class="tw-notice" id="tw-notice" role="status" hidden></div>
|
|
76
76
|
<div id="tw-doc">
|
|
77
|
-
<p class="
|
|
77
|
+
<p class="hall-loading hall-loading--page" role="status">Reading the page…</p>
|
|
78
78
|
</div>
|
|
79
79
|
</article>
|
|
80
80
|
|
|
@@ -34,6 +34,8 @@
|
|
|
34
34
|
</div>
|
|
35
35
|
|
|
36
36
|
<main id="hall-main">
|
|
37
|
+
<!-- task 1005043: the page loader, shown until the sealed notice or a pane has something showing. -->
|
|
38
|
+
<p class="hall-loading hall-loading--page hall-loading--zones" role="status">Loading…</p>
|
|
37
39
|
<!-- Restricted notice — shown by watch.js when the viewer is below the
|
|
38
40
|
floor, with the floor written in from the pack's rank label. -->
|
|
39
41
|
<section class="scroll" id="sealed-scroll" aria-labelledby="sealed-h" hidden>
|
|
@@ -754,7 +754,7 @@
|
|
|
754
754
|
if (!btn || !target.contains(btn)) return;
|
|
755
755
|
const slug = btn.getAttribute('data-doc-open');
|
|
756
756
|
const view = $('#engagement-doc-view');
|
|
757
|
-
if (view) view.innerHTML =
|
|
757
|
+
if (view) view.innerHTML = '<p class="hall-loading" role="status">Reading…</p>';
|
|
758
758
|
try {
|
|
759
759
|
const res = await fetchJson(`/security/docs/${encodeURIComponent(slug)}`);
|
|
760
760
|
const doc = res.doc || {};
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
<p class="claim-count-line" id="claim-count" aria-live="polite"></p>
|
|
106
106
|
|
|
107
107
|
<div class="task-list" id="task-list">
|
|
108
|
-
<div class="
|
|
108
|
+
<div class="hall-loading" role="status">Loading…</div>
|
|
109
109
|
</div>
|
|
110
110
|
<div id="ready-pager-mount"></div>
|
|
111
111
|
</section>
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
|
|
118
118
|
<table class="ledger" id="wip-table">
|
|
119
119
|
<thead><tr><th></th><th>#</th><th>Task</th><th>Builder</th><th>For</th><th></th></tr></thead>
|
|
120
|
-
<tbody><tr><td colspan="6" class="ledger__empty"
|
|
120
|
+
<tbody><tr><td colspan="6" class="ledger__empty"><span class="hall-loading" role="status">Loading…</span></td></tr></tbody>
|
|
121
121
|
</table>
|
|
122
122
|
<div id="wip-pager-mount"></div>
|
|
123
123
|
</section>
|
|
@@ -139,7 +139,7 @@
|
|
|
139
139
|
<div class="stat-row" id="works-summary" style="margin-bottom: 14px" hidden></div>
|
|
140
140
|
<p class="scroll__lede">The most recently shipped tasks.</p>
|
|
141
141
|
<div class="history" id="history-list">
|
|
142
|
-
<div class="
|
|
142
|
+
<div class="hall-loading" role="status">Loading…</div>
|
|
143
143
|
</div>
|
|
144
144
|
<div id="done-pager-mount"></div>
|
|
145
145
|
</section>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# The loader — the Bongos mark wherever a hall page waits for its data (task 1005043)
|
|
2
|
+
|
|
3
|
+
Goal 1000095. Owner ask, 2026-11-03: a smooth load screen with the Bongos logo whenever the hall
|
|
4
|
+
waits for data, in the spirit of monday.com's Vibe data loaders (vibe.monday.com, Skeleton: grey
|
|
5
|
+
shapes breathing 0.4 to 1 and back over 0.8s, in the place the content will land).
|
|
6
|
+
|
|
7
|
+
- **One component, in `style.css`: `.hall-loading`.** CSS only. `::before` is the ring and the core
|
|
8
|
+
(a mask of shell.js's brand-mark geometry, painted `--ink` at 30%), `::after` is the dot (the same
|
|
9
|
+
box, painted `--accent`). Both turn together round the still core, easing out of and back into the
|
|
10
|
+
logo's own pose every 1.6s; the ink breathes on half that beat. The element's own text is the
|
|
11
|
+
caption. It arrives 240ms late (filter: opacity), so a read that answers fast never shows it.
|
|
12
|
+
Sizes: the default (40px, a panel), `--page` (64px, a page's main area), `--inline` (16px, a line).
|
|
13
|
+
- **A page opts in by putting the class on the placeholder it already had**, and gives it
|
|
14
|
+
`role="status"`. The words did not change; a bare `…` became `Loading…`. A placeholder a script
|
|
15
|
+
rewrites IN PLACE (atlas's caption, sessions' status line, the goal dependency ledes, the profile's
|
|
16
|
+
activity placeholder) keeps its plain words, because the class would stay on the finished sentence.
|
|
17
|
+
- **Pages that are blank while they wait get a page loader that puts itself away.** The sealed/body
|
|
18
|
+
pages (board-room, deploy, fleet, gate, government, market-watch-settings, project-settings,
|
|
19
|
+
sessions) open their `<main>` on `.hall-loading--wait`, which shows only while every section after
|
|
20
|
+
it is `[hidden]`. Home and Watch, whose zones and panes are always present, use
|
|
21
|
+
`.hall-loading--zones`: it shows while no showing zone has a showing child. Both are `:has()`
|
|
22
|
+
rules inside `@supports`, so a browser without it simply never shows them. No page script hides
|
|
23
|
+
them. Settings' layout is never `[hidden]`, so `hideLayout()` puts its loader away.
|
|
24
|
+
- **Lists that paint later carry a loader inside them**: Blockers' list (its first `renderList`
|
|
25
|
+
replaces it), Collab's three ask lists (its failure path now stops them and says why, where the page
|
|
26
|
+
used to stay blank), the table placeholder rows (the loader sits inside the `ledger__empty` cell).
|
|
27
|
+
- **The skeletons breathe too.** `.kit-skel` and the goal list's `.gr-skel__ring` take the loader's
|
|
28
|
+
breath, so the Goals list's row skeleton is the Vibe pattern exactly.
|
|
29
|
+
- **DESIGN.md holds.** Transform and filter only; every colour a token (the black in the masks is
|
|
30
|
+
alpha); the Kill Switch stops it all and leaves the logo. The 20s ambient floor is for the plates:
|
|
31
|
+
a loader is the live thing on the page, which is also why its dot may spend the accent.
|
|
32
|
+
- **Not covered:** the apex pages (account, projects, community) and the status page run their own
|
|
33
|
+
sheets; the city, street, goal map and thinking sky draw their own worlds and kept their own lines.
|
|
34
|
+
- **Look at it:** `node scripts/hall-preview/server.js --port 4630 --fixture-me --latency 5000` holds
|
|
35
|
+
every read 5s (task 1005043 added `--latency`). Tests: `tests/hall_loading.mjs`.
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.26",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.24.
|
|
9
|
+
"version": "1.24.26",
|
|
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.24.
|
|
3
|
+
"version": "1.24.26",
|
|
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",
|
package/release-notes.json
CHANGED
|
@@ -11065,5 +11065,21 @@
|
|
|
11065
11065
|
"id": "1004908",
|
|
11066
11066
|
"text": "Contractors are now only paid once a signed NDA is on file and the payment processor has collected their tax details; if either is missing the payment waits and the person is told why. Bongos never stores tax numbers."
|
|
11067
11067
|
}
|
|
11068
|
+
],
|
|
11069
|
+
"1.24.25": [
|
|
11070
|
+
{
|
|
11071
|
+
"id": "1004593",
|
|
11072
|
+
"text": "A hosted project's Settings page no longer offers an unreleased version; it now offers exactly what the Upgrade page offers."
|
|
11073
|
+
}
|
|
11074
|
+
],
|
|
11075
|
+
"1.24.26": [
|
|
11076
|
+
{
|
|
11077
|
+
"id": "1005043",
|
|
11078
|
+
"text": "While a page in the builders' hall is waiting for its data, the Bongos logo now turns gently where the content will appear, instead of a blank space or a line of grey text."
|
|
11079
|
+
},
|
|
11080
|
+
{
|
|
11081
|
+
"id": "1004736",
|
|
11082
|
+
"text": "A hosted project's Update button no longer leads to a missing page; it opens the Upgrade page for that project."
|
|
11083
|
+
}
|
|
11068
11084
|
]
|
|
11069
11085
|
}
|
|
@@ -22,6 +22,7 @@ node scripts/hall-preview/server.js # neutral pack, port 4600
|
|
|
22
22
|
| `--modules-off discord,provisioning` | report those modules disabled in `window.__MODULES__` — verifies `moduleOn()` gating (the Discord links, Instances/Start-a-project widgets must vanish) |
|
|
23
23
|
| `--empty` | drain every array in every fixture — the empty-state screenshot pass |
|
|
24
24
|
| `--fixture-me` | don't read your own account live — serve the checked-in `me.json` snapshot (see below) |
|
|
25
|
+
| `--latency 3000` | hold every data read that many ms before answering, so a page's loading state (the hall's `.hall-loading` mark) stays on screen long enough to look at |
|
|
25
26
|
|
|
26
27
|
Writes are not wired: any `POST/PATCH/DELETE` answers `501 preview_readonly` — except
|
|
27
28
|
the canned writes in `server.js` and, since task 1004685, the five per-builder pref
|
|
@@ -65,6 +65,10 @@ const MODULES_ON = String(arg('--modules-on') || '').split(',').map((s) => s.tri
|
|
|
65
65
|
// own account live. The screenshot matrix in the README wants a stable subject,
|
|
66
66
|
// and it is the explicit way to preview the signed-out fallback (task 1003315).
|
|
67
67
|
const FIXTURE_ME = process.argv.includes('--fixture-me');
|
|
68
|
+
// --latency: hold every data read this many ms before answering (task 1005043),
|
|
69
|
+
// so a page's loading state stays on screen long enough to be looked at. The
|
|
70
|
+
// fixtures answer in a millisecond, which is why no loader had ever been seen.
|
|
71
|
+
const LATENCY = Math.max(0, Number(arg('--latency')) || 0);
|
|
68
72
|
const BRAND_FILE = arg('--brand') || 'config/branding.neutral.json';
|
|
69
73
|
process.env.BONGOS_BRANDING_FILE = path.resolve(ROOT, BRAND_FILE);
|
|
70
74
|
|
|
@@ -295,6 +299,11 @@ function fixtureFor(apiPath, query = {}) {
|
|
|
295
299
|
// ---- app ----------------------------------------------------------------------
|
|
296
300
|
const app = express();
|
|
297
301
|
|
|
302
|
+
// --latency (above): the live channel is left alone, it is a stream, not a read.
|
|
303
|
+
if (LATENCY) {
|
|
304
|
+
app.use((req, res, next) => (/^\/api\//.test(req.path) && !/\/live\/?$/.test(req.path) ? setTimeout(next, LATENCY) : next()));
|
|
305
|
+
}
|
|
306
|
+
|
|
298
307
|
// The API stub — intercepts every /api/gds/*, /api/bongos/*, /api/bongos/v1/*
|
|
299
308
|
// request BEFORE mountInternalSurfaces can reach the real routes (which would
|
|
300
309
|
// touch Postgres — the harness is DB-less by design). A plain function
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/bongos/apex-hall-redirects.js — the apex hands the hall's addresses to the hall's own
|
|
4
|
+
// host: /builders/* (the ADR 0036 cutover) and /deploy, the deploy door every hosted
|
|
5
|
+
// project's Update button names (task 1004736).
|
|
6
|
+
//
|
|
7
|
+
// Moved out of serve-internal.js, which sits at the 1500-line size ratchet, and mounted from
|
|
8
|
+
// it at the same point in the chain. `skip(req)` is true for a module surface host and for
|
|
9
|
+
// the hall's own host (on a single-host instance that is every request), so both answer only
|
|
10
|
+
// on the apex. `hostOf(req)` is the request's host, lower-cased, without a port.
|
|
11
|
+
|
|
12
|
+
// #739 (ADR 0036 cutover): the builder surface's canonical home is the subdomain. On the
|
|
13
|
+
// apex, /builders and /builders/* go to builders.<apex>/<rest> so old links keep working.
|
|
14
|
+
// 301 (permanent) — the cutover is proven live (#741; started as a 302 in #739). The URL
|
|
15
|
+
// fragment (#/task/N deep-links) is preserved by the browser across the redirect; the query
|
|
16
|
+
// string is carried explicitly. The target apex comes from the request host, so it stays
|
|
17
|
+
// brand-agnostic.
|
|
18
|
+
function buildersRedirect({ skip, hostOf }) {
|
|
19
|
+
return (req, res, next) => {
|
|
20
|
+
if (skip(req)) return next();
|
|
21
|
+
if (req.path !== '/builders' && !req.path.startsWith('/builders/')) return next();
|
|
22
|
+
const rest = req.path.replace(/^\/builders\/?/, ''); // '/builders'→'', '/builders/watch'→'watch'
|
|
23
|
+
const qs = req.url.includes('?') ? req.url.slice(req.url.indexOf('?')) : '';
|
|
24
|
+
return res.redirect(301, `https://builders.${hostOf(req)}/${rest}${qs}`);
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// THE DEPLOY DOOR ON THE APEX (task 1004736). A hosted project's Update button is
|
|
29
|
+
// `<hub origin>/deploy?project=<slug>` (routes/core-update.js hubDeployUrl, and the
|
|
30
|
+
// HUB_UPGRADE_URL provisioning writes), and the hub origin is its apex, where nothing served
|
|
31
|
+
// /deploy: every hosted owner who pressed Update met a 404. The page is the hall's, so the
|
|
32
|
+
// apex hands the request over, query and all, and the hall decides (its page gate, or
|
|
33
|
+
// Settings where no door runs). Answered here rather than by rewriting the links, because
|
|
34
|
+
// those already sit in every project's web.env and core and would wait for each project's
|
|
35
|
+
// core to move. 302: the apex may serve its own /deploy one day.
|
|
36
|
+
const APEX_DEPLOY_RE = /^\/deploy(?:\.html)?\/?$/;
|
|
37
|
+
function deployRedirect({ skip, hostOf }) {
|
|
38
|
+
return (req, res, next) => {
|
|
39
|
+
if (skip(req)) return next();
|
|
40
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') return next();
|
|
41
|
+
if (!APEX_DEPLOY_RE.test(req.path)) return next();
|
|
42
|
+
const qs = req.url.includes('?') ? req.url.slice(req.url.indexOf('?')) : '';
|
|
43
|
+
return res.redirect(302, `https://builders.${hostOf(req)}/deploy${qs}`);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Mount both on `app`, in order, at the caller's point in the chain. */
|
|
48
|
+
function mountApexHallRedirects(app, { skip, hostOf }) {
|
|
49
|
+
app.use(buildersRedirect({ skip, hostOf }));
|
|
50
|
+
app.use(deployRedirect({ skip, hostOf }));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = { mountApexHallRedirects };
|
|
@@ -255,7 +255,8 @@ async function cached(slot, fresh, load, t) {
|
|
|
255
255
|
* registry — `{ ok, versions, published, latest, newest }` or `{ ok:false, error }`
|
|
256
256
|
* notes — the newest version's release notes, `{ ok, version, notes }` or
|
|
257
257
|
* `{ ok:false, version, error }`; `{ ok:false, skipped }` when the caller
|
|
258
|
-
* already holds notes that reach the newest version
|
|
258
|
+
* already holds notes that reach the newest version, or needs none
|
|
259
|
+
* (`haveNotesFor(newest, registry)`).
|
|
259
260
|
*
|
|
260
261
|
* `get` swaps the HTTPS transport and `store`/`now` the cache, so tests drive the real
|
|
261
262
|
* parsing and failure paths rather than a stub standing in front of them.
|
|
@@ -267,7 +268,7 @@ async function readRegistry({ pkg, haveNotesFor = () => false, get = https.get,
|
|
|
267
268
|
() => fetchPackument({ pkg, get, origin }), t);
|
|
268
269
|
if (!registry.ok) return { registry, notes: { ok: false, error: 'the registry could not be read' } };
|
|
269
270
|
const version = registry.newest;
|
|
270
|
-
if (haveNotesFor(version)) return { registry, notes: { ok: false, skipped: true, version } };
|
|
271
|
+
if (haveNotesFor(version, registry)) return { registry, notes: { ok: false, skipped: true, version } };
|
|
271
272
|
const ns = store.notes;
|
|
272
273
|
if (ns.version !== version) { ns.version = version; ns.value = null; ns.at = 0; }
|
|
273
274
|
const notes = await cached(ns, (s) => s.value.ok || t - s.at < RETRY_AFTER_MS,
|
|
@@ -31,6 +31,7 @@ const { asyncHandler } = require('./_helpers');
|
|
|
31
31
|
const auth = require('../auth');
|
|
32
32
|
const { readCoreUpdate, readCoreUpgrades } = require('../core-update');
|
|
33
33
|
const { readSoftwareUpdate } = require('../software-update');
|
|
34
|
+
const { followForShape } = require('../../../scripts/bongos/update-channel.js');
|
|
34
35
|
const seams = require('../../module-seams');
|
|
35
36
|
const { loadIdpConfig } = require('../auth-config');
|
|
36
37
|
const upgradeRequest = require('../upgrade-request');
|
|
@@ -88,6 +89,13 @@ function updateDoor({ provisioning, idp, hubUpgradeUrl }) {
|
|
|
88
89
|
return url ? { kind: 'hub', url } : { kind: 'cli', url: null };
|
|
89
90
|
}
|
|
90
91
|
|
|
92
|
+
// WHICH PUBLISHED VERSIONS THIS HALL MAY BE OFFERED (task 1004593, ADR 0361 D5): the hall that
|
|
93
|
+
// runs the door is the platform's own, the one project that takes every candidate; every other
|
|
94
|
+
// project follows releases, as the runner and /deploy offer them. followForShape is the runner's.
|
|
95
|
+
function followFor(door) {
|
|
96
|
+
return followForShape(door && door.kind === 'local' ? 'control-plane' : null);
|
|
97
|
+
}
|
|
98
|
+
|
|
91
99
|
function safeHubUpgradeUrl() {
|
|
92
100
|
try { return require('../../instance-config').resolveEnv('HUB_UPGRADE_URL') || null; } catch { return null; }
|
|
93
101
|
}
|
|
@@ -327,12 +335,12 @@ function buildCoreUpdateRouter({
|
|
|
327
335
|
asyncHandler('GET /software-update', async (req, res) => {
|
|
328
336
|
let running = null;
|
|
329
337
|
try { running = require('../../module-api').CORE_VERSION; } catch { /* version unknown */ }
|
|
338
|
+
const door = updateDoor(doorInputs());
|
|
330
339
|
// canUpdate does not depend on the rule, so it runs beside the rule read, not after it.
|
|
331
340
|
const [update, can] = await Promise.all([
|
|
332
|
-
resolveChannel(selfChannel).then(async ({ channel, source }) => ({ ...(await softwareUpdate({ running, channel })), channel_source: source })),
|
|
341
|
+
resolveChannel(selfChannel).then(async ({ channel, source }) => ({ ...(await softwareUpdate({ running, channel, follow: followFor(door) })), channel_source: source })),
|
|
333
342
|
canUpdate(req),
|
|
334
343
|
]);
|
|
335
|
-
const door = updateDoor(doorInputs());
|
|
336
344
|
const ask = await readAsk({ update, door, deps: askDeps() });
|
|
337
345
|
const held = await safeHold(upgradeHold, { running, offered: update && update.reading === 'ok' ? update.offered : null });
|
|
338
346
|
// The history only for a viewer who holds core.pin.move (see safeMoved's gate note).
|
|
@@ -357,8 +365,8 @@ function buildCoreUpdateRouter({
|
|
|
357
365
|
let running = null;
|
|
358
366
|
try { running = require('../../module-api').CORE_VERSION; } catch { /* version unknown */ }
|
|
359
367
|
const { channel } = await resolveChannel(selfChannel);
|
|
360
|
-
const update = await softwareUpdate({ running, channel });
|
|
361
368
|
const door = updateDoor(doorInputs());
|
|
369
|
+
const update = await softwareUpdate({ running, channel, follow: followFor(door) });
|
|
362
370
|
const deps = askDeps();
|
|
363
371
|
return upgradeRequest.requestUpgrade({
|
|
364
372
|
host: deps.host,
|
|
@@ -34,6 +34,7 @@ const fs = require('fs');
|
|
|
34
34
|
const express = require('express');
|
|
35
35
|
|
|
36
36
|
const { buildGdsRouter } = require('./routes');
|
|
37
|
+
const { mountApexHallRedirects } = require('./apex-hall-redirects');
|
|
37
38
|
const { attachFail } = require('./middleware/error-envelope');
|
|
38
39
|
const metrics = require('./metrics');
|
|
39
40
|
const { logger } = require('./logger');
|
|
@@ -923,24 +924,8 @@ function mountInternalSurfaces(app) {
|
|
|
923
924
|
next();
|
|
924
925
|
});
|
|
925
926
|
|
|
926
|
-
//
|
|
927
|
-
|
|
928
|
-
// builders.<apex>/<rest> so old links keep working while the subdomain becomes
|
|
929
|
-
// the front door. Runs ONLY on the apex (skipped on the status + builders
|
|
930
|
-
// hosts; /api is host-agnostic and mounted earlier, and never starts /builders).
|
|
931
|
-
// 301 (permanent) — the cutover is proven live + verified end-to-end (#741;
|
|
932
|
-
// started as a 302 in #739). The URL fragment (#/task/N deep-links) is preserved
|
|
933
|
-
// by the browser across the redirect; the query string is carried explicitly.
|
|
934
|
-
// The redirect target apex is read from the request host (strip a leading
|
|
935
|
-
// "builders."/"status." if present) so it stays brand-agnostic.
|
|
936
|
-
app.use((req, res, next) => {
|
|
937
|
-
if (isModuleSurfaceHost(req) || isBuildersHost(req)) return next();
|
|
938
|
-
if (req.path !== '/builders' && !req.path.startsWith('/builders/')) return next();
|
|
939
|
-
const apex = siteOf(req).host;
|
|
940
|
-
const rest = req.path.replace(/^\/builders\/?/, ''); // '/builders'→'', '/builders/watch'→'watch'
|
|
941
|
-
const qs = req.url.includes('?') ? req.url.slice(req.url.indexOf('?')) : '';
|
|
942
|
-
return res.redirect(301, `https://builders.${apex}/${rest}${qs}`);
|
|
943
|
-
});
|
|
927
|
+
// The apex hands /builders/* (ADR 0036) and /deploy (task 1004736) to the hall's own host.
|
|
928
|
+
mountApexHallRedirects(app, { skip: (req) => isModuleSurfaceHost(req) || isBuildersHost(req), hostOf: (req) => siteOf(req).host });
|
|
944
929
|
|
|
945
930
|
// The /watch monitoring page and the /gate page (task 1179) both sit behind
|
|
946
931
|
// `page.view.government` — Metic and up since ADR 0157. Same server-side page
|
|
@@ -22,6 +22,12 @@
|
|
|
22
22
|
// /deploy, via the provisioning.selfUpdateChannel seam — task 1004535), else the runner's
|
|
23
23
|
// default. The banner (core-update.js) is handed the same rule, so the two cannot disagree.
|
|
24
24
|
//
|
|
25
|
+
// ONLY WHAT THIS PROJECT'S FOLLOW ADMITS IS OFFERED (task 1004593, ADR 0361 D5). Merges publish
|
|
26
|
+
// as candidates above the registry's `latest` label; a project that follows releases is offered
|
|
27
|
+
// nothing above it, through `versionsForFollow`, the function the runner and the banner use.
|
|
28
|
+
// Before this the panel offered a candidate that /deploy then said did not exist. The route
|
|
29
|
+
// names the follow: candidates only on the platform's own hall.
|
|
30
|
+
//
|
|
25
31
|
// A READING, NEVER A GUESS. `reading` is 'ok' only when the registry answered; "up to date"
|
|
26
32
|
// is `offered: null` on an ok reading and nothing else. Notes that could not be read say so,
|
|
27
33
|
// and a version whose notes are missing is shown as missing rather than as empty.
|
|
@@ -30,6 +36,8 @@ const {
|
|
|
30
36
|
CORE_PKG,
|
|
31
37
|
DEFAULT_CHANNEL,
|
|
32
38
|
normalizeChannel,
|
|
39
|
+
normalizeFollow,
|
|
40
|
+
versionsForFollow,
|
|
33
41
|
resolveChannelTarget,
|
|
34
42
|
compareSemver,
|
|
35
43
|
} = require('../../scripts/bongos/update-channel.js');
|
|
@@ -62,22 +70,29 @@ function entriesFor(notes, version) {
|
|
|
62
70
|
return out;
|
|
63
71
|
}
|
|
64
72
|
|
|
73
|
+
/** The published versions `follow` admits, oldest first; [] when a release follower has no label. */
|
|
74
|
+
function admittedVersions(follow, registry) {
|
|
75
|
+
return versionsForFollow({ follow, available: registry.versions, released: registry.latest });
|
|
76
|
+
}
|
|
77
|
+
|
|
65
78
|
/**
|
|
66
79
|
* The panel's whole answer, from one registry reading. PURE — no clock, no I/O.
|
|
67
80
|
*
|
|
68
81
|
* `registry` and `notes` are package-registry.js's `readRegistry` result. Versions in the
|
|
69
|
-
* update are the
|
|
82
|
+
* update are the admitted ones above `running` and at or below `offered`, newest first, and
|
|
83
|
+
* `newest` is the newest admitted version, so a candidate is never named to a release follower.
|
|
70
84
|
*/
|
|
71
|
-
function softwareUpdateVerdict({ running, channel, registry, notes, checkedAt }) {
|
|
85
|
+
function softwareUpdateVerdict({ running, channel, follow, registry, notes, checkedAt }) {
|
|
72
86
|
const ch = normalizeChannel(channel == null ? DEFAULT_CHANNEL : channel);
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (!registry || !registry.ok)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
87
|
+
const fol = normalizeFollow(follow);
|
|
88
|
+
const base = { checked_at: checkedAt, running: running || null, channel: ch, follow: fol };
|
|
89
|
+
const noAnswer = (error) => ({ ...base, reading: 'no_answer', error, offered: null, newest: null, whats_new: null });
|
|
90
|
+
if (!running || !VERSION_RE.test(String(running))) return noAnswer('this project could not name the version it runs');
|
|
91
|
+
if (!registry || !registry.ok) return noAnswer((registry && registry.error) || 'the registry could not be read');
|
|
92
|
+
const admitted = admittedVersions(fol, registry);
|
|
93
|
+
// No label, no answer: the runner moves a release follower on nothing it cannot call released.
|
|
94
|
+
if (!admitted.length) return noAnswer('the registry does not say which version is released');
|
|
95
|
+
const offered = resolveChannelTarget({ installed: running, channel: ch, available: admitted }) || null;
|
|
81
96
|
const published = registry.published || {};
|
|
82
97
|
const answer = {
|
|
83
98
|
...base,
|
|
@@ -85,12 +100,12 @@ function softwareUpdateVerdict({ running, channel, registry, notes, checkedAt })
|
|
|
85
100
|
error: null,
|
|
86
101
|
offered,
|
|
87
102
|
offered_published_at: offered ? published[offered] || null : null,
|
|
88
|
-
newest:
|
|
103
|
+
newest: admitted.reduce((a, v) => (compareSemver(v, a) > 0 ? v : a)),
|
|
89
104
|
whats_new: null,
|
|
90
105
|
};
|
|
91
106
|
if (!offered) return answer;
|
|
92
107
|
|
|
93
|
-
const inUpdate =
|
|
108
|
+
const inUpdate = admitted
|
|
94
109
|
.filter((v) => compareSemver(v, running) > 0 && compareSemver(v, offered) <= 0)
|
|
95
110
|
.sort((a, b) => compareSemver(b, a));
|
|
96
111
|
const shown = inUpdate.slice(0, VERSIONS_MAX);
|
|
@@ -115,14 +130,19 @@ function softwareUpdateVerdict({ running, channel, registry, notes, checkedAt })
|
|
|
115
130
|
* `get`/`now`/`store` pass through to the registry reader, so a test drives the real
|
|
116
131
|
* packument parsing, the real tarball stream and the real cache rather than a stub.
|
|
117
132
|
*/
|
|
118
|
-
async function readSoftwareUpdate({ running, channel, get, now = Date.now, store } = {}) {
|
|
133
|
+
async function readSoftwareUpdate({ running, channel, follow, get, now = Date.now, store } = {}) {
|
|
119
134
|
const t = now();
|
|
135
|
+
const fol = normalizeFollow(follow);
|
|
120
136
|
const { registry, notes } = await readRegistry({
|
|
121
137
|
pkg: CORE_PKG, get, now, store,
|
|
122
|
-
// Nothing to describe when nothing
|
|
123
|
-
haveNotesFor: (
|
|
138
|
+
// Nothing to describe when nothing this project could take is newer: skip the download.
|
|
139
|
+
haveNotesFor: (_newest, facts) => {
|
|
140
|
+
if (!VERSION_RE.test(String(running || ''))) return true;
|
|
141
|
+
const admitted = admittedVersions(fol, facts);
|
|
142
|
+
return !admitted.some((v) => compareSemver(v, running) > 0);
|
|
143
|
+
},
|
|
124
144
|
});
|
|
125
|
-
return softwareUpdateVerdict({ running, channel, registry, notes, checkedAt: new Date(t).toISOString() });
|
|
145
|
+
return softwareUpdateVerdict({ running, channel, follow: fol, registry, notes, checkedAt: new Date(t).toISOString() });
|
|
126
146
|
}
|
|
127
147
|
|
|
128
148
|
module.exports = { readSoftwareUpdate };
|
package/src/module-api.js
CHANGED
|
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
|
|
|
81
81
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
82
82
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
83
83
|
|
|
84
|
-
const CORE_VERSION = '1.24.
|
|
84
|
+
const CORE_VERSION = '1.24.26'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
85
85
|
|
|
86
86
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
87
87
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|