@bongos/core 1.19.1013 → 1.19.1015

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.
Files changed (68) hide show
  1. package/.bongos-core.json +90 -85
  2. package/docs/adr/0227-a-task-network-map-is-refused-the-goal-graph-is-the-map.md +4 -1
  3. package/docs/adr/0331-the-sky-is-the-ideators-home.md +6 -1
  4. package/docs/copy-inventory.md +152 -112
  5. package/docs/copy-registry.json +1001 -304
  6. package/docs/module-api-changelog.md +4 -0
  7. package/modules/hall-ui/public/atlas.html +1 -1
  8. package/modules/hall-ui/public/blockers.html +1 -1
  9. package/modules/hall-ui/public/board-room.html +1 -1
  10. package/modules/hall-ui/public/collab.html +1 -1
  11. package/modules/hall-ui/public/copy-desk.html +1 -1
  12. package/modules/hall-ui/public/deploy.html +1 -1
  13. package/modules/hall-ui/public/diagrams.html +1 -1
  14. package/modules/hall-ui/public/drachmae.html +1 -1
  15. package/modules/hall-ui/public/fleet-costs.js +80 -0
  16. package/modules/hall-ui/public/fleet.html +64 -0
  17. package/modules/hall-ui/public/fleet.js +45 -0
  18. package/modules/hall-ui/public/fleet.states.json +14 -0
  19. package/modules/hall-ui/public/gate.html +1 -1
  20. package/modules/hall-ui/public/goal-map.html +125 -52
  21. package/modules/hall-ui/public/goal-map.states.json +29 -5
  22. package/modules/hall-ui/public/goals.html +1 -1
  23. package/modules/hall-ui/public/government.html +1 -1
  24. package/modules/hall-ui/public/idea.html +1 -1
  25. package/modules/hall-ui/public/ideas.html +1 -1
  26. package/modules/hall-ui/public/index.html +1 -1
  27. package/modules/hall-ui/public/modules.html +1 -1
  28. package/modules/hall-ui/public/people.html +1 -1
  29. package/modules/hall-ui/public/primer.html +1 -1
  30. package/modules/hall-ui/public/profile.html +1 -1
  31. package/modules/hall-ui/public/project-settings.html +1 -1
  32. package/modules/hall-ui/public/ranks.html +1 -1
  33. package/modules/hall-ui/public/roadmap.html +1 -1
  34. package/modules/hall-ui/public/roster.html +1 -1
  35. package/modules/hall-ui/public/sessions.html +1 -1
  36. package/modules/hall-ui/public/settings.html +1 -1
  37. package/modules/hall-ui/public/shell.js +15 -0
  38. package/modules/hall-ui/public/sky-data.js +19 -1
  39. package/modules/hall-ui/public/sky-interaction.js +14 -7
  40. package/modules/hall-ui/public/sky-layout.js +2 -1
  41. package/modules/hall-ui/public/sky-tour.js +10 -6
  42. package/modules/hall-ui/public/studio.html +1 -1
  43. package/modules/hall-ui/public/task.html +1 -1
  44. package/modules/hall-ui/public/thinking.css +6 -3
  45. package/modules/hall-ui/public/thinking.html +16 -10
  46. package/modules/hall-ui/public/watch.html +1 -1
  47. package/modules/hall-ui/public/work.html +1 -1
  48. package/modules/provisioning/routes/provisioning.js +4 -4
  49. package/modules/public-landing/public/projects.html +7 -0
  50. package/modules/ui-design/kit/fixtures/provisioning-instances.json +1 -0
  51. package/package-lock.json +2 -2
  52. package/package.json +1 -1
  53. package/src/bongos/auth.js +8 -0
  54. package/src/bongos/serve-internal.js +9 -0
  55. package/src/module-api.js +1 -1
  56. package/tests/auth_resolve_failure.mjs +3 -2
  57. package/tests/fleet_cost_view.mjs +189 -0
  58. package/tests/goal_map_page.mjs +346 -211
  59. package/tests/hall_audit.mjs +1 -0
  60. package/tests/hall_nav.mjs +9 -2
  61. package/tests/hall_page_gate_map.mjs +5 -2
  62. package/tests/helpers.mjs +21 -5
  63. package/tests/nav_permission_atoms.mjs +1 -1
  64. package/tests/render_check.mjs +6 -4
  65. package/modules/hall-ui/public/goal-map-lib.js +0 -349
  66. package/modules/hall-ui/public/goal-map.css +0 -117
  67. package/modules/hall-ui/public/goal-map.js +0 -232
  68. package/tests/goal_map_lib.mjs +0 -214
@@ -102,7 +102,7 @@
102
102
  <script src="/builders/brand-apply.js?v=2026-06-27-r75" defer></script>
103
103
  <!-- The app shell (sidebar + top bar + view router). Dependency-free; loaded
104
104
  BEFORE builders.js so the chrome ids exist when events are wired. -->
105
- <script src="/builders/shell.js?v=2026-09-22-movefeedback1" defer></script>
105
+ <script src="/builders/shell.js?v=2026-09-25-sky4b" defer></script>
106
106
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
107
107
  <script src="/builders/palette.js" defer></script>
108
108
  <!-- task 2166: the shared component kit (window.OTBKit) — pager/ledger/rail.
@@ -80,7 +80,7 @@
80
80
 
81
81
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
82
82
 
83
- <script src="/builders/shell.js?v=2026-09-22-movefeedback1"></script>
83
+ <script src="/builders/shell.js?v=2026-09-25-sky4b"></script>
84
84
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
85
85
  <script src="/builders/palette.js" defer></script>
86
86
  <script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
@@ -55,7 +55,7 @@
55
55
 
56
56
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
57
57
  chrome exists before the page script runs. -->
58
- <script src="/builders/shell.js?v=2026-09-22-movefeedback1"></script>
58
+ <script src="/builders/shell.js?v=2026-09-25-sky4b"></script>
59
59
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
60
60
  <script src="/builders/palette.js" defer></script>
61
61
  <!-- shared frontend toolkit (window.OTB — rank labels, number formats) + the
@@ -115,7 +115,7 @@
115
115
 
116
116
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
117
117
 
118
- <script src="/builders/shell.js?v=2026-09-22-movefeedback1"></script>
118
+ <script src="/builders/shell.js?v=2026-09-25-sky4b"></script>
119
119
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
120
120
  <script src="/builders/palette.js" defer></script>
121
121
  <!-- the shared markdown reader (window.OTBMdReader) — primer.js renders through
@@ -187,7 +187,7 @@
187
187
 
188
188
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
189
189
  chrome exists before the page script runs. -->
190
- <script src="/builders/shell.js?v=2026-09-22-movefeedback1"></script>
190
+ <script src="/builders/shell.js?v=2026-09-25-sky4b"></script>
191
191
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
192
192
  <script src="/builders/palette.js" defer></script>
193
193
  <!-- shared frontend toolkit (window.OTB) — loaded before profile.js, which pulls from it. -->
@@ -58,7 +58,7 @@
58
58
 
59
59
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
60
60
 
61
- <script src="/builders/shell.js?v=2026-09-22-movefeedback1"></script>
61
+ <script src="/builders/shell.js?v=2026-09-25-sky4b"></script>
62
62
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
63
63
  <script src="/builders/palette.js" defer></script>
64
64
  <script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
@@ -68,7 +68,7 @@
68
68
 
69
69
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
70
70
  chrome exists before the page script runs. -->
71
- <script src="/builders/shell.js?v=2026-09-22-movefeedback1"></script>
71
+ <script src="/builders/shell.js?v=2026-09-25-sky4b"></script>
72
72
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
73
73
  <script src="/builders/palette.js" defer></script>
74
74
  <!-- shared frontend toolkit (window.OTB) — loaded before ranks.js, which pulls from it. -->
@@ -41,7 +41,7 @@
41
41
 
42
42
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
43
43
 
44
- <script src="/builders/shell.js?v=2026-09-22-movefeedback1"></script>
44
+ <script src="/builders/shell.js?v=2026-09-25-sky4b"></script>
45
45
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
46
46
  <script src="/builders/palette.js" defer></script>
47
47
  <script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
@@ -50,7 +50,7 @@
50
50
 
51
51
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
52
52
  chrome exists before the page script runs. -->
53
- <script src="/builders/shell.js?v=2026-09-22-movefeedback1"></script>
53
+ <script src="/builders/shell.js?v=2026-09-25-sky4b"></script>
54
54
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
55
55
  <script src="/builders/palette.js" defer></script>
56
56
  <!-- shared frontend toolkit (window.OTB — rank labels, number formats) + the
@@ -99,7 +99,7 @@
99
99
 
100
100
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
101
101
  chrome exists before the page script runs. -->
102
- <script src="/builders/shell.js?v=2026-09-22-movefeedback1"></script>
102
+ <script src="/builders/shell.js?v=2026-09-25-sky4b"></script>
103
103
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
104
104
  <script src="/builders/palette.js" defer></script>
105
105
  <!-- shared frontend toolkit (window.OTB) — loaded BEFORE sessions.js so it can pull from it. -->
@@ -416,7 +416,7 @@
416
416
 
417
417
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
418
418
  chrome exists before the page scripts run. -->
419
- <script src="/builders/shell.js?v=2026-09-22-movefeedback1"></script>
419
+ <script src="/builders/shell.js?v=2026-09-25-sky4b"></script>
420
420
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
421
421
  <script src="/builders/palette.js" defer></script>
422
422
  <!-- shared frontend toolkit (window.OTB) — loaded before consuming IIFEs. -->
@@ -155,6 +155,17 @@
155
155
  { id: 'home', label: 'Home', icon: 'home', href: '/', view: 'home', gate: 'always' },
156
156
  { id: 'roadmap', label: 'Roadmap', icon: 'roadmap', href: '/roadmap', gate: 'member' },
157
157
  { id: 'goals', label: 'Goals', icon: 'goals', href: '/goals', gate: 'member' },
158
+ // task 1004232 (sky part 4 of 5, goal 1000095; ADR 0227 §11, ADR 0331):
159
+ // THE PROJECT SKY — the whole project drawn from the ledger, at the URL
160
+ // the goal map held. The map took no nav slot on purpose (task 1003479:
161
+ // reachable from Goals, never load-bearing); the owner granted the sky
162
+ // that slot on 2026-09-24, directly under Goals because it is the goals
163
+ // seen as one picture — the same audience, the project. gate 'member' as
164
+ // Goals is: cosmetic, like every gate here — the page gate is the wall
165
+ // (requireBuilderPage, the tier the map sat on). The id stays 'goal-map'
166
+ // because the href does: Goals' own "see which goals wait on each other"
167
+ // link and every bookmark land here unchanged.
168
+ { id: 'goal-map', label: 'Sky', icon: 'goals', href: '/goal-map', gate: 'member' },
158
169
  // task 1966: Tasks (the board formerly labelled "Work") is visible to ANY
159
170
  // signed-in builder (incl. newcomer Xenos). Claimability is controlled
160
171
  // per-task, not by hiding the page — every row carries a rank tag and the
@@ -334,6 +345,10 @@
334
345
  // `core.pin.move` atom its page and both its routes check — cosmetic only, as
335
346
  // every item in this list is; the wall is server-side.
336
347
  { id: 'deploy', label: 'Deploy', icon: 'admin', href: '/deploy', gate: 'archon' },
348
+ // task 1001949: what each hosted project costs. 'government' mirrors the metic floor
349
+ // of `provisioning.fleet.manage`, the atom its page and its one read check (the
350
+ // Deploy precedent above); dropped where the provisioning module is not enabled.
351
+ { id: 'fleet', label: 'Fleet costs', icon: 'admin', href: '/fleet', gate: 'government', module: 'provisioning' },
337
352
  // BV1.R113: the roles-and-permissions tab (ADR 0151). The one genuinely
338
353
  // Archon-only item the 'government' gate comment anticipated — cosmetic
339
354
  // only. The wall is the `government.manage` atom checked on each of the
@@ -25,6 +25,24 @@
25
25
  // "must not ship" rows): the "claude layer" pill and every born-of mark. The
26
26
  // code that draws them is kept, and it reads `filed_via` / `bornOf` that are
27
27
  // simply absent today — so part 5 turns them on by shipping the facts.
28
+ //
29
+ // ONE PAGE, TWO DOORS (part 4, task 1004232). /thinking and /goal-map are the
30
+ // same seven files over the same markup; the body's `data-sky-lens` says which
31
+ // lens is HOME — 'mine' on /thinking (the ideator's sky), 'all' on /goal-map
32
+ // (the project sky). `S.home` is read once here; sky-tour.js boots into it and
33
+ // hands back to it after the tour, and sky-interaction.js's switcher leaves
34
+ // for the other page rather than switching when the lens asked for lives
35
+ // there. Every lens still exists inside either page, because the tour walks
36
+ // all three.
37
+ //
38
+ // ONE PAGE, TWO DOORS (part 4, task 1004232). /thinking and /goal-map are the
39
+ // same seven files over the same markup; the body's `data-sky-lens` says which
40
+ // lens is HOME — 'mine' on /thinking (the ideator's sky), 'all' on /goal-map
41
+ // (the project sky). `S.home` is read once here; sky-tour.js boots into it and
42
+ // hands back to it after the tour, and sky-interaction.js's switcher leaves
43
+ // for the other page rather than switching when the lens asked for lives
44
+ // there. Every lens still exists inside either page, because the tour walks
45
+ // all three.
28
46
 
29
47
  (() => {
30
48
  'use strict';
@@ -78,7 +96,7 @@
78
96
  baseIdeas: [], ideas: [], becameGoal: new Map(), becameCrit: new Map(), T0: NOW - 86400000,
79
97
  hasFiledVia: false, canNod: false, template: null, buildingVersionIds: [], loaded: false,
80
98
  rehearsal: false, claudeLayer: false, influence: false, ideasOnMap: false, recentOnly: false,
81
- lens: 'mine', picked: null, hover: null, filter: 'all', gfilter: 'all', dirty: true, gravityGoal: null, flying: null, tourSpot: null,
99
+ lens: 'mine', home: document.body.dataset.skyLens === 'all' ? 'all' : 'mine', picked: null, hover: null, filter: 'all', gfilter: 'all', dirty: true, gravityGoal: null, flying: null, tourSpot: null,
82
100
  clock: NOW, playing: false, clockHidden: store.get('sky-clock') === 'off',
83
101
  charted: new Map(), focusSet: null, explore: { on: store.get('sky-explore') !== 'off', seen: new Set(JSON.parse(store.get('sky-seen') || '[]')) },
84
102
  cam: { x: 0, y: 0, k: 0.12 }, flight: null, W: 0, H: 0, DPR: 1, t0: 0, shuttles: [],
@@ -5,10 +5,12 @@
5
5
  //
6
6
  // The prototype's "hover card + shuttle menu", "interaction" and "lenses and
7
7
  // toggles" sections, verbatim but for the shared state on `SKY.S`. The one
8
- // behaviour this part CHANGES on purpose: the third lens button, "the whole
9
- // project", leaves for the project sky at /goal-map (part 4 builds that page)
10
- // instead of switching this one — the internal 'all' lens survives because the
11
- // tour walks it. And the "claude layer" pill only appears once part 5's
8
+ // behaviour this part CHANGES on purpose: the lens switcher LEAVES for the
9
+ // other page when the lens asked for is that page's home (part 4, task
10
+ // 1004232) — on /thinking, "the whole project" goes to /goal-map; on
11
+ // /goal-map, "simple" and "your thinking" go to /thinking — instead of
12
+ // switching in place. Every lens survives inside both pages because the tour
13
+ // walks all three. And the "claude layer" pill only appears once part 5's
12
14
  // `filed_via` exists on at least one thought; until then it is hidden and
13
15
  // every thought shows (the README's "must not ship" row).
14
16
  //
@@ -90,9 +92,14 @@
90
92
  if (!noFly) { if (l === 'all') SKY.fitAll(); else SKY.fitPresent(); }
91
93
  SKY.maybeStart(); SKY.renderRoom(); updateStats(); S.dirty = true;
92
94
  }
93
- $('lensSimple').addEventListener('click', () => setLens('simple')); $('lensMine').addEventListener('click', () => setLens('mine'));
94
- // "the whole project" is the project sky — its own page (part 4).
95
- $('lensAll').addEventListener('click', () => { location.assign(`${hallBase()}/goal-map`); });
95
+ // The switcher is two doors (part 4, task 1004232): a lens that lives on the
96
+ // OTHER page is reached by going there. /thinking is home to 'simple' and
97
+ // 'mine'; /goal-map is home to 'all'. Inside a page every lens still works
98
+ // (the tour walks all three), so only the button leaves.
99
+ const leave = (p) => location.assign(`${hallBase()}${p}`);
100
+ $('lensSimple').addEventListener('click', () => (S.home === 'all' ? leave('/thinking') : setLens('simple')));
101
+ $('lensMine').addEventListener('click', () => (S.home === 'all' ? leave('/thinking') : setLens('mine')));
102
+ $('lensAll').addEventListener('click', () => (S.home === 'all' ? setLens('all') : leave('/goal-map')));
96
103
  $('fitBtn').addEventListener('click', () => (S.lens === 'all' ? SKY.fitAll() : SKY.fitPresent()));
97
104
  function togglePill(id, get, set) { $(id).addEventListener('click', () => { set(!get()); $(id).setAttribute('aria-pressed', String(get())); SKY.renderRoom(); updateStats(); S.dirty = true; }); }
98
105
  togglePill('recentBtn', () => S.recentOnly, (v) => { S.recentOnly = v; });
@@ -6,7 +6,8 @@
6
6
  // sky-data.js for the split). The clock draws the sky as it stood on a day —
7
7
  // nothing moves position, only what exists changes. Routes are the goal
8
8
  // graph's own edges, charted by a shuttle when asked. Explore is the project
9
- // sky's rule (part 4 owns that page; the tour walks it here). The camera is a
9
+ // sky's rule (home at /goal-map since part 4; the tour walks it on /thinking
10
+ // too). The camera is a
10
11
  // pan/zoom over world coordinates laid out in sky-data.js.
11
12
 
12
13
  (() => {
@@ -7,9 +7,12 @@
7
7
  // prototype's own start-up line. The thirteen steps and their words are the
8
8
  // design of record's; the tour opens once per browser (`sky-tour-seen`).
9
9
  //
10
- // Steps 7, 8 and 10 walk the project sky's lens ('all') inside this page —
11
- // which is why that lens survives here although its switcher button leaves
12
- // for /goal-map (part 4).
10
+ // The tour walks every lens inside whichever page it runs on — steps 7, 8
11
+ // and 10 the project sky's ('all'), steps 1, 11 and 12 the ideator's ('mine'),
12
+ // step 13 'simple' — which is why all three survive in both pages although the
13
+ // switcher leaves for the other page. When it ends it hands back to the page's
14
+ // HOME lens (`S.home`: 'mine' on /thinking, 'all' on /goal-map — part 4, task
15
+ // 1004232), and the boot opens in that lens too.
13
16
 
14
17
  (() => {
15
18
  'use strict';
@@ -43,7 +46,7 @@
43
46
  function spot(el, add) { if (!add) { S.spotted.forEach((e) => e.classList.remove('spot')); S.spotted = []; } if (el) { el.classList.add('spot'); S.spotted.push(el); } }
44
47
  function showStep() { const s = STEPS[S.tourI]; if (!s) return endTour(); spot(null); S.tourSpot = null; s.go(); $('tourN').textContent = `how to read the sky · ${S.tourI + 1} of ${STEPS.length}`; $('tourDots').innerHTML = STEPS.map((_, n) => `<i class="${n === S.tourI ? 'on' : ''}"></i>`).join(''); $('tourH').textContent = s.h; $('tourP').textContent = s.p; $('tourNext').textContent = S.tourI === STEPS.length - 1 ? 'start reading' : 'next'; $('tourBack').hidden = S.tourI === 0; $('tour').hidden = false; S.dirty = true; }
45
48
  function startTour() { SKY.pick(null); SKY.toggleRoom(false); SKY.clearCharts(); $('start').hidden = true; S.exploreWas = S.explore.on; S.tourI = 0; showStep(); }
46
- function endTour() { $('tour').hidden = true; spot(null); S.tourSpot = null; S.tourI = -1; if (S.exploreWas != null) { S.explore.on = S.exploreWas; $('exploreBtn').setAttribute('aria-pressed', String(S.explore.on)); S.exploreWas = null; } store.set('sky-tour-seen', '4'); SKY.setLens('mine', true); SKY.clearCharts(); SKY.maybeStart(); S.dirty = true; }
49
+ function endTour() { $('tour').hidden = true; spot(null); S.tourSpot = null; S.tourI = -1; if (S.exploreWas != null) { S.explore.on = S.exploreWas; $('exploreBtn').setAttribute('aria-pressed', String(S.explore.on)); S.exploreWas = null; } store.set('sky-tour-seen', '4'); SKY.setLens(S.home, true); SKY.clearCharts(); SKY.maybeStart(); S.dirty = true; }
47
50
  $('tourNext').addEventListener('click', () => { S.tourI++; showStep(); }); $('tourBack').addEventListener('click', () => { S.tourI = Math.max(0, S.tourI - 1); showStep(); }); $('tourSkip').addEventListener('click', endTour); $('tourBtn').addEventListener('click', startTour);
48
51
 
49
52
  // ---------------------------------------------------------------- the loop
@@ -68,8 +71,9 @@
68
71
  // The Full Idea switch needs the template's key to file; without the read there is nothing to file as.
69
72
  $('fullToggle').hidden = !S.template;
70
73
  S.t0 = performance.now();
71
- SKY.resize(); SKY.setClock(NOW); SKY.setLens('mine', true); SKY.renderPanel(null); $('time').hidden = S.clockHidden;
72
- S.cam.k = SKY.fitRad(S.BANDS[0].r1 + 120);
74
+ SKY.resize(); SKY.setClock(NOW); SKY.setLens(S.home, true); SKY.renderPanel(null); $('time').hidden = S.clockHidden;
75
+ // The first frame is the home lens's own fit: the present for your sky, the whole sky for the project's.
76
+ S.cam.k = S.home === 'all' ? SKY.fitRad(S.BANDS[S.BANDS.length - 1].r1 + 100) : SKY.fitRad(S.BANDS[0].r1 + 120);
73
77
  document.body.dataset.skyReady = '1';
74
78
  requestAnimationFrame(frame);
75
79
  if (store.get('sky-tour-seen') !== '4') setTimeout(startTour, 600);
@@ -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-22-movefeedback1"></script>
69
+ <script src="/builders/shell.js?v=2026-09-25-sky4b"></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
@@ -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-22-movefeedback1"></script>
60
+ <script src="/builders/shell.js?v=2026-09-25-sky4b"></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. -->
@@ -4,7 +4,10 @@
4
4
  prototype as the design of record and this sheet is its pixel contract. It
5
5
  deliberately does NOT load the hall's style.css or its tokens — the sky is
6
6
  its own surface (black void, Manrope, JetBrains Mono for figures), and the
7
- template's values are the contract. Keep it one file (the task says so). */
7
+ template's values are the contract. Keep it one file (the task says so).
8
+ ONE SHEET, TWO DOORS (part 4, task 1004232): /thinking and /goal-map are the
9
+ same page opened in different lenses, so both bodies are named below and
10
+ nothing in here is per-page. */
8
11
  /* The template declared these on :root. They sit on the page's body here,
9
12
  with the template's values untouched, because the server splices the
10
13
  branding pack's own :root (--ink, --accent, --rule …) into <head> AFTER this
@@ -14,7 +17,7 @@
14
17
  its own — tests/hall_reading_world.mjs; color-scheme inherits from body
15
18
  too). Same reason `body` below is qualified: a pack backdrop is written as
16
19
  `body{background:…}` and the void must win. */
17
- body[data-page="thinking"] {
20
+ body[data-page="thinking"], body[data-page="goal-map"] {
18
21
  color-scheme: dark;
19
22
  --void: #000000; --ink: #ffffff; --ink-soft: #c0c0c0; --ink-faint: #a8a8a8; --ink-dim: #65656a; --ink-ghost: #3a3a3f;
20
23
  --rule: rgba(255,255,255,.16); --rule-soft: rgba(255,255,255,.08); --glass: rgba(255,255,255,.075); --glass-strong: rgba(255,255,255,.14);
@@ -23,7 +26,7 @@ body[data-page="thinking"] {
23
26
  --face: Manrope, "Segoe UI", system-ui, -apple-system, sans-serif;
24
27
  }
25
28
  html, body { height: 100%; }
26
- body[data-page="thinking"] { margin: 0; background: var(--void); color: var(--ink); font-family: var(--face); font-size: 15px; line-height: 1.5; overflow: hidden; -webkit-font-smoothing: antialiased; }
29
+ body[data-page="thinking"], body[data-page="goal-map"] { margin: 0; background: var(--void); color: var(--ink); font-family: var(--face); font-size: 15px; line-height: 1.5; overflow: hidden; -webkit-font-smoothing: antialiased; }
27
30
  button { font-family: inherit; }
28
31
  a { color: var(--accent); }
29
32
  .lbl { font: var(--label); letter-spacing: .14em; text-transform: uppercase; color: var(--ink-faint); }
@@ -12,7 +12,7 @@
12
12
  links, injected into this head by the server like every hall page's
13
13
  (tests/hall_tokens.mjs: no hall page hardcodes a web-font stylesheet).
14
14
  The template carried its own Google Fonts link for the same two faces. -->
15
- <link rel="stylesheet" href="/builders/thinking.css?v=2026-09-25-sky3">
15
+ <link rel="stylesheet" href="/builders/thinking.css?v=2026-09-25-sky4">
16
16
  </head>
17
17
  <!-- THE SKY (task 1004231 / sky part 3 of 5, goal 1000095; ADR 0331, ADR 0227
18
18
  §11). This page is docs/design/mocks/sky/sky.template.html — the prototype
@@ -29,7 +29,13 @@
29
29
  room survives inside it as the list drawer (L). /sky, the old ambient
30
30
  page's URL, 301s here (src/bongos/serve-internal.js). It carries no hall
31
31
  chrome on purpose: the sky is a full-screen surface with its own header,
32
- and the hall's doors (the record, the goal, the task) open from the panel. -->
32
+ and the hall's doors (the record, the goal, the task) open from the panel.
33
+
34
+ The same markup, sheet and scripts serve THE PROJECT SKY at /goal-map
35
+ (part 4, task 1004232): that body says data-sky-lens="all", so the boot
36
+ opens in "the whole project" and the tour hands back to it. The switcher
37
+ is two doors — "the whole project" leaves for /goal-map from here, and
38
+ "simple" / "your thinking" leave for here from there. -->
33
39
  <body data-page="thinking" data-page-title="Your sky">
34
40
 
35
41
  <!-- The one <main> landmark the kit's a11y floor asks of every hall page
@@ -124,13 +130,13 @@
124
130
  </div>
125
131
  </footer>
126
132
 
127
- <script src="/builders/idea-objects.js?v=2026-09-25-sky3"></script>
128
- <script src="/builders/sky-data.js?v=2026-09-25-sky3"></script>
129
- <script src="/builders/sky-layout.js?v=2026-09-25-sky3"></script>
130
- <script src="/builders/sky-draw.js?v=2026-09-25-sky3"></script>
131
- <script src="/builders/sky-interaction.js?v=2026-09-25-sky3"></script>
132
- <script src="/builders/sky-panel.js?v=2026-09-25-sky3"></script>
133
- <script src="/builders/sky-composer.js?v=2026-09-25-sky3"></script>
134
- <script src="/builders/sky-tour.js?v=2026-09-25-sky3"></script>
133
+ <script src="/builders/idea-objects.js?v=2026-09-25-sky4"></script>
134
+ <script src="/builders/sky-data.js?v=2026-09-25-sky4"></script>
135
+ <script src="/builders/sky-layout.js?v=2026-09-25-sky4"></script>
136
+ <script src="/builders/sky-draw.js?v=2026-09-25-sky4"></script>
137
+ <script src="/builders/sky-interaction.js?v=2026-09-25-sky4"></script>
138
+ <script src="/builders/sky-panel.js?v=2026-09-25-sky4"></script>
139
+ <script src="/builders/sky-composer.js?v=2026-09-25-sky4"></script>
140
+ <script src="/builders/sky-tour.js?v=2026-09-25-sky4"></script>
135
141
  </body>
136
142
  </html>
@@ -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-22-movefeedback1"></script>
165
+ <script src="/builders/shell.js?v=2026-09-25-sky4b"></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-22-movefeedback1"></script>
174
+ <script src="/builders/shell.js?v=2026-09-25-sky4b"></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. -->
@@ -49,7 +49,7 @@ const express = require('express');
49
49
  const api = require('../../../src/module-api');
50
50
  const auth = api, { pool, validateOrRespond, parseId } = api;
51
51
  const provisioning = require('../provisioning');
52
- const { costLedger } = require('../cost-ledger');
52
+ const { costLedger, ownerMonthlyUsd } = require('../cost-ledger');
53
53
  const { onboardPlan, stepsForShape } = require('../onboard-plan');
54
54
  const { ALWAYS_ON_CORE, OPTIONAL_MODULES, starterBundleAnswerTable, optionalModules, effectiveModules } = require('../starter-bundles');
55
55
  const { recommendationsForDetails } = require('../recommendations');
@@ -144,10 +144,10 @@ function publishVerdict(row) {
144
144
  }
145
145
 
146
146
  // The owner-facing projection every instance read returns: the allow-listed row
147
- // plus the derived publish verdict. One helper so a new read cannot ship the row
148
- // without the verdict and leave an owner unable to see why they are dark matter.
147
+ // plus the derived publish verdict and the estimate of the owner's own hosting bill
148
+ // (task 1001949; null = unpriced, never 0). Owner- or archon-scoped reads only.
149
149
  function ownerInstance(row) {
150
- return { ...provisioning.publicInstance(row), publish: publishVerdict(row) };
150
+ return { ...provisioning.publicInstance(row), publish: publishVerdict(row), owner_monthly_usd: ownerMonthlyUsd({ hostingShape: row.hosting_shape, tier: row.tier }) };
151
151
  }
152
152
 
153
153
  // Project an instance row for the fleet view — the owner identity + the same
@@ -5573,6 +5573,12 @@ summary{min-height:24px;padding:3px 0;}
5573
5573
 
5574
5574
  /* one label · value pair of the manage ledger */
5575
5575
  function ledgerRow(r) { return '<span class="k">' + r[0] + '</span><span class="v">' + r[1] + '</span>'; }
5576
+ /* the owner's own hosting bill (task 1001949, ADR 0327): an estimate, since the
5577
+ provider bills them directly. null is unpriced — a $0 would be a price nobody quoted */
5578
+ function hostingBill(usd) {
5579
+ if (typeof usd !== 'number' || !isFinite(usd)) return 'not priced yet';
5580
+ return '$' + (usd % 1 ? usd.toFixed(2) : String(usd)) + '/month, estimated. Render bills you directly.';
5581
+ }
5576
5582
  /* the head's planet and the ledger's planet row (task 1003345) — one resolution
5577
5583
  (planetOf) for both, so the head, the row and the sky can never disagree; the
5578
5584
  planet row is the slot the picker (task 1003347) fills */
@@ -5601,6 +5607,7 @@ summary{min-height:24px;padding:3px 0;}
5601
5607
  var server = inst.hosting_shape ? esc(SHAPE_LABEL[inst.hosting_shape] || inst.hosting_shape) : '';
5602
5608
  if (inst.onboard_mode === 'adopt') server += (server ? ', ' : '') + 'adopted repo';
5603
5609
  if (server) rows.push(['server', server]);
5610
+ if (inst.hosting_shape === 'cloud-host' && inst.status !== 'torn_down') rows.push(['hosting bill', hostingBill(inst.owner_monthly_usd)]);
5604
5611
  $('mMeta').innerHTML = rows.map(ledgerRow).join('');
5605
5612
  $('mMeta').hidden = !rows.length;
5606
5613
  var addr = addressRow(inst, true);
@@ -26,6 +26,7 @@
26
26
  "domain": null,
27
27
  "target_ref": "builder/harbor",
28
28
  "hosting_shape": "cloud-host",
29
+ "owner_monthly_usd": null,
29
30
  "onboard_mode": "scaffold",
30
31
  "created_at": "$now-6m",
31
32
  "provisioned_at": null,
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.1013",
3
+ "version": "1.19.1015",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.1013",
9
+ "version": "1.19.1015",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.1013",
3
+ "version": "1.19.1015",
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",
@@ -462,6 +462,10 @@ const PAGE_GATES = Object.freeze({
462
462
  // — a vulnerability window, and exactly the kind of topology ADR 0043 keeps off
463
463
  // unauthenticated surfaces.
464
464
  requireCorePinPage: Object.freeze({ permission: 'core.pin.move', deniedTo: '/builders' }),
465
+ // The /fleet cost page (task 1001949) — on `provisioning.fleet.manage`, the SAME atom
466
+ // its one read (GET /provisioning/fleet/cost-ledger) checks: every owner's projects
467
+ // and what they cost, so the page reaches exactly as far as the data does.
468
+ requireFleetPage: Object.freeze({ permission: 'provisioning.fleet.manage', deniedTo: '/builders' }),
465
469
  });
466
470
 
467
471
  // The `alsoAdmits` door — "is this visitor the page's OTHER audience?", asked of
@@ -637,6 +641,9 @@ async function requireBoardVotePage(req, res, next) {
637
641
  async function requireCorePinPage(req, res, next) {
638
642
  return runPageGate('requireCorePinPage', req, res, next);
639
643
  }
644
+ async function requireFleetPage(req, res, next) {
645
+ return runPageGate('requireFleetPage', req, res, next);
646
+ }
640
647
 
641
648
  // ---------------------------------------------------------------------------
642
649
  // Route-gate inventory (V3.R06 #231) — the canonical "who can call what" list.
@@ -929,6 +936,7 @@ module.exports = {
929
936
  requireProjectCuratePage,
930
937
  requireBoardVotePage,
931
938
  requireCorePinPage,
939
+ requireFleetPage,
932
940
  requireRank,
933
941
  rankMeetsThreshold,
934
942
  requirePermission,
@@ -954,6 +954,8 @@ function mountInternalSurfaces(app) {
954
954
  // statically, and a comment in that gap makes the page test as UNGATED while
955
955
  // looking gated here. Prose goes above the const, as here.
956
956
  const DEPLOY_PAGE_RE = /^\/builders\/deploy(?:\.html)?\/?$/;
957
+ // The fleet cost page (task 1001949), on the atom its one read checks.
958
+ const FLEET_PAGE_RE = /^\/builders\/fleet(?:\.html)?\/?$/;
957
959
  // /scouting WAS the legacy twin of /people — the same /api/gds/scouting
958
960
  // directory, left behind when task 1002577 graduated it into the hall. Task
959
961
  // 1003470 had already made the two one page (scouting.html rendered people's
@@ -1004,6 +1006,9 @@ function mountInternalSurfaces(app) {
1004
1006
  if (DEPLOY_PAGE_RE.test(norm)) {
1005
1007
  return gdsAuth.requireCorePinPage(req, res, next);
1006
1008
  }
1009
+ if (FLEET_PAGE_RE.test(norm)) {
1010
+ return gdsAuth.requireFleetPage(req, res, next);
1011
+ }
1007
1012
  next();
1008
1013
  });
1009
1014
 
@@ -1166,6 +1171,10 @@ function mountInternalSurfaces(app) {
1166
1171
  // task 1003479 (goal 1000089): `goal-map` — the map of which goals wait on which,
1167
1172
  // reached from Goals. It reads GET /goals/graph, which is `requireBuilder`, so a
1168
1173
  // page gated any lower would show a signed-out visitor an empty shell.
1174
+ // task 1004232 (sky part 4): `goal-map` is now THE PROJECT SKY — the same page
1175
+ // as `thinking`, opened in its "the whole project" lens — and holds the nav
1176
+ // slot under Goals the map was denied (ADR 0227 §11). Same tier: it reads GET
1177
+ // /sky and GET /sky/mine, the sky's own reads, behind the same sign-in.
1169
1178
  // task 1004231 (sky part 3): `thinking` is now THE SKY itself — the prototype
1170
1179
  // the owner adopted (docs/design/mocks/sky/, ADR 0331) built over GET /sky +
1171
1180
  // GET /sky/mine, with the room inside it as the list drawer. The old ambient
package/src/module-api.js CHANGED
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
71
71
  // there. scripts/gds/bump-version.js still rewrites the literal below; it appends
72
72
  // the entry to that file. Look for a version's history there, not here.
73
73
  // ---------------------------------------------------------------------------
74
- const CORE_VERSION = '1.19.1013'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
74
+ const CORE_VERSION = '1.19.1015'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
75
75
 
76
76
  // A namespaced logger so a module's log lines are attributable + consistent.
77
77
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -98,16 +98,17 @@ test('AUDIT: every async middleware mounted directly on a router owns its reject
98
98
  // requireBoardVotePage / ADR 0266 /board-room vote gate)
99
99
  // requireCorePinPage / ADR 0293's /deploy door — the narrowest of them,
100
100
  // on the archon-floor `core.pin.move` atom.
101
+ // requireFleetPage — task 1001949's /fleet cost page, same delegation.
101
102
  // Task 1003450 removed two more — requireNonXenosPage and the R113
102
103
  // requireGovernmentManagePage — which had both ended up dispatched from no
103
104
  // route at all; tests/hall_page_gate_map.mjs now fails if that recurs.
104
105
  // Anything else async and router-mounted must be added with its own try/catch.
105
106
  const src = fs.readFileSync(path.join(ROOT, 'src', 'bongos', 'auth.js'), 'utf8');
106
107
  const asyncMw = [...src.matchAll(/^async function (require\w+|allow\w+)\s*\(req, res, next\)/gm)].map((m) => m[1]);
107
- assert.deepEqual(asyncMw.sort(), ['requireBoardVotePage', 'requireBuilder', 'requireBuilderPage', 'requireCorePinPage', 'requireGovernmentPage', 'requireProjectCuratePage'],
108
+ assert.deepEqual(asyncMw.sort(), ['requireBoardVotePage', 'requireBuilder', 'requireBuilderPage', 'requireCorePinPage', 'requireFleetPage', 'requireGovernmentPage', 'requireProjectCuratePage'],
108
109
  'a new async router middleware appeared — give it a try/catch and add it here');
109
110
  // The page gates must actually delegate (that is WHY they are safe).
110
- for (const name of ['requireBuilderPage', 'requireGovernmentPage', 'requireProjectCuratePage', 'requireBoardVotePage', 'requireCorePinPage']) {
111
+ for (const name of ['requireBuilderPage', 'requireGovernmentPage', 'requireProjectCuratePage', 'requireBoardVotePage', 'requireCorePinPage', 'requireFleetPage']) {
111
112
  const fn = src.slice(src.indexOf(`async function ${name}`));
112
113
  assert.match(sliceFn(fn), /runPageGate\(/, `${name} must delegate to the guarded runPageGate`);
113
114
  }