@bongos/core 1.19.1012 → 1.19.1014
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 +113 -78
- package/docs/adr/0331-the-sky-is-the-ideators-home.md +6 -1
- package/docs/copy-inventory.md +120 -66
- package/docs/copy-registry.json +984 -498
- package/docs/file-map.md +1 -1
- package/docs/module-api-changelog.md +4 -0
- package/modules/hall-ui/public/atlas.html +1 -1
- package/modules/hall-ui/public/blockers.html +1 -1
- package/modules/hall-ui/public/board-room.html +1 -1
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/copy-desk.html +1 -1
- package/modules/hall-ui/public/deploy.html +1 -1
- package/modules/hall-ui/public/diagrams.html +1 -1
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/fleet-costs.js +80 -0
- package/modules/hall-ui/public/fleet.html +64 -0
- package/modules/hall-ui/public/fleet.js +45 -0
- package/modules/hall-ui/public/fleet.states.json +14 -0
- package/modules/hall-ui/public/gate.html +1 -1
- package/modules/hall-ui/public/goal-map.html +1 -1
- package/modules/hall-ui/public/goals.html +1 -1
- package/modules/hall-ui/public/government.html +1 -1
- package/modules/hall-ui/public/idea-objects.js +4 -1
- package/modules/hall-ui/public/idea.html +1 -1
- package/modules/hall-ui/public/ideas.html +1 -1
- package/modules/hall-ui/public/index.html +1 -1
- package/modules/hall-ui/public/modules.html +1 -1
- package/modules/hall-ui/public/people.html +1 -1
- package/modules/hall-ui/public/primer.html +1 -1
- package/modules/hall-ui/public/profile.html +1 -1
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/ranks.html +1 -1
- package/modules/hall-ui/public/roadmap.html +1 -1
- package/modules/hall-ui/public/roster.html +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings.html +1 -1
- package/modules/hall-ui/public/shell.js +4 -0
- package/modules/hall-ui/public/sky-composer.js +155 -0
- package/modules/hall-ui/public/sky-data.js +287 -0
- package/modules/hall-ui/public/sky-draw.js +244 -0
- package/modules/hall-ui/public/sky-interaction.js +137 -0
- package/modules/hall-ui/public/sky-layout.js +81 -0
- package/modules/hall-ui/public/sky-panel.js +141 -0
- package/modules/hall-ui/public/sky-tour.js +80 -0
- package/modules/hall-ui/public/studio.html +1 -1
- package/modules/hall-ui/public/task.html +1 -1
- package/modules/hall-ui/public/thinking.css +203 -267
- package/modules/hall-ui/public/thinking.html +119 -58
- package/modules/hall-ui/public/thinking.states.json +33 -9
- package/modules/hall-ui/public/watch.html +1 -1
- package/modules/hall-ui/public/work.html +1 -1
- package/modules/provisioning/routes/provisioning.js +4 -4
- package/modules/public-landing/public/projects.html +7 -0
- package/modules/ui-design/kit/fixtures/provisioning-instances.json +1 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/bongos/auth.js +8 -0
- package/src/bongos/routes/me.js +4 -1
- package/src/bongos/serve-internal.js +25 -6
- package/src/module-api.js +1 -1
- package/tests/auth_resolve_failure.mjs +3 -2
- package/tests/fleet_cost_view.mjs +189 -0
- package/tests/hall_audit.mjs +27 -0
- package/tests/hall_nav.mjs +3 -1
- package/tests/hall_page_gate_map.mjs +13 -5
- package/tests/hall_sky.mjs +393 -405
- package/tests/hall_thinking_room.mjs +274 -296
- package/tests/helpers.mjs +185 -217
- package/tests/ideator_large_dead_thought_proof.mjs +33 -25
- package/tests/ideator_traceable_proof.mjs +61 -39
- package/tests/nav_permission_atoms.mjs +1 -1
- package/modules/hall-ui/public/sky.css +0 -365
- package/modules/hall-ui/public/sky.html +0 -79
- package/modules/hall-ui/public/sky.js +0 -580
- package/modules/hall-ui/public/sky.states.json +0 -20
- package/modules/hall-ui/public/thinking.js +0 -418
|
@@ -2,74 +2,135 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<title>Your
|
|
7
|
-
<meta name="description" content="
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
6
|
+
<title>Your sky — {{worldName}}</title>
|
|
7
|
+
<meta name="description" content="Your thinking, drawn from the ledger: every thought you filed as one of five objects, in the goal that caught it, sized by the work it caused.">
|
|
8
8
|
<link rel="icon" href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text x='16' y='24' font-size='24' text-anchor='middle' font-family='sans-serif'>⌂</text></svg>">
|
|
9
9
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
10
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
<!-- The faces (Manrope, JetBrains Mono) come from the branding pack's font
|
|
12
|
+
links, injected into this head by the server like every hall page's
|
|
13
|
+
(tests/hall_tokens.mjs: no hall page hardcodes a web-font stylesheet).
|
|
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">
|
|
13
16
|
</head>
|
|
14
|
-
<!-- THE
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
<!-- THE SKY (task 1004231 / sky part 3 of 5, goal 1000095; ADR 0331, ADR 0227
|
|
18
|
+
§11). This page is docs/design/mocks/sky/sky.template.html — the prototype
|
|
19
|
+
the owner adopted as the design of record — built for real over GET /sky
|
|
20
|
+
and GET /sky/mine (part 2). The markup below is the template's, verbatim;
|
|
21
|
+
the sheet is its <style>; its one script is split into the seven sky-*.js
|
|
22
|
+
files under the 1,500-line ratchet (data → layout → draw → interaction →
|
|
23
|
+
panel → composer → tour). Every deviation a gate or a route forced is
|
|
24
|
+
listed in the shipping task's notes, one line each; anything else that
|
|
25
|
+
differs from the template is a defect.
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
is the least interesting thing on it (modules/hall-ui/CLAUDE.md, the
|
|
31
|
-
two page-head voices). The room is a working surface, not a document,
|
|
32
|
-
so it takes the board's head and not the reading room's. -->
|
|
33
|
-
<div class="page-head page-head--board">
|
|
34
|
-
<h1 class="page-head__h">Your thinking</h1>
|
|
35
|
-
<p class="page-head__sub" id="tr-sub">Loading what you filed…</p>
|
|
36
|
-
</div>
|
|
27
|
+
The file is still `thinking` and the URL still /thinking: this is the
|
|
28
|
+
ideator's home, the page that replaced THE ROOM (task 1004098) — and the
|
|
29
|
+
room survives inside it as the list drawer (L). /sky, the old ambient
|
|
30
|
+
page's URL, 301s here (src/bongos/serve-internal.js). It carries no hall
|
|
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. -->
|
|
33
|
+
<body data-page="thinking" data-page-title="Your sky">
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
<a href="/builders/sky">your sky</a>.
|
|
46
|
-
</p>
|
|
35
|
+
<!-- The one <main> landmark the kit's a11y floor asks of every hall page
|
|
36
|
+
(one-header-main-h1). The template had none; the canvas is the page's
|
|
37
|
+
content, and the landmark wraps it with no rule of its own, so nothing
|
|
38
|
+
moves — the canvas is position: fixed. -->
|
|
39
|
+
<main>
|
|
40
|
+
<canvas id="sky" tabindex="0" role="img" aria-label="The sky, drawn from the ledger. Every thought and goal here is also a row in the list; press L to open it, ? to take the tour."></canvas>
|
|
41
|
+
</main>
|
|
47
42
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
43
|
+
<!-- The template wrote the whole top strip as the <header>. The kit's phone
|
|
44
|
+
floor (top-bar-80px) measures the page's <header>, and the strip wraps to
|
|
45
|
+
125px at 390 by the template's own rule — so the header is the BRAND (the
|
|
46
|
+
h1 and the figures) and the lens and tools stay its siblings inside the
|
|
47
|
+
same .top strip. No selector names the element, so the render is the
|
|
48
|
+
template's, pixel for pixel. -->
|
|
49
|
+
<div class="top">
|
|
50
|
+
<header class="brand">
|
|
51
|
+
<h1>your sky <em id="lensWord">— your thinking</em></h1>
|
|
52
|
+
<div class="stats" id="stats"></div>
|
|
53
|
+
</header>
|
|
54
|
+
<div class="lens" role="group" aria-label="Filter" id="lensGroup">
|
|
55
|
+
<button type="button" id="lensSimple" aria-pressed="false">simple</button>
|
|
56
|
+
<button type="button" id="lensMine" aria-pressed="true">your thinking</button>
|
|
57
|
+
<button type="button" id="lensAll" aria-pressed="false">the whole project</button>
|
|
58
|
+
</div>
|
|
59
|
+
<div class="tools" id="tools">
|
|
60
|
+
<button type="button" class="pill" id="roomBtn" aria-pressed="false" title="The same sky, as a list (L)">list</button>
|
|
61
|
+
<button type="button" class="pill" id="recentBtn" aria-pressed="false" hidden title="Only the thoughts of the last thirty days.">recent</button>
|
|
62
|
+
<button type="button" class="pill" id="claudeBtn" aria-pressed="false" title="Show the thoughts a session filed under your name. Off by default: only what a person filed.">claude layer</button>
|
|
63
|
+
<button type="button" class="pill" id="influenceBtn" aria-pressed="false" title="Only the goals your thoughts touched — everything else fades.">my influence</button>
|
|
64
|
+
<button type="button" class="pill" id="exploreBtn" aria-pressed="true" hidden title="Explore: the sky reveals itself as you follow routes from where you begin.">explore</button>
|
|
65
|
+
<button type="button" class="pill" id="ideasBtn" aria-pressed="false" hidden title="Lay your thoughts over the project map.">my ideas</button>
|
|
66
|
+
<button type="button" class="pill" id="rehearse" aria-pressed="false" title="What if: invents removals, dimming and nods — none has happened in the ledger yet — so you can see how the dead would read. Sizes are never invented.">what if</button>
|
|
67
|
+
<button type="button" class="pill icon" id="clockBtn" title="Show or hide the clock" aria-label="Show or hide the clock">◷</button>
|
|
68
|
+
<button type="button" class="pill icon" id="tourBtn" title="How to read the sky (?)" aria-label="How to read the sky">?</button>
|
|
69
|
+
<button type="button" class="pill icon" id="fitBtn" title="Fit the sky (0)" aria-label="Fit the whole sky">⌂</button>
|
|
60
70
|
</div>
|
|
61
71
|
</div>
|
|
62
72
|
|
|
63
|
-
<div
|
|
73
|
+
<div class="jump">
|
|
74
|
+
<input type="search" id="jump" placeholder="jump to a goal or a thought" autocomplete="off" aria-label="Jump to a goal or a thought">
|
|
75
|
+
<ul id="jumpList" hidden></ul>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div class="composer" id="composerWrap">
|
|
79
|
+
<form id="file" autocomplete="off">
|
|
80
|
+
<input type="text" id="thought" placeholder="release a thought — one line" aria-label="Release a thought" maxlength="180">
|
|
81
|
+
<button type="button" class="mode" id="fullToggle" aria-pressed="false" title="File it as a Full Idea: the line, then five questions, scored on completeness. Off = a quick idea, one line.">full idea</button>
|
|
82
|
+
<button type="submit" class="go" id="fileGo">file</button>
|
|
83
|
+
</form>
|
|
84
|
+
<div class="gravity" id="gravity" aria-live="polite">gravity — the goal that catches it lights as you type; letting it float is a real answer</div>
|
|
85
|
+
<div class="steps" id="fullSteps" hidden></div>
|
|
86
|
+
<div class="receipt" id="receipt" hidden role="status"><span class="dot"></span><span id="receiptText"></span><button type="button" id="receiptGo">see it</button><button type="button" id="receiptX" aria-label="Dismiss">×</button></div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<div class="card glass" id="card" role="tooltip" aria-hidden="true"></div>
|
|
90
|
+
<div class="menu glass" id="menu" hidden role="menu" aria-label="Chart the routes"></div>
|
|
91
|
+
|
|
92
|
+
<aside class="room" id="room" hidden aria-label="The list">
|
|
93
|
+
<div class="head"><h2 id="roomTitle">the room</h2><span class="lbl" id="roomCount"></span></div>
|
|
94
|
+
<div class="filters" id="filters" role="group" aria-label="Filter"></div>
|
|
95
|
+
<div class="rows" id="rows"></div>
|
|
96
|
+
</aside>
|
|
97
|
+
|
|
98
|
+
<aside class="panel" id="panel" hidden aria-live="polite"></aside>
|
|
99
|
+
|
|
100
|
+
<div class="start glass" id="start" hidden role="dialog" aria-label="Where do you begin?">
|
|
101
|
+
<span class="lbl">explore</span>
|
|
102
|
+
<h3>Where do you begin?</h3>
|
|
103
|
+
<p>The sky reveals itself as you go. Pick a well to start in; every route you chart brings the goal at its far end into view.</p>
|
|
104
|
+
<div class="opts" id="startOpts"></div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div class="tour glass" id="tour" hidden role="dialog" aria-label="How to read the sky">
|
|
108
|
+
<div class="step"><span class="lbl" id="tourN"></span><span class="dots" id="tourDots"></span></div>
|
|
109
|
+
<h3 id="tourH"></h3>
|
|
110
|
+
<p id="tourP"></p>
|
|
111
|
+
<div class="acts"><button type="button" class="door primary" id="tourNext">next</button><button type="button" class="door" id="tourBack">back</button><button type="button" class="skip" id="tourSkip">skip the tour</button></div>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<footer class="foot">
|
|
115
|
+
<div class="time" id="time">
|
|
116
|
+
<button type="button" class="play" id="play" aria-pressed="false" aria-label="Replay the sky from the first day">▶</button>
|
|
117
|
+
<input type="range" id="clock" min="0" max="1000" value="1000" aria-label="The day the sky is drawn for">
|
|
118
|
+
<span class="when" id="when">today</span>
|
|
119
|
+
<button type="button" class="hide" id="timeHide" aria-label="Hide the clock">×</button>
|
|
120
|
+
</div>
|
|
121
|
+
<div class="footrow">
|
|
122
|
+
<div class="legend" id="legend"></div>
|
|
123
|
+
<div class="alt" id="alt"></div>
|
|
124
|
+
</div>
|
|
125
|
+
</footer>
|
|
64
126
|
|
|
65
|
-
<script src="/builders/
|
|
66
|
-
<script src="/builders/
|
|
67
|
-
<script src="/builders/
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
<script src="/builders/
|
|
72
|
-
<script src="/builders/
|
|
73
|
-
<script src="/builders/thinking.js?v=2026-09-20-r03"></script>
|
|
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>
|
|
74
135
|
</body>
|
|
75
136
|
</html>
|
|
@@ -1,20 +1,44 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_": "THE
|
|
2
|
+
"_": "THE SKY (task 1004231 / sky part 3 of 5, goal 1000095; ADR 0331) through modules/ui-design/kit/render.js. The page draws GET /sky + GET /sky/mine; against the kit's own stub neither exists, so every state below renders the EMPTY sky — a real state the floors must hold in (the composer, the tour, the list drawer and the lenses all exist with no thought filed). To look at the populated sky, run it against the hall-preview harness, whose sky.json + sky__mine.json fixtures carry the wells and one of each of the five objects:\n\n node scripts/hall-preview/server.js --port 4615 --fixture-me\n node modules/ui-design/kit/render.js --states modules/hall-ui/public/thinking.states.json --base-in http://localhost:4615 --base-out http://localhost:4615\n\nThe tour opens by itself on a first visit (sky-tour-seen), so states that want the bare sky skip it first.",
|
|
3
3
|
"page": "/builders/thinking",
|
|
4
4
|
"surface": "hall-ui",
|
|
5
5
|
"stub": { "prefix": "/builders/" },
|
|
6
|
+
"modes": ["dark"],
|
|
6
7
|
"modeQuery": false,
|
|
7
|
-
"ignoreRequests": ["/api/(gds|bongos)/"],
|
|
8
|
-
"ignoreConsole": ["/api/(gds|bongos)/", "\\b401\\b", "\\b404\\b", "Unauthorized", "Not Found", "load failed"],
|
|
8
|
+
"ignoreRequests": ["/api/(gds|bongos)/", "fonts\\.g(oogleapis|static)\\.com"],
|
|
9
|
+
"ignoreConsole": ["/api/(gds|bongos)/", "\\b401\\b", "\\b404\\b", "Unauthorized", "Not Found", "load failed", "fonts\\.g(oogleapis|static)\\.com"],
|
|
9
10
|
"states": {
|
|
10
|
-
"
|
|
11
|
-
"burning": {
|
|
11
|
+
"tour": {
|
|
12
12
|
"auth": true,
|
|
13
|
-
"_": "The
|
|
14
|
-
"actions": [["wait",
|
|
15
|
-
"expect": { "visible": ["#
|
|
13
|
+
"_": "The first visit: the sky with the tour's first step over it.",
|
|
14
|
+
"actions": [["wait", 1400]],
|
|
15
|
+
"expect": { "visible": ["#sky", "#tour"] }
|
|
16
|
+
},
|
|
17
|
+
"sky": {
|
|
18
|
+
"auth": true,
|
|
19
|
+
"_": "The 'your thinking' lens, bare: composer, lenses, tools, legend and the clock.",
|
|
20
|
+
"actions": [["wait", 1200], ["clickIfVisible", "#tourSkip"], ["wait", 300]],
|
|
21
|
+
"expect": { "visible": ["#sky", "#composerWrap"] }
|
|
22
|
+
},
|
|
23
|
+
"list": {
|
|
24
|
+
"auth": true,
|
|
25
|
+
"_": "The list drawer — the room — opened by the L key.",
|
|
26
|
+
"actions": [["wait", 1200], ["clickIfVisible", "#tourSkip"], ["wait", 200], ["press", "l"], ["wait", 300]],
|
|
27
|
+
"expect": { "visible": ["#room", "#rows"] }
|
|
28
|
+
},
|
|
29
|
+
"simple": {
|
|
30
|
+
"auth": true,
|
|
31
|
+
"_": "The simple lens: thoughts in their wells, recent / all, nothing else to press.",
|
|
32
|
+
"actions": [["wait", 1200], ["clickIfVisible", "#tourSkip"], ["wait", 200], ["click", "#lensSimple"], ["wait", 300]],
|
|
33
|
+
"expect": { "visible": ["#sky", "#recentBtn"] }
|
|
34
|
+
},
|
|
35
|
+
"full-idea": {
|
|
36
|
+
"auth": true,
|
|
37
|
+
"_": "The composer with a line typed: gravity says what would catch it. (The full-idea switch is hidden when the template read fails, as it does against the kit stub.)",
|
|
38
|
+
"actions": [["wait", 1200], ["clickIfVisible", "#tourSkip"], ["wait", 200], ["fill", "#thought", "a thought about the sky"], ["wait", 600]],
|
|
39
|
+
"expect": { "visible": ["#composerWrap", "#gravity"] }
|
|
16
40
|
},
|
|
17
41
|
"out": { "auth": false, "actions": [["wait", 600]] }
|
|
18
42
|
},
|
|
19
|
-
"reducedMotion": { "auth": true, "actions": [["wait",
|
|
43
|
+
"reducedMotion": { "auth": true, "actions": [["wait", 1200], ["clickIfVisible", "#tourSkip"], ["wait", 200]] }
|
|
20
44
|
}
|
|
@@ -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-
|
|
165
|
+
<script src="/builders/shell.js?v=2026-09-24-fleetcosts1"></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-
|
|
174
|
+
<script src="/builders/shell.js?v=2026-09-24-fleetcosts1"></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
|
|
148
|
-
//
|
|
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);
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.1014",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.1014",
|
|
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.
|
|
3
|
+
"version": "1.19.1014",
|
|
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/src/bongos/auth.js
CHANGED
|
@@ -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,
|
package/src/bongos/routes/me.js
CHANGED
|
@@ -127,7 +127,10 @@ const discordIsLinked = (builderId) =>
|
|
|
127
127
|
// this projection omitted it. Adding an atom here is the deliberate act this comment
|
|
128
128
|
// already described; tests/nav_permission_atoms.mjs now fails if shell.js gates on
|
|
129
129
|
// one that is missing, so the act cannot be skipped again.
|
|
130
|
-
|
|
130
|
+
// `idea.triage` (task 1004231): the sky's panel draws "nod — make it claimable"
|
|
131
|
+
// on a thought awaiting a nod only for a builder who may triage ideas; the nod
|
|
132
|
+
// itself runs the existing POST /tasks/:id/promote, which stays the wall.
|
|
133
|
+
const NAV_PERMISSION_ATOMS = Object.freeze(['board.vote.cast', 'core.pin.move', 'idea.triage']);
|
|
131
134
|
|
|
132
135
|
// navPermissionsFor(builderId) → { '<atom>': boolean } over NAV_PERMISSION_ATOMS,
|
|
133
136
|
// or null when authority could not be established (government off, port missing,
|
|
@@ -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,13 +1171,13 @@ 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.
|
|
1169
|
-
// task
|
|
1170
|
-
//
|
|
1171
|
-
//
|
|
1172
|
-
//
|
|
1173
|
-
//
|
|
1174
|
+
// task 1004231 (sky part 3): `thinking` is now THE SKY itself — the prototype
|
|
1175
|
+
// the owner adopted (docs/design/mocks/sky/, ADR 0331) built over GET /sky +
|
|
1176
|
+
// GET /sky/mine, with the room inside it as the list drawer. The old ambient
|
|
1177
|
+
// page at `sky` (task 1004105) is gone; its URL 301s to /thinking below, so it
|
|
1178
|
+
// leaves this regex — one URL cannot be two tiers, and a redirect is not a page.
|
|
1174
1179
|
const SIGNEDIN_BUILDERS_PAGE_RE =
|
|
1175
|
-
/^\/builders\/(?:work|settings|atlas|modules|goals|goal-map|roadmap|copy-desk|studio|ideas|thinking|
|
|
1180
|
+
/^\/builders\/(?:work|settings|atlas|modules|goals|goal-map|roadmap|copy-desk|studio|ideas|thinking|collab|task|idea|primer(?:\.md)?|diagrams)(?:\.html)?\/?$/;
|
|
1176
1181
|
// The diagram image assets under /builders/diagrams/ ride the same tier as the
|
|
1177
1182
|
// page that embeds them.
|
|
1178
1183
|
const SIGNEDIN_BUILDERS_PREFIX_RE = /^\/builders\/diagrams\//;
|
|
@@ -1227,6 +1232,20 @@ function mountInternalSurfaces(app) {
|
|
|
1227
1232
|
res.redirect(301, `${prefix}/work${qs}`);
|
|
1228
1233
|
});
|
|
1229
1234
|
|
|
1235
|
+
// task 1004231 (sky part 3 of 5, ADR 0331): the ideator's sky replaced BOTH
|
|
1236
|
+
// the room (/thinking) and the old ambient sky (/sky, task 1004105) with one
|
|
1237
|
+
// page at /thinking. The old URL survives as a 301 so a bookmark or the
|
|
1238
|
+
// room's old lede link lands on the sky — same shape as the /board → /work
|
|
1239
|
+
// redirect above: apex and subdomain forms, with or without .html, query
|
|
1240
|
+
// string preserved. The page gate never sees /sky: a redirect is not a page.
|
|
1241
|
+
app.get(/^(?:\/builders)?\/sky(?:\.html)?\/?$/, (req, res, next) => {
|
|
1242
|
+
if (isModuleSurfaceHost(req)) return next();
|
|
1243
|
+
const prefix = req.path.startsWith('/builders') ? '/builders' : '';
|
|
1244
|
+
const qi = req.url.indexOf('?');
|
|
1245
|
+
const qs = qi >= 0 ? req.url.slice(qi) : '';
|
|
1246
|
+
res.redirect(301, `${prefix}/thinking${qs}`);
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1230
1249
|
// Onboarding diagram PNGs (V3.R76 #295) live in docs/onboarding/diagrams as the
|
|
1231
1250
|
// single source of truth alongside their Mermaid sources. Serve them here so the
|
|
1232
1251
|
// builders'-hall diagrams page can embed them without duplicating the binaries.
|
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.
|
|
74
|
+
const CORE_VERSION = '1.19.1014'; // 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
|
}
|