@bongos/core 1.20.77 → 1.20.79
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 +83 -58
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +3 -0
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/adr/0136-update-channel-subscription-policy.md +1 -1
- package/docs/adr/0360-bongos-follows-every-candidate-every-other-project-follows-releases.md +66 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +70 -3
- package/docs/api-reference.md +3 -2
- package/docs/copy-inventory.md +22 -14
- package/docs/copy-registry.json +125 -41
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +5 -1
- package/docs/page-readings.json +150 -137
- package/docs/recipes/upgrading-the-core.md +11 -2
- package/modules/copy-desk/artist-stats.js +15 -1
- package/modules/copy-desk/module.json +1 -1
- package/modules/copy-desk/page-status.js +41 -0
- package/modules/copy-desk/routes/copy-desk-artist.js +44 -0
- package/modules/copy-desk/tests/copy_no_cms.mjs +11 -2
- package/modules/hall-ui/public/deploy.states.json +1 -1
- package/modules/hall-ui/public/profile-crafts.js +231 -0
- package/modules/hall-ui/public/profile.css +60 -0
- package/modules/hall-ui/public/profile.html +31 -12
- package/modules/hall-ui/public/profile.js +180 -75
- package/modules/hall-ui/public/profile.states.json +34 -0
- package/modules/hall-ui/public/sky-draw.js +66 -39
- package/modules/hall-ui/public/sky-interaction.js +1 -1
- package/modules/hall-ui/public/sky-panel.js +1 -1
- package/modules/hall-ui/public/thinking.css +52 -24
- package/modules/hall-ui/records/profile-roles.md +18 -0
- package/modules/npm-release/public/work.js +72 -0
- package/modules/npm-release/work.js +67 -1
- package/modules/provisioning/render-standup.js +2 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +24 -0
- package/scripts/gds/init.js +16 -6
- package/scripts/gds/update-channel.js +68 -1
- package/scripts/gds/update-sweep.js +100 -4
- package/scripts/migrate.sh +6 -2
- package/src/module-api.js +1 -1
- package/tests/hall_sky.mjs +77 -0
- package/tests/helpers.mjs +32 -7
- package/tests/init.mjs +21 -5
- package/tests/migrate_applied_check.mjs +94 -0
- package/tests/npm_release_page.mjs +67 -0
- package/tests/npm_release_work.mjs +82 -0
- package/tests/profile_activity.mjs +23 -15
- package/tests/profile_role_headline.mjs +4 -4
- package/tests/profile_role_tabs.mjs +163 -0
- package/tests/update_channel.mjs +80 -1
- package/tests/update_subscription_engine.mjs +99 -0
- package/tests/upgrade_migrate.mjs +1 -1
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
if (!h || h.kind === 'shuttle') { card.classList.remove('on'); card.setAttribute('aria-hidden', 'true'); return; }
|
|
32
32
|
const { goalById } = S; let html = '';
|
|
33
33
|
if (h.kind === 'idea') { const i = h.ref; const o = OBJ[i.st] || { noun: i.st, word: i.st }; const wellG = i.well && goalById.get(i.well); const r = reach(i).length; html = `<div class="k">${glyphHtml(i)}<span class="lbl${i.st === 'star' ? ' hot' : ''}">${o.noun} · ${o.word}${i.origin === 'claude' ? ' · filed by a session' : ''}</span></div><div class="t">${esc(i.title)}</div><div class="m">${i.shaped ? `<b>${i.shaped.kind === 'goal' ? 'became this goal' : 'became a ring'}</b> · ` : ''}${wellG ? `caught by <b>${esc(short(wellG.title))}</b>` : `open space · ${Math.round(i.age)} days adrift`} · ${esc(became(i))}${i.size && i.size.credits ? ` · size <b>${i.size.credits}</b>` : ''}${i.lit < 1 ? ` · <b>${Math.round(i.lit * 100)}%</b> alive` : ''}${r ? ' · its reach is drawn' : ''}</div>`; }
|
|
34
|
-
else if (h.kind === 'ring') { const c = h.ref; const g = h.goal; html = `<div class="k"><span class="mk well" style="${c.satisfied ? 'border-style:solid;border-color:rgba(
|
|
34
|
+
else if (h.kind === 'ring') { const c = h.ref; const g = h.goal; html = `<div class="k"><span class="mk well" style="${c.satisfied ? 'border-style:solid;border-color:rgba(var(--sky-ink-rgb),.7)' : ''}"></span><span class="lbl">ring ${h.index + 1} of ${g.crit.length} · ${c.satisfied ? 'closed' : 'open'}</span></div><div class="t">${esc(SKY.plain(c.text).slice(0, 180))}${SKY.plain(c.text).length > 180 ? '…' : ''}</div><div class="m">${c.tasks.length ? `<b>${c.tasks.length}</b> task${c.tasks.length === 1 ? '' : 's'} on this ring · <b>${c.credits}</b> credits` : 'no task gates it yet'}${c.uatLabel ? ` · ${esc(c.uatLabel)}` : (c.pendingReview && !c.uat ? ' · awaiting review' : '')}${c.backendOnly ? ' · backend-only' : ''}${c.bornOf ? ` · <b style="color:var(--accent)">born of a thought</b>: ${esc(ideaTitle(c.bornOf))}` : ''}</div>`; }
|
|
35
35
|
else if (h.kind === 'goal') { const g = h.ref; if (!seen(g)) html = `<div class="k"><span class="mk unseen"></span><span class="lbl">unexplored</span></div><div class="t">Something is here.</div><div class="m">Follow a route from a goal you know to reach it — or turn explore off to see everything.</div>`; else { const met = g.crit.filter((c) => c.satisfied).length; const state = g.status === 'open' ? (g.live > 0 ? 'gravity well · alive' : 'gravity well') : g.status === 'achieved' ? 'settled system' : 'collapsed'; html = `<div class="k"><span class="mk ${g.status === 'open' ? (g.live > 0 ? 'alive' : 'well') : g.status === 'achieved' ? 'settled' : 'collapsed'}"></span><span class="lbl${g.live > 0 ? ' hot' : ''}">${state} · ${esc(g.band.label)}</span></div><div class="t">${esc(g.title)}</div><div class="m"><b>${g.taskDone}</b> of <b>${g.taskTotal}</b> tasks${g.credits ? ` · <b>${g.credits}</b> credits` : ''} · ${g.crit.length ? `<b>${met}</b> of <b>${g.crit.length}</b> rings closed` : 'no rings written'}${g.live > 0 ? ` · <b>${g.active}</b> held now, <b>${g.recent}</b> landed this week` : ''}${g.thoughts.filter(inLayer).length ? ` · <b>${g.thoughts.filter(inLayer).length}</b> of your thoughts` : ''}${g.bornOf ? ` · <b style="color:var(--accent)">born of a thought</b>` : ''}${edgesOf(g).length ? ` · <b>${edgesOf(g).length}</b> route${edgesOf(g).length === 1 ? '' : 's'}` : ''}</div>`; } }
|
|
36
36
|
else if (h.kind === 'task' || h.kind === 'ltask') { const t = h.ref; const kin = t.goal && t.kin ? t.goal.tasks.filter((x) => x.kin === t.kin && x !== t).length : 0; html = `<div class="k"><span class="mk task${t.status === 'active' ? ' live' : ''}"></span><span class="lbl${t.status === 'active' ? ' hot' : ''}">task · ${esc(t.status)}</span></div><div class="t">${esc(t.title)}</div><div class="m">${t.credits != null ? `<b>${t.credits}</b> credits · ` : ''}${t.ci >= 0 ? `ring ${t.ci + 1}` : 'outer orbit'}${t.sourceRef ? ' · born of a thought' : ''}${kin ? ` · lines up with <b>${kin}</b> kin` : ''}${t.drift ? ' · in motion' : ''}</div>`; }
|
|
37
37
|
else if (h.kind === 'stub') { const e = h.edge; const waits = e.a === h.goal; html = `<div class="k"><span class="mk route"></span><span class="lbl chart">route · ${waits ? 'waits on' : 'waited on by'}</span></div><div class="t">${seen(h.other) ? esc(h.other.title) : 'an unexplored goal'}</div><div class="m"><b>${e.count}</b> task dependenc${e.count === 1 ? 'y' : 'ies'} · click to chart it · double-click to chart every route from here</div>`; }
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
${rch.length ? fact('lives on in', rch.map((g) => `<a href="#" data-goal="${esc(g.id)}">${esc(short(g.title))}</a>`).join(', ')) : ''}
|
|
40
40
|
${fact('size — credits', i.size ? `${i.size.credits}${i.size.historical ? ' <span class="lbl">hist.</span>' : ''}` : 'unknown', true)}
|
|
41
41
|
${i.size ? fact('of which', esc(sizeWords(i))) : ''}
|
|
42
|
-
${bright ? fact('brightness — still alive', `<span class="bar" style="width:${Math.max(4, Math.round(i.lit * 70))}px;${i.lit > 0 ? '' : 'background:rgba(
|
|
42
|
+
${bright ? fact('brightness — still alive', `<span class="bar" style="width:${Math.max(4, Math.round(i.lit * 70))}px;${i.lit > 0 ? '' : 'background:rgba(var(--sky-ink-rgb),.2)'}"></span>${esc(bright)}`) : ''}
|
|
43
43
|
${pos ? fact('position', esc(pos)) : ''}
|
|
44
44
|
${fact('filed', esc(fmtDate(i.capturedAt)) + (i.kind ? ` · ${esc(i.kind)}` : '') + (i.grade === 'full' ? ' · full idea' : '') + ' · ' + esc(SKY.viaWord(i.filedVia)) + (i.origin === 'claude' ? ' · by a session, under your name' : ''))}
|
|
45
45
|
</div>
|
|
@@ -24,11 +24,39 @@
|
|
|
24
24
|
body[data-page] .sky-stage {
|
|
25
25
|
color-scheme: dark;
|
|
26
26
|
--void: #000000; --ink: #ffffff; --ink-soft: #c0c0c0; --ink-faint: #a8a8a8; --ink-dim: #65656a; --ink-ghost: #3a3a3f;
|
|
27
|
-
--rule: rgba(
|
|
27
|
+
--rule: rgba(var(--sky-ink-rgb),.16); --rule-soft: rgba(var(--sky-ink-rgb),.08); --glass: rgba(var(--sky-ink-rgb),.075); --glass-strong: rgba(var(--sky-ink-rgb),.14);
|
|
28
28
|
--accent: #fb9569; --accent-on: #2a1305; --chart: #f2d27a;
|
|
29
|
+
--sky-ink-rgb: 255,255,255; --sky-void-rgb: 0,0,0; --sky-accent-rgb: 251,149,105; --sky-chart-rgb: 242,210,122;
|
|
30
|
+
--sky-cold: #141418; --sky-cold-rgb: 20,20,24; --sky-drop: rgba(0,0,0,.6); --sky-temp: 1;
|
|
29
31
|
--label: 400 11px/1.2 "JetBrains Mono", ui-monospace, Consolas, monospace; --mono: 400 13px/1.4 "JetBrains Mono", ui-monospace, Consolas, monospace;
|
|
30
32
|
--face: Manrope, "Segoe UI", system-ui, -apple-system, sans-serif;
|
|
31
33
|
}
|
|
34
|
+
/* LIGHT (task 1004500, owner ask 2026-10-01). The sky follows the hall's theme:
|
|
35
|
+
the void becomes paper and the white ladder becomes an ink ladder at the SAME
|
|
36
|
+
alphas, so every mark keeps its place in the hierarchy; the accent and the
|
|
37
|
+
chart yellow darken until they read on paper. Dark above is the template's,
|
|
38
|
+
untouched. Written twice for the same reason style.css writes dark twice:
|
|
39
|
+
html[data-theme] once shell.js runs, the media query for first paint.
|
|
40
|
+
sky-draw.js reads these channels (--sky-*-rgb) off the stage, so the canvas
|
|
41
|
+
repaints with the toggle. */
|
|
42
|
+
html[data-theme="light"] body[data-page] .sky-stage {
|
|
43
|
+
color-scheme: light;
|
|
44
|
+
--void: #f4f3f5; --ink: #17181a; --ink-soft: #3f4044; --ink-faint: #55565a; --ink-dim: #6c6d72; --ink-ghost: #a3a4a8;
|
|
45
|
+
--rule: rgba(23,24,26,.16); --rule-soft: rgba(23,24,26,.08); --glass: rgba(23,24,26,.06); --glass-strong: rgba(23,24,26,.11);
|
|
46
|
+
--accent: #b8501f; --accent-on: #ffffff; --chart: #8f6a0c;
|
|
47
|
+
--sky-ink-rgb: 23,24,26; --sky-void-rgb: 244,243,245; --sky-accent-rgb: 184,80,31; --sky-chart-rgb: 143,106,12;
|
|
48
|
+
--sky-cold: #dcdbde; --sky-cold-rgb: 220,219,222; --sky-drop: rgba(23,24,26,.14); --sky-temp: .42;
|
|
49
|
+
}
|
|
50
|
+
@media (prefers-color-scheme: light) {
|
|
51
|
+
html:not([data-theme]) body[data-page] .sky-stage {
|
|
52
|
+
color-scheme: light;
|
|
53
|
+
--void: #f4f3f5; --ink: #17181a; --ink-soft: #3f4044; --ink-faint: #55565a; --ink-dim: #6c6d72; --ink-ghost: #a3a4a8;
|
|
54
|
+
--rule: rgba(23,24,26,.16); --rule-soft: rgba(23,24,26,.08); --glass: rgba(23,24,26,.06); --glass-strong: rgba(23,24,26,.11);
|
|
55
|
+
--accent: #b8501f; --accent-on: #ffffff; --chart: #8f6a0c;
|
|
56
|
+
--sky-ink-rgb: 23,24,26; --sky-void-rgb: 244,243,245; --sky-accent-rgb: 184,80,31; --sky-chart-rgb: 143,106,12;
|
|
57
|
+
--sky-cold: #dcdbde; --sky-cold-rgb: 220,219,222; --sky-drop: rgba(23,24,26,.14); --sky-temp: .42;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
32
60
|
html, body { height: 100%; }
|
|
33
61
|
body[data-page] .sky-stage { margin: 0; background: var(--void); color: var(--ink); font-family: var(--face); font-size: 15px; line-height: 1.5; overflow: hidden; overflow: clip; -webkit-font-smoothing: antialiased; }
|
|
34
62
|
/* clip, not just hidden (task 1004342): a hidden-overflow box is still scrollable, so
|
|
@@ -100,10 +128,10 @@ html[data-fullscreen="on"] .sky-stage .top { padding-right: 184px; }
|
|
|
100
128
|
.pill[aria-pressed="true"]::before { content: ""; display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--accent); margin-right: 8px; vertical-align: 1px; }
|
|
101
129
|
.pill.icon { width: 36px; padding: 0; display: inline-grid; place-items: center; }
|
|
102
130
|
.pill.icon::before { display: none; }
|
|
103
|
-
.spot { outline: 2px solid var(--accent) !important; outline-offset: 4px; box-shadow: 0 0 0 8px rgba(
|
|
131
|
+
.spot { outline: 2px solid var(--accent) !important; outline-offset: 4px; box-shadow: 0 0 0 8px rgba(var(--sky-accent-rgb),.12), 0 0 40px rgba(var(--sky-accent-rgb),.35); }
|
|
104
132
|
|
|
105
133
|
.composer { position: fixed; left: 50%; top: calc(76px + env(safe-area-inset-top, 0px)); transform: translateX(-50%); width: min(620px, calc(100% - 32px)); z-index: 6; display: flex; flex-direction: column; gap: 8px; align-items: center; }
|
|
106
|
-
.composer form { display: flex; width: 100%; height: 48px; border-radius: 999px; background: rgba(
|
|
134
|
+
.composer form { display: flex; width: 100%; height: 48px; border-radius: 999px; background: rgba(var(--sky-void-rgb),.5); box-shadow: inset 0 0 0 1px var(--rule); backdrop-filter: blur(6px); align-items: center; padding: 0 6px 0 22px; gap: 8px; }
|
|
107
135
|
.composer form:focus-within { box-shadow: inset 0 0 0 1px var(--ink-faint); }
|
|
108
136
|
.composer input { flex: 1; min-width: 0; height: 100%; background: transparent; border: 0; color: var(--ink); font: 300 17px/1 var(--face); letter-spacing: -.01em; outline: none; }
|
|
109
137
|
.composer input::placeholder { color: var(--ink-dim); }
|
|
@@ -112,12 +140,12 @@ html[data-fullscreen="on"] .sky-stage .top { padding-right: 184px; }
|
|
|
112
140
|
.gravity { min-height: 18px; font: var(--mono); font-variant-numeric: tabular-nums; color: var(--ink-dim); text-align: center; }
|
|
113
141
|
.gravity b { font-weight: 400; color: var(--ink-soft); }
|
|
114
142
|
.gravity .hot { color: var(--accent); }
|
|
115
|
-
.receipt { display: flex; align-items: center; gap: 12px; height: 34px; padding: 0 8px 0 16px; border-radius: 999px; box-shadow: inset 0 0 0 1px var(--rule-soft); background: rgba(
|
|
116
|
-
.receipt .dot { width: 7px; height: 7px; border-radius: 50%; background: rgba(
|
|
143
|
+
.receipt { display: flex; align-items: center; gap: 12px; height: 34px; padding: 0 8px 0 16px; border-radius: 999px; box-shadow: inset 0 0 0 1px var(--rule-soft); background: rgba(var(--sky-void-rgb),.45); color: var(--ink-soft); font-size: 13px; }
|
|
144
|
+
.receipt .dot { width: 7px; height: 7px; border-radius: 50%; background: rgba(var(--sky-ink-rgb),.5); box-shadow: 0 0 0 4px rgba(var(--sky-ink-rgb),.05); }
|
|
117
145
|
.receipt button { border: 0; background: transparent; color: var(--ink-dim); cursor: pointer; height: 26px; padding: 0 8px; border-radius: 999px; font-size: 12px; white-space: nowrap; flex-shrink: 0; }
|
|
118
146
|
.receipt button:hover { color: var(--ink); background: var(--glass); }
|
|
119
147
|
|
|
120
|
-
.glass { background: rgba(
|
|
148
|
+
.glass { background: rgba(var(--sky-ink-rgb),.07); box-shadow: inset 0 0 0 1px rgba(var(--sky-ink-rgb),.14), inset 0 1px 0 rgba(var(--sky-ink-rgb),.12), 0 18px 40px var(--sky-drop); backdrop-filter: blur(14px) saturate(140%); -webkit-backdrop-filter: blur(14px) saturate(140%); }
|
|
121
149
|
.card { position: fixed; z-index: 7; max-width: 300px; padding: 12px 14px; border-radius: 14px; pointer-events: none; color: var(--ink); font-size: 13.5px; line-height: 1.4; display: flex; flex-direction: column; gap: 5px; opacity: 0; transform: translateY(4px); transition: opacity .12s ease, transform .12s ease; }
|
|
122
150
|
.card.on { opacity: 1; transform: none; }
|
|
123
151
|
.card .k { display: flex; align-items: center; gap: 8px; }
|
|
@@ -131,7 +159,7 @@ html[data-fullscreen="on"] .sky-stage .top { padding-right: 184px; }
|
|
|
131
159
|
.menu button { border: 0; background: transparent; color: var(--ink-soft); text-align: left; padding: 8px 10px; border-radius: 8px; font-size: 13.5px; cursor: pointer; }
|
|
132
160
|
.menu button:hover { background: var(--glass-strong); color: var(--ink); }
|
|
133
161
|
|
|
134
|
-
.panel { position: fixed; right: 20px; top: calc(140px + env(safe-area-inset-top, 0px)); bottom: calc(96px + env(safe-area-inset-bottom, 0px)); width: 340px; z-index: 5; display: flex; flex-direction: column; gap: 14px; padding: 22px 24px; border-radius: 16px; background: rgba(
|
|
162
|
+
.panel { position: fixed; right: 20px; top: calc(140px + env(safe-area-inset-top, 0px)); bottom: calc(96px + env(safe-area-inset-bottom, 0px)); width: 340px; z-index: 5; display: flex; flex-direction: column; gap: 14px; padding: 22px 24px; border-radius: 16px; background: rgba(var(--sky-void-rgb),.62); box-shadow: inset 0 0 0 1px var(--rule-soft); backdrop-filter: blur(8px); overflow: auto; }
|
|
135
163
|
.panel .kind { display: flex; align-items: center; gap: 12px; min-height: 28px; }
|
|
136
164
|
.panel .kind .glyph { width: 32px; display: grid; place-items: center; }
|
|
137
165
|
.panel h2 { margin: 0; font-size: 21px; font-weight: 300; line-height: 1.25; letter-spacing: -.02em; text-wrap: balance; }
|
|
@@ -157,7 +185,7 @@ html[data-fullscreen="on"] .sky-stage .top { padding-right: 184px; }
|
|
|
157
185
|
.orbit-list .st { font: var(--label); letter-spacing: .1em; text-transform: uppercase; color: var(--ink-dim); flex-shrink: 0; width: 72px; }
|
|
158
186
|
.orbit-list .st.live { color: var(--accent); }
|
|
159
187
|
|
|
160
|
-
.room { position: fixed; left: 20px; top: calc(140px + env(safe-area-inset-top, 0px)); bottom: calc(96px + env(safe-area-inset-bottom, 0px)); width: 440px; z-index: 5; display: flex; flex-direction: column; gap: 14px; padding: 22px 0 0; border-radius: 16px; background: rgba(
|
|
188
|
+
.room { position: fixed; left: 20px; top: calc(140px + env(safe-area-inset-top, 0px)); bottom: calc(96px + env(safe-area-inset-bottom, 0px)); width: 440px; z-index: 5; display: flex; flex-direction: column; gap: 14px; padding: 22px 0 0; border-radius: 16px; background: rgba(var(--sky-void-rgb),.62); box-shadow: inset 0 0 0 1px var(--rule-soft); backdrop-filter: blur(8px); }
|
|
161
189
|
.room .head { display: flex; align-items: baseline; gap: 14px; padding: 0 24px; }
|
|
162
190
|
.room .head h2 { margin: 0; font-size: 21px; font-weight: 300; letter-spacing: -.02em; }
|
|
163
191
|
.room .head .lbl { margin-left: auto; }
|
|
@@ -175,9 +203,9 @@ html[data-fullscreen="on"] .sky-stage .top { padding-right: 184px; }
|
|
|
175
203
|
.empty { padding: 20px 24px; color: var(--ink-dim); font-size: 14px; }
|
|
176
204
|
|
|
177
205
|
.jump { position: fixed; left: 20px; top: calc(76px + env(safe-area-inset-top, 0px)); z-index: 6; }
|
|
178
|
-
.jump input { width: 260px; height: 40px; border-radius: 999px; border: 0; box-shadow: inset 0 0 0 1px var(--rule-soft); background: rgba(
|
|
206
|
+
.jump input { width: 260px; height: 40px; border-radius: 999px; border: 0; box-shadow: inset 0 0 0 1px var(--rule-soft); background: rgba(var(--sky-void-rgb),.5); color: var(--ink); padding: 0 16px; font: 400 14px/1 var(--face); }
|
|
179
207
|
.jump input::placeholder { color: var(--ink-dim); }
|
|
180
|
-
.jump ul { list-style: none; margin: 6px 0 0; padding: 6px; border-radius: 12px; background: rgba(
|
|
208
|
+
.jump ul { list-style: none; margin: 6px 0 0; padding: 6px; border-radius: 12px; background: rgba(var(--sky-void-rgb),.8); box-shadow: inset 0 0 0 1px var(--rule-soft); max-height: 300px; overflow: auto; width: 360px; }
|
|
181
209
|
.jump li button { width: 100%; text-align: left; border: 0; background: transparent; color: var(--ink-soft); padding: 8px 10px; border-radius: 8px; font-size: 13.5px; cursor: pointer; display: flex; gap: 10px; align-items: baseline; }
|
|
182
210
|
.jump li button:hover, .jump li button:focus-visible { background: var(--glass); color: var(--ink); }
|
|
183
211
|
.jump li .lbl { flex-shrink: 0; width: 60px; }
|
|
@@ -185,7 +213,7 @@ html[data-fullscreen="on"] .sky-stage .top { padding-right: 184px; }
|
|
|
185
213
|
.tour, .start { position: fixed; left: 50%; bottom: calc(110px + env(safe-area-inset-bottom, 0px)); transform: translateX(-50%); width: min(560px, calc(100% - 32px)); z-index: 8; padding: 18px 22px 16px; border-radius: 16px; display: flex; flex-direction: column; gap: 10px; }
|
|
186
214
|
.tour .step { display: flex; align-items: center; gap: 10px; }
|
|
187
215
|
.tour .dots { display: flex; gap: 5px; margin-left: auto; }
|
|
188
|
-
.tour .dots i { width: 5px; height: 5px; border-radius: 50%; background: rgba(
|
|
216
|
+
.tour .dots i { width: 5px; height: 5px; border-radius: 50%; background: rgba(var(--sky-ink-rgb),.2); }
|
|
189
217
|
.tour .dots i.on { background: var(--accent); }
|
|
190
218
|
.tour h3, .start h3 { margin: 0; font-size: 19px; font-weight: 300; letter-spacing: -.02em; line-height: 1.25; }
|
|
191
219
|
.tour p, .start p { margin: 0; font-size: 14px; line-height: 1.5; color: var(--ink-soft); }
|
|
@@ -206,7 +234,7 @@ html[data-fullscreen="on"] .sky-stage .top { padding-right: 184px; }
|
|
|
206
234
|
.alt { margin-left: auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; text-align: right; color: var(--ink-dim); font: var(--mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
207
235
|
.alt b { color: var(--ink-soft); font-weight: 400; }
|
|
208
236
|
.hint { color: var(--ink-ghost); }
|
|
209
|
-
.time { display: flex; align-items: center; gap: 8px; width: min(360px, 100%); align-self: center; padding: 3px 4px 3px 4px; border-radius: 999px; background: rgba(
|
|
237
|
+
.time { display: flex; align-items: center; gap: 8px; width: min(360px, 100%); align-self: center; padding: 3px 4px 3px 4px; border-radius: 999px; background: rgba(var(--sky-void-rgb),.45); box-shadow: inset 0 0 0 1px var(--rule-soft); opacity: .8; }
|
|
210
238
|
.time:hover { opacity: 1; }
|
|
211
239
|
.time .play { width: 24px; height: 24px; border-radius: 50%; border: 0; background: var(--glass-strong); color: var(--ink); cursor: pointer; display: grid; place-items: center; font-size: 9px; flex-shrink: 0; }
|
|
212
240
|
.time .play[aria-pressed="true"] { background: var(--accent); color: var(--accent-on); }
|
|
@@ -218,7 +246,7 @@ html[data-fullscreen="on"] .sky-stage .top { padding-right: 184px; }
|
|
|
218
246
|
.composer .mode:hover { color: var(--ink); }
|
|
219
247
|
.composer .mode[aria-pressed="true"] { background: var(--glass-strong); color: var(--ink); }
|
|
220
248
|
.composer .mode[aria-pressed="true"]::before { content: ""; display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--accent); margin-right: 7px; vertical-align: 1px; }
|
|
221
|
-
.steps { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 10px; max-width: 100%; font-size: 13px; color: var(--ink-soft); padding: 5px 8px 5px 14px; border-radius: 999px; background: rgba(
|
|
249
|
+
.steps { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 10px; max-width: 100%; font-size: 13px; color: var(--ink-soft); padding: 5px 8px 5px 14px; border-radius: 999px; background: rgba(var(--sky-void-rgb),.5); box-shadow: inset 0 0 0 1px var(--rule-soft); }
|
|
222
250
|
.steps b { font: var(--label); letter-spacing: .14em; text-transform: uppercase; color: var(--accent); font-weight: 400; }
|
|
223
251
|
.steps .skip { border: 0; background: transparent; color: var(--ink-dim); cursor: pointer; font-size: 12px; height: 24px; padding: 0 8px; border-radius: 999px; }
|
|
224
252
|
.steps .skip:hover { color: var(--ink); background: var(--glass); }
|
|
@@ -227,18 +255,18 @@ html[data-fullscreen="on"] .sky-stage .top { padding-right: 184px; }
|
|
|
227
255
|
.mk { display: block; width: 9px; height: 9px; border-radius: 50%; box-sizing: border-box; }
|
|
228
256
|
.mk.star { background: var(--accent); box-shadow: 0 0 10px color-mix(in srgb, var(--accent) 75%, transparent); }
|
|
229
257
|
.mk.star.dim { opacity: .55; box-shadow: 0 0 4px color-mix(in srgb, var(--accent) 40%, transparent); }
|
|
230
|
-
.mk.protostar { width: 7px; height: 7px; background: rgba(
|
|
231
|
-
.mk.satellite { width: 6px; height: 6px; background: rgba(
|
|
232
|
-
.mk.remnant { width: 12px; height: 12px; background: transparent; border: 1px solid rgba(
|
|
233
|
-
.mk.cold { width: 6px; height: 6px; background:
|
|
234
|
-
.mk.well { width: 14px; height: 14px; background: transparent; border: 1px dashed rgba(
|
|
235
|
-
.mk.alive { width: 14px; height: 14px; background: transparent; border: 1px solid rgba(
|
|
236
|
-
.mk.settled { width: 12px; height: 12px; background: transparent; border: 1px solid rgba(
|
|
237
|
-
.mk.collapsed { width: 9px; height: 9px; background:
|
|
238
|
-
.mk.unseen { width: 12px; height: 12px; background: transparent; border: 1px dashed rgba(
|
|
239
|
-
.mk.task { width: 4px; height: 4px; background: rgba(
|
|
258
|
+
.mk.protostar { width: 7px; height: 7px; background: rgba(var(--sky-ink-rgb),.5); box-shadow: 0 0 0 4px rgba(var(--sky-ink-rgb),.05); }
|
|
259
|
+
.mk.satellite { width: 6px; height: 6px; background: rgba(var(--sky-ink-rgb),.4); outline: 1px solid rgba(var(--sky-ink-rgb),.16); outline-offset: 3px; }
|
|
260
|
+
.mk.remnant { width: 12px; height: 12px; background: transparent; border: 1px solid rgba(var(--sky-ink-rgb),.4); }
|
|
261
|
+
.mk.cold { width: 6px; height: 6px; background: var(--sky-cold); border: 1px solid rgba(var(--sky-ink-rgb),.18); }
|
|
262
|
+
.mk.well { width: 14px; height: 14px; background: transparent; border: 1px dashed rgba(var(--sky-ink-rgb),.35); }
|
|
263
|
+
.mk.alive { width: 14px; height: 14px; background: transparent; border: 1px solid rgba(var(--sky-ink-rgb),.35); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent), 0 0 10px var(--accent); }
|
|
264
|
+
.mk.settled { width: 12px; height: 12px; background: transparent; border: 1px solid rgba(var(--sky-ink-rgb),.5); box-shadow: inset 0 0 0 3px var(--void), inset 0 0 0 4px rgba(var(--sky-ink-rgb),.35), 0 0 8px rgba(var(--sky-ink-rgb),.25); }
|
|
265
|
+
.mk.collapsed { width: 9px; height: 9px; background: var(--void); border: 1px solid rgba(var(--sky-ink-rgb),.3); box-shadow: 0 0 0 2px rgba(var(--sky-void-rgb),1), 0 0 0 3px rgba(var(--sky-ink-rgb),.08); }
|
|
266
|
+
.mk.unseen { width: 12px; height: 12px; background: transparent; border: 1px dashed rgba(var(--sky-ink-rgb),.2); }
|
|
267
|
+
.mk.task { width: 4px; height: 4px; background: rgba(var(--sky-ink-rgb),.6); }
|
|
240
268
|
.mk.task.live { background: var(--accent); box-shadow: 0 0 6px var(--accent); }
|
|
241
|
-
.mk.born { width: 10px; height: 10px; background: transparent; border: 1px solid var(--accent); box-shadow: inset 0 0 0 2px
|
|
269
|
+
.mk.born { width: 10px; height: 10px; background: transparent; border: 1px solid var(--accent); box-shadow: inset 0 0 0 2px var(--void), inset 0 0 0 5px var(--accent); }
|
|
242
270
|
.mk.route { width: 14px; height: 2px; border-radius: 1px; background: var(--chart); box-shadow: 0 0 6px var(--chart); }
|
|
243
271
|
|
|
244
272
|
@media (max-width: 900px) {
|
|
@@ -30,3 +30,21 @@ Three fixtures, one per craft: `builders__profile` (the engineer, id 95, the vie
|
|
|
30
30
|
- The engineer tile "works shipped" has no "across N goals" foot, the ideator's "thoughts filed" no "since <month>" foot, the artist's "credits from pages" no "N a page" foot, and the surface rows no "where" sub-label: none of those is in the read.
|
|
31
31
|
- **Karma ends every artist's and ideator's set of tiles**, which the mock does not draw. The owner's call, reviewing this build on 2026-10-01: "karma for artists and ideators is definitely key". The engineer's set carries it already.
|
|
32
32
|
- The tiles' grid fits at 148px rather than 170px, so five tiles sit on one row inside the hall's narrower panel.
|
|
33
|
+
|
|
34
|
+
## The Activity tabs, one per role (task 1004435, WA6.RP05)
|
|
35
|
+
|
|
36
|
+
The section used to offer All / Engineer / Artist / Ideator / UI tabs over one task ledger, so an ideator's tab listed tasks whose discipline happened to be "ideator" and an artist's listed only artist-labelled tasks they had shipped. Now there is **one tab per role the person has** (`roles.shown`, the header's chips, main first), each counted the way the header leads, each its craft's own record, each with its own panel:
|
|
37
|
+
|
|
38
|
+
- **Engineer**: the works completed and goals created ledgers, WHOLE (no craft filter: most of the corpus is unclassified and a filter reads as "you did less").
|
|
39
|
+
- **Ideator**: the room's public trail, `public/profile-crafts.js`. One read of up to the route's cap of thoughts (`GET /inbox/by-builder/:id`, which already carried each thought's `state` and `size`), drawn as the sky's five marks by the day each was filed (size = diameter, brightness = a star's opacity, a folded-in thought tethered to the one it joined), the legend in the sky's words from `OTBIdeaObjects`, and every drawn thought a row in the ledger under it: state, size (~ for a historical estimate), still alive, and the old trail's "what became of it" cell folded in whole. A mark and its row light together on hover or focus; clicking a mark focuses its row.
|
|
40
|
+
- **Artist**: the pages they rewrote in Tweak Mode that were approved, four at a time with "Show N more", from `GET /copy-desk/artists/:id/pages` (signed in; copy-desk's `composeArtistPages`). Each row: page, surface, approval day, changed-line count, and a before/after: the round's first changed line beside the approval renders (`/tasks/:id/visuals`, `before-` and `after-desktop-light`) as small thumbnails.
|
|
41
|
+
|
|
42
|
+
Signed out, the section is one sign-in line (owner decision (a)).
|
|
43
|
+
|
|
44
|
+
### Deliberate differences from the mock (RP05)
|
|
45
|
+
|
|
46
|
+
- **The tab labels are lowercase**, the hall's own `.setup__tab` style; the mock capitalises them. The live hall's chrome wins over a mock's (the blend rule).
|
|
47
|
+
- **"What became of it" is the existing cell**, with its links, descent and triage note, not the mock's short phrase: the task asked to fold the old trail in, not to lose what it said.
|
|
48
|
+
- **The whole after line is highlighted**, where the mock highlights only the changed words: the batch stores whole lines, not the changed span.
|
|
49
|
+
- **The artist tab's lede does not say what each page paid**: the pay per page is not in the read.
|
|
50
|
+
- **The trail's window** is the span of the person's own thoughts (at least a month), with a line at each month, rather than the mock's fixed three months.
|
|
@@ -88,6 +88,77 @@
|
|
|
88
88
|
</li>`;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
// ---- this hall's own upgrades (task 1004297) ----------------------------------------
|
|
92
|
+
//
|
|
93
|
+
// The Bongos hall takes every new version by itself, so the page has to say two things the
|
|
94
|
+
// stages cannot: whether that is still happening, and whether a version was tried here and
|
|
95
|
+
// rolled back. Both come off the server's reading; nothing is inferred here.
|
|
96
|
+
const HOW = {
|
|
97
|
+
door: 'Upgrade pressed on /deploy',
|
|
98
|
+
sweep: 'the automatic update',
|
|
99
|
+
server: 'on the server',
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
function ago(iso) {
|
|
103
|
+
const t = Date.parse(iso);
|
|
104
|
+
if (!Number.isFinite(t)) return 'a while ago';
|
|
105
|
+
const mins = Math.max(0, Math.round((Date.now() - t) / 60000));
|
|
106
|
+
if (mins < 1) return 'just now';
|
|
107
|
+
if (mins < 60) return `${mins} minute${mins === 1 ? '' : 's'} ago`;
|
|
108
|
+
const hours = Math.round(mins / 60);
|
|
109
|
+
if (hours < 48) return `${hours} hour${hours === 1 ? '' : 's'} ago`;
|
|
110
|
+
return `${Math.round(hours / 24)} days ago`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// A stalled wait is the automation having stopped, and gets the warning box; an ordinary one
|
|
114
|
+
// (published minutes ago) is one quiet sentence, because it is the system working.
|
|
115
|
+
function waitingHtml(w) {
|
|
116
|
+
if (!w || !w.first) return '';
|
|
117
|
+
const more = w.versions > 1 ? ` (and ${w.versions - 1} newer)` : '';
|
|
118
|
+
const since = w.since ? ` ${ago(w.since)}` : '';
|
|
119
|
+
if (w.stalled) {
|
|
120
|
+
return `<div class="ov-note ov-note--warn"><p><strong>The automatic update may have stopped.</strong> ${escapeHtml(w.first)}${escapeHtml(more)} was published${escapeHtml(since)} and this hall has not installed it. Each new version should reach this hall by itself within about fifteen minutes of being published.</p></div>`;
|
|
121
|
+
}
|
|
122
|
+
return `<p class="ov-row__sub">${escapeHtml(w.first)}${escapeHtml(more)} was published${escapeHtml(since)}; this hall takes it on its next automatic update.</p>`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function upgradeRow(u) {
|
|
126
|
+
const facts = [`<span class="ov-fact">${escapeHtml(HOW[u.how] || HOW.server)}</span>`];
|
|
127
|
+
if (u.rolled_back) facts.unshift('<span class="ov-fact"><b>rolled back</b></span>');
|
|
128
|
+
const move = `${u.from || '(none)'} → ${u.to || '?'}`;
|
|
129
|
+
return `<li class="ov-row ov-row--dense${u.rolled_back ? ' nr-upgrade--back' : ''}">
|
|
130
|
+
<div class="ov-row__main">
|
|
131
|
+
<span class="ov-row__title ov-key">${escapeHtml(u.rolled_back ? `Tried ${move}` : move)}</span>
|
|
132
|
+
${u.rolled_back ? `<p class="ov-row__sub">${escapeHtml(u.reason ? `It did not come back healthy (${unDash(u.reason)}), so the hall went back to ${u.from || 'the version before'}.` : `It did not come back healthy, so the hall went back to ${u.from || 'the version before'}.`)}</p>` : ''}
|
|
133
|
+
<div class="ov-row__facts">${facts.join('')}</div>
|
|
134
|
+
</div>
|
|
135
|
+
<div class="ov-row__right">${when(u.at)}</div>
|
|
136
|
+
</li>`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function upgradesHtml(d) {
|
|
140
|
+
const u = d.upgrades;
|
|
141
|
+
if (!u) return '';
|
|
142
|
+
let body;
|
|
143
|
+
if (!u.ok) body = `<p class="ov-row__sub">${escapeHtml(u.error || 'This hall\'s upgrade ledger could not be read')}.</p>`;
|
|
144
|
+
else if (!u.rows.length) body = '<p class="ov-row__sub">No upgrade is recorded on this hall yet.</p>';
|
|
145
|
+
else body = `<ul class="ov-rows">${u.rows.map(upgradeRow).join('')}</ul>`;
|
|
146
|
+
const latest = u.ok && u.rows[0];
|
|
147
|
+
const warn = latest && latest.rolled_back
|
|
148
|
+
? `<div class="ov-note ov-note--warn"><p><strong>The last upgrade here was rolled back.</strong> ${escapeHtml(latest.to || 'That version')} was installed, did not come back healthy, and was undone. This hall will not try it again for a day; the next newer version is installed as soon as it is published.</p></div>`
|
|
149
|
+
: '';
|
|
150
|
+
return `<div class="ov-group" data-upgrades>
|
|
151
|
+
<div class="ov-group__head">
|
|
152
|
+
<span class="ov-group__name">Upgrades on this hall</span>
|
|
153
|
+
${u.ok ? `<span class="ov-group__n">${u.rows.length}</span>` : ''}
|
|
154
|
+
</div>
|
|
155
|
+
<p class="ov-row__sub">Each new version should reach this hall by itself within about fifteen minutes of being published, checked that the site came back healthy, and rolled back if it did not.</p>
|
|
156
|
+
${warn}
|
|
157
|
+
${warn ? '' : waitingHtml(d.waiting)}
|
|
158
|
+
${body}
|
|
159
|
+
</div>`;
|
|
160
|
+
}
|
|
161
|
+
|
|
91
162
|
function moreButton(key, total, shown) {
|
|
92
163
|
return total > shown ? `<button type="button" class="ov-toggle" data-expand="${escapeHtml(key)}">Show all ${total}</button>` : '';
|
|
93
164
|
}
|
|
@@ -251,6 +322,7 @@
|
|
|
251
322
|
${problemsHtml(data)}
|
|
252
323
|
${figsHtml(stages, data)}
|
|
253
324
|
${groups || '<p class="ov-row__sub">Nothing was finished in this window.</p>'}
|
|
325
|
+
${upgradesHtml(data)}
|
|
254
326
|
<p class="ov-foot">read ${escapeHtml(new Date(data.checked_at).toLocaleTimeString())} · ${escapeHtml(data.package)} · release notes from ${escapeHtml(data.notes.source ? `${data.notes.source.from === 'package' ? 'the published' : 'this hall\'s'} ${data.notes.source.version} package` : 'nowhere')}</p>`;
|
|
255
327
|
work.hidden = false;
|
|
256
328
|
if (vers) {
|
|
@@ -189,6 +189,66 @@ async function taskTitles(pool, ids) {
|
|
|
189
189
|
return new Map(rows.map((r) => [String(r.id), r.title]));
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
// THIS HALL'S OWN UPGRADES (task 1004297) — the core_upgrades ledger `bongos upgrade` writes on
|
|
193
|
+
// every move and every rollback, newest first. The Bongos hall takes each new version by itself
|
|
194
|
+
// now, so this list is the record of that happening — and a rolled-back row is the one thing on
|
|
195
|
+
// this page that says a version was tried here and refused. Fail-soft: an unreadable ledger is
|
|
196
|
+
// reported as unreadable, never as "no upgrades".
|
|
197
|
+
//
|
|
198
|
+
// HOW IT WAS MOVED (`how`: a method, never a person's name) is only as precise as the ledger is: 'owner-control' is the Upgrade button on
|
|
199
|
+
// /deploy; a row the lane noted as `auto-subscription` is the unattended sweep; anything else was
|
|
200
|
+
// started on the server, by the sweep through go-live or by a person at a shell, and the ledger
|
|
201
|
+
// does not tell those two apart — so the page says "on the server" rather than guess.
|
|
202
|
+
const UPGRADES_SHOWN = 8;
|
|
203
|
+
async function recentUpgrades(pool) {
|
|
204
|
+
try {
|
|
205
|
+
const { rows } = await pool.query(
|
|
206
|
+
`SELECT from_version, to_version, applied_at, source, note
|
|
207
|
+
FROM core_upgrades
|
|
208
|
+
ORDER BY applied_at DESC, id DESC
|
|
209
|
+
LIMIT $1`,
|
|
210
|
+
[UPGRADES_SHOWN]
|
|
211
|
+
);
|
|
212
|
+
return {
|
|
213
|
+
ok: true,
|
|
214
|
+
rows: rows.map((r) => {
|
|
215
|
+
const note = r.note == null ? '' : String(r.note);
|
|
216
|
+
const rolledBack = /^rolled_back:/.test(note);
|
|
217
|
+
const how = r.source === 'owner-control' ? 'door'
|
|
218
|
+
: /auto-subscription/.test(note) ? 'sweep'
|
|
219
|
+
: 'server';
|
|
220
|
+
return {
|
|
221
|
+
from: r.from_version || null,
|
|
222
|
+
to: r.to_version || null,
|
|
223
|
+
at: r.applied_at ? new Date(r.applied_at).toISOString() : null,
|
|
224
|
+
how,
|
|
225
|
+
rolled_back: rolledBack,
|
|
226
|
+
// "rolled_back: <reason> (attempted A → B, restored C)" — the reason, bounded.
|
|
227
|
+
reason: rolledBack ? boundText(note.replace(/^rolled_back:\s*/, '').replace(/\s*\(attempted[^)]*\)\s*$/, '')) : null,
|
|
228
|
+
};
|
|
229
|
+
}),
|
|
230
|
+
};
|
|
231
|
+
} catch (e) {
|
|
232
|
+
return { ok: false, error: `this hall's upgrade ledger could not be read (${e.message})` };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// HOW LONG THE NEWEST VERSION HAS BEEN WAITING HERE (task 1004297). The Bongos hall takes every
|
|
237
|
+
// new version by itself, within one sweep of it being published; a version that has waited longer
|
|
238
|
+
// than twice that is the automation having stopped, and the page says so rather than letting it
|
|
239
|
+
// look like an ordinary queue. The sweep's period is host configuration (15 minutes, declared in
|
|
240
|
+
// config/scheduled-routines.json); this is the threshold, not a reading of it.
|
|
241
|
+
const WAIT_ALARM_MIN = 30;
|
|
242
|
+
function waitingHere({ live, versions, published, now }) {
|
|
243
|
+
if (!live || !Array.isArray(versions)) return null;
|
|
244
|
+
const newer = versions.filter((v) => compareVersions(v, live) > 0).sort(compareVersions);
|
|
245
|
+
if (!newer.length) return null;
|
|
246
|
+
const first = newer[0];
|
|
247
|
+
const since = published[first] ? Date.parse(published[first]) : NaN;
|
|
248
|
+
const minutes = Number.isFinite(since) ? Math.max(0, Math.round((now - since) / 60000)) : null;
|
|
249
|
+
return { versions: newer.length, first, since: Number.isFinite(since) ? new Date(since).toISOString() : null, minutes, stalled: minutes != null && minutes > WAIT_ALARM_MIN };
|
|
250
|
+
}
|
|
251
|
+
|
|
192
252
|
function clampInt(v, dflt, max) {
|
|
193
253
|
const n = Number.parseInt(v, 10);
|
|
194
254
|
return Number.isInteger(n) && n > 0 ? Math.min(n, max) : dflt;
|
|
@@ -256,9 +316,11 @@ async function readWork({
|
|
|
256
316
|
for (const v of hist.versions) for (const e of v.entries || []) if (e.id && !e.title) e.title = more.get(e.id) || null;
|
|
257
317
|
}
|
|
258
318
|
|
|
319
|
+
const at = now ? now() : Date.now();
|
|
320
|
+
const isCore = pkg === CORE_PACKAGE;
|
|
259
321
|
return {
|
|
260
322
|
package: pkg,
|
|
261
|
-
checked_at: new Date(
|
|
323
|
+
checked_at: new Date(at).toISOString(),
|
|
262
324
|
live: liveVersion,
|
|
263
325
|
released,
|
|
264
326
|
newest: registry.ok ? registry.newest : null,
|
|
@@ -267,6 +329,10 @@ async function readWork({
|
|
|
267
329
|
window_days: windowDays,
|
|
268
330
|
stages,
|
|
269
331
|
history: hist,
|
|
332
|
+
// Only the core's page has a "this hall" to have upgraded: another package is not what this
|
|
333
|
+
// server runs.
|
|
334
|
+
upgrades: isCore ? await recentUpgrades(pool) : null,
|
|
335
|
+
waiting: isCore && registry.ok ? waitingHere({ live: liveVersion, versions: registry.versions, published, now: at }) : null,
|
|
270
336
|
};
|
|
271
337
|
}
|
|
272
338
|
|
|
@@ -57,7 +57,8 @@ const SERVICE_RE = /^srv-[a-z0-9]{8,64}$/;
|
|
|
57
57
|
// The hall is never the app (ADR 0345 D1). A command that runs the Bongos core itself puts
|
|
58
58
|
// a second, database-less copy of the project's hall on Render, where it never answers and
|
|
59
59
|
// the deploy times out — measured live on project 43 (task 1004483). It cannot see through
|
|
60
|
-
//
|
|
60
|
+
// `npm start`, so the scaffold no longer puts the hall there: it is `npm run hall`, and
|
|
61
|
+
// `npm start` is left for the app (task 1004484, scripts/gds/init.js).
|
|
61
62
|
const HALL_COMMAND_RE = /platform-server\.js|node_modules[\\/]@bongos[\\/]core/i;
|
|
62
63
|
const HALL_REFUSAL = 'That command starts the project’s Builders’ hall, which we already host — not your app. Give the command that starts your app.';
|
|
63
64
|
function badRequest(body) {
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.79",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.20.
|
|
9
|
+
"version": "1.20.79",
|
|
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.20.
|
|
3
|
+
"version": "1.20.79",
|
|
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
|
@@ -8591,5 +8591,29 @@
|
|
|
8591
8591
|
"id": "1004419",
|
|
8592
8592
|
"text": "New founders now answer the last three planet questions: how many people will build (with a growth guess they can adjust), how builders might be rewarded (skipped for someone building alone), and how much the platform should r"
|
|
8593
8593
|
}
|
|
8594
|
+
],
|
|
8595
|
+
"1.20.78": [
|
|
8596
|
+
{
|
|
8597
|
+
"id": "1004500",
|
|
8598
|
+
"text": "Your thinking and the project sky now have a light mode. When the hall is set to light, the sky turns to paper with ink-drawn rings and thoughts and light panels; switch to dark and the black night sky comes back exactly as be"
|
|
8599
|
+
},
|
|
8600
|
+
{
|
|
8601
|
+
"id": "1004435",
|
|
8602
|
+
"text": "A profile's Activity section now has one tab for each role the person has, each with a count. The engineer tab lists their finished work and the goals they created. The ideator tab draws their ideas as the sky trail, with a li"
|
|
8603
|
+
},
|
|
8604
|
+
{
|
|
8605
|
+
"id": "1004488",
|
|
8606
|
+
"text": "The script that updates the database on every deploy could randomly re-run a handful of updates it had already made. Most were harmless repeats, but one could change live data or fail a deploy. It now checks reliably, proven o"
|
|
8607
|
+
},
|
|
8608
|
+
{
|
|
8609
|
+
"id": "1004297",
|
|
8610
|
+
"text": "Each project can now be set to take every new version (Bongos) or only released ones (everyone else), a version that fails is no longer retried in a loop, and the deploy page shows each upgrade, any rollback, and when the auto"
|
|
8611
|
+
}
|
|
8612
|
+
],
|
|
8613
|
+
"1.20.79": [
|
|
8614
|
+
{
|
|
8615
|
+
"id": "1004484",
|
|
8616
|
+
"text": "New projects no longer have their 'start' command pointing at the Bongos hall, so when an owner later connects Render with the usual 'npm start', it runs their app instead of a broken second copy of the hall."
|
|
8617
|
+
}
|
|
8594
8618
|
]
|
|
8595
8619
|
}
|
package/scripts/gds/init.js
CHANGED
|
@@ -126,20 +126,24 @@ function coreVersionSafe() {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
// The npm scripts a STANDALONE greenfield instance runs. They MIRROR how the control
|
|
129
|
-
// plane runs a standalone instance (scripts/gds/provision.js): `
|
|
129
|
+
// plane runs a standalone instance (scripts/gds/provision.js): `hall` = the systemd
|
|
130
130
|
// ExecStart (`node <core>/src/platform-server.js`), `migrate` = standaloneMigrateCmd
|
|
131
|
-
// (`bash <core>/scripts/migrate.sh`). Keeping them identical means `npm
|
|
131
|
+
// (`bash <core>/scripts/migrate.sh`). Keeping them identical means `npm run hall` /
|
|
132
132
|
// `npm run migrate` on the instance behave like production, and `bongos upgrade`'s
|
|
133
133
|
// `npm run migrate` step resolves to the core's migrate.sh (ADR 0108).
|
|
134
|
+
// The hall is deliberately NOT `start` (task 1004484): `npm start` belongs to the
|
|
135
|
+
// project's own app, and a host like Render reads it as "how this app starts" — given the
|
|
136
|
+
// hall there, the deploy has no database and times out. Nothing runs an instance's
|
|
137
|
+
// `npm start`: the box unit and `bongos dev` both start platform-server.js directly.
|
|
134
138
|
const INSTANCE_CORE_PKG_DIR = 'node_modules/@bongos/core';
|
|
135
|
-
const
|
|
139
|
+
const INSTANCE_HALL_SCRIPT = `node ${INSTANCE_CORE_PKG_DIR}/src/platform-server.js`;
|
|
136
140
|
const INSTANCE_MIGRATE_SCRIPT = `bash ${INSTANCE_CORE_PKG_DIR}/scripts/migrate.sh`;
|
|
137
141
|
|
|
138
142
|
// PURE (exported for tests) — build/augment the greenfield instance package.json so a
|
|
139
143
|
// fresh instance is `npm ci`-installable and runnable (ADR 0108 greenfield gap 1/4,
|
|
140
144
|
// task 2053): the @bongos/core dependency (the `file:` tarball pin when a tarball is
|
|
141
145
|
// vendored, else the declared caret range — the CARET RESOLVES NOWHERE on npm, which is
|
|
142
|
-
// exactly why the vendored path pins `file:`) plus the
|
|
146
|
+
// exactly why the vendored path pins `file:`) plus the hall/migrate npm scripts a
|
|
143
147
|
// standalone instance runs. Preserves any pre-existing fields + scripts (fills only what
|
|
144
148
|
// is missing); greenfield OWNS the core dep, so it is always (re)set to `coreDep`. Never
|
|
145
149
|
// spawns npm — generating the lockfile is a separate imperative step in main().
|
|
@@ -155,7 +159,13 @@ function buildInstancePackageJson(existing, { name = 'cloudbongos-instance', cor
|
|
|
155
159
|
if (!pkg.name) pkg.name = name;
|
|
156
160
|
if (pkg.private === undefined) pkg.private = true;
|
|
157
161
|
pkg.scripts = { ...(isObj(pkg.scripts) ? pkg.scripts : {}) };
|
|
158
|
-
|
|
162
|
+
// A scaffold from before task 1004484 wrote the hall as `start`; move exactly that
|
|
163
|
+
// command (never an owner's own start) to `hall`, unless they already have a `hall`.
|
|
164
|
+
if (pkg.scripts.start === INSTANCE_HALL_SCRIPT && !pkg.scripts.hall) {
|
|
165
|
+
pkg.scripts.hall = pkg.scripts.start;
|
|
166
|
+
delete pkg.scripts.start;
|
|
167
|
+
}
|
|
168
|
+
if (!pkg.scripts.hall) pkg.scripts.hall = INSTANCE_HALL_SCRIPT;
|
|
159
169
|
if (!pkg.scripts.migrate) pkg.scripts.migrate = INSTANCE_MIGRATE_SCRIPT;
|
|
160
170
|
pkg.dependencies = {
|
|
161
171
|
...(isObj(moduleDeps) ? moduleDeps : {}),
|
|
@@ -1136,7 +1146,7 @@ module.exports = {
|
|
|
1136
1146
|
buildReadmeSection, appendReadmeSection, layerReadme, README_MARKER,
|
|
1137
1147
|
KNOWN_MODULES, HOSTING_SHAPES, CORE_PKG,
|
|
1138
1148
|
// greenfield installability (ADR 0108 gap 1/4, task 2053)
|
|
1139
|
-
buildInstancePackageJson, coreVersionSafe,
|
|
1149
|
+
buildInstancePackageJson, coreVersionSafe, INSTANCE_HALL_SCRIPT, INSTANCE_MIGRATE_SCRIPT,
|
|
1140
1150
|
// enabled-module dep composition (ADR 0138 / task 2160)
|
|
1141
1151
|
moduleDeclaredDeps, enabledModuleDeps,
|
|
1142
1152
|
};
|