@bongos/core 1.19.1012 → 1.19.1013

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 (34) hide show
  1. package/.bongos-core.json +51 -41
  2. package/docs/adr/0331-the-sky-is-the-ideators-home.md +6 -1
  3. package/docs/copy-inventory.md +99 -56
  4. package/docs/copy-registry.json +830 -465
  5. package/docs/file-map.md +1 -1
  6. package/docs/module-api-changelog.md +2 -0
  7. package/modules/hall-ui/public/idea-objects.js +4 -1
  8. package/modules/hall-ui/public/sky-composer.js +155 -0
  9. package/modules/hall-ui/public/sky-data.js +287 -0
  10. package/modules/hall-ui/public/sky-draw.js +244 -0
  11. package/modules/hall-ui/public/sky-interaction.js +137 -0
  12. package/modules/hall-ui/public/sky-layout.js +81 -0
  13. package/modules/hall-ui/public/sky-panel.js +141 -0
  14. package/modules/hall-ui/public/sky-tour.js +80 -0
  15. package/modules/hall-ui/public/thinking.css +203 -267
  16. package/modules/hall-ui/public/thinking.html +119 -58
  17. package/modules/hall-ui/public/thinking.states.json +33 -9
  18. package/package-lock.json +2 -2
  19. package/package.json +1 -1
  20. package/src/bongos/routes/me.js +4 -1
  21. package/src/bongos/serve-internal.js +20 -6
  22. package/src/module-api.js +1 -1
  23. package/tests/hall_audit.mjs +27 -0
  24. package/tests/hall_page_gate_map.mjs +11 -5
  25. package/tests/hall_sky.mjs +393 -405
  26. package/tests/hall_thinking_room.mjs +274 -296
  27. package/tests/helpers.mjs +185 -217
  28. package/tests/ideator_large_dead_thought_proof.mjs +33 -25
  29. package/tests/ideator_traceable_proof.mjs +61 -39
  30. package/modules/hall-ui/public/sky.css +0 -365
  31. package/modules/hall-ui/public/sky.html +0 -79
  32. package/modules/hall-ui/public/sky.js +0 -580
  33. package/modules/hall-ui/public/sky.states.json +0 -20
  34. 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 thinking — {{worldName}}</title>
7
- <meta name="description" content="Everything you filed and what became of it: what it became, how much work it caused, and whether that work is still alive.">
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
- <link rel="stylesheet" href="/builders/style.css?v=2026-07-02-shell1">
12
- <link rel="stylesheet" href="/builders/thinking.css">
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 PAGE THE SPEC CALLS "THE ROOM" (task 1004098 / BV2.R03). The file is
15
- `thinking` and not `room` because `room.css` is already taken, by the hall's
16
- READING-ROOM archetype (the primer, diagrams, drachmae — task 1003306,
17
- ADR 0228), and `board-room` is the government surface. Three unrelated
18
- rooms in one directory is a collision waiting to be made by whoever next
19
- links a sheet into a page; the page's own h1 is the design's phrase, so
20
- nothing about the vocabulary is lost by naming the file after it. -->
21
- <body data-page="thinking" data-page-title="Your thinking">
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
- <div class="app">
24
- <aside class="app-sidebar" id="app-sidebar" aria-label="Primary"></aside>
25
- <div class="app-scrim" id="app-scrim" hidden></div>
26
- <div class="app-body">
27
- <header class="app-topbar" id="app-topbar"></header>
28
- <div class="app-content">
29
- <!-- The LABEL voice, like every other working surface: the page's own name
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
- <main id="hall-main">
39
- <section class="scroll" id="tr-scroll" aria-labelledby="tr-h">
40
- <h2 id="tr-h" class="scroll__h">What you filed, and what became of it</h2>
41
- <p class="scroll__lede">
42
- Your own view of your own thinking. Size is the work a thought caused and
43
- never falls; brightness is how much of that work is still alive — that is
44
- the part that can go out. The same trail is also a map, in
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
- <!-- The figures, the filters and the rows are all written by
49
- thinking.js from ONE read, so nothing here can be a second copy of
50
- a number the server already computed. -->
51
- <div id="tr-figs" class="tr-figs" role="status" aria-live="polite"></div>
52
- <div id="tr-filters" class="tr-filters" role="group" aria-label="Show only"></div>
53
- <div id="tr-body"><p class="tr-note">Loading…</p></div>
54
- <p class="tr-foot" id="tr-foot" hidden></p>
55
- </section>
56
- </main>
57
-
58
- <footer class="app-foot" id="app-foot"></footer>
59
- </div>
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 id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></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/shell.js?v=2026-09-22-movefeedback1"></script>
66
- <script src="/builders/palette.js" defer></script>
67
- <script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
68
- <!-- The filter strip is the kit's tab strip (makeTabs) rather than a fifth
69
- hand-rolled pill row: it already carries the count pills, the hash sync and
70
- the narrow-screen scroller this page would otherwise reinvent. -->
71
- <script src="/builders/hall-kit.js"></script>
72
- <script src="/builders/idea-objects.js?v=2026-09-20-r03"></script>
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 ROOM (task 1004098 / BV2.R03) through modules/ui-design/kit/render.js. Every row on this page comes from ONE authenticated read, so against the kit's own stub it renders its signed-out / empty state — which is a state the floors must hold in and is rendered here deliberately. To look at the populated page, run it against the hall-preview harness, whose inbox__by-builder.json fixture carries one of each of the five objects plus a historical size and an unreadable brightness:\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",
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
- "room": { "auth": true, "actions": [["wait", 900]], "expect": { "visible": ["#tr-figs", "#tr-body"] } },
11
- "burning": {
11
+ "tour": {
12
12
  "auth": true,
13
- "_": "The filter strip is the page's only control, and the state it selects is the one a person opens this page for. clickIfVisible and not click: the tab is HIDDEN when the trail carries no stars, which is exactly what the kit's own stub produces, so a bare click would fail the state rather than render it.",
14
- "actions": [["wait", 900], ["clickIfVisible", "[data-tab=\"star\"]"], ["wait", 200]],
15
- "expect": { "visible": ["#tr-body"] }
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", 900]] }
43
+ "reducedMotion": { "auth": true, "actions": [["wait", 1200], ["clickIfVisible", "#tourSkip"], ["wait", 200]] }
20
44
  }
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.1012",
3
+ "version": "1.19.1013",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.1012",
9
+ "version": "1.19.1013",
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.1012",
3
+ "version": "1.19.1013",
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",
@@ -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
- const NAV_PERMISSION_ATOMS = Object.freeze(['board.vote.cast', 'core.pin.move']);
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,
@@ -1166,13 +1166,13 @@ function mountInternalSurfaces(app) {
1166
1166
  // task 1003479 (goal 1000089): `goal-map` — the map of which goals wait on which,
1167
1167
  // reached from Goals. It reads GET /goals/graph, which is `requireBuilder`, so a
1168
1168
  // page gated any lower would show a signed-out visitor an empty shell.
1169
- // task 1004105 (BV2.R26): `sky` — THE SKY, the ambient view over the same
1170
- // trail the room lists — joins that tier for exactly the same reasons. It is
1171
- // the SAME read (GET /inbox/by-builder/:id with the id from GET /me), drawn
1172
- // as a map instead of a table, so a floor above sign-in would protect on one
1173
- // page what it admits on the other.
1169
+ // task 1004231 (sky part 3): `thinking` is now THE SKY itself — the prototype
1170
+ // the owner adopted (docs/design/mocks/sky/, ADR 0331) built over GET /sky +
1171
+ // GET /sky/mine, with the room inside it as the list drawer. The old ambient
1172
+ // page at `sky` (task 1004105) is gone; its URL 301s to /thinking below, so it
1173
+ // leaves this regex — one URL cannot be two tiers, and a redirect is not a page.
1174
1174
  const SIGNEDIN_BUILDERS_PAGE_RE =
1175
- /^\/builders\/(?:work|settings|atlas|modules|goals|goal-map|roadmap|copy-desk|studio|ideas|thinking|sky|collab|task|idea|primer(?:\.md)?|diagrams)(?:\.html)?\/?$/;
1175
+ /^\/builders\/(?:work|settings|atlas|modules|goals|goal-map|roadmap|copy-desk|studio|ideas|thinking|collab|task|idea|primer(?:\.md)?|diagrams)(?:\.html)?\/?$/;
1176
1176
  // The diagram image assets under /builders/diagrams/ ride the same tier as the
1177
1177
  // page that embeds them.
1178
1178
  const SIGNEDIN_BUILDERS_PREFIX_RE = /^\/builders\/diagrams\//;
@@ -1227,6 +1227,20 @@ function mountInternalSurfaces(app) {
1227
1227
  res.redirect(301, `${prefix}/work${qs}`);
1228
1228
  });
1229
1229
 
1230
+ // task 1004231 (sky part 3 of 5, ADR 0331): the ideator's sky replaced BOTH
1231
+ // the room (/thinking) and the old ambient sky (/sky, task 1004105) with one
1232
+ // page at /thinking. The old URL survives as a 301 so a bookmark or the
1233
+ // room's old lede link lands on the sky — same shape as the /board → /work
1234
+ // redirect above: apex and subdomain forms, with or without .html, query
1235
+ // string preserved. The page gate never sees /sky: a redirect is not a page.
1236
+ app.get(/^(?:\/builders)?\/sky(?:\.html)?\/?$/, (req, res, next) => {
1237
+ if (isModuleSurfaceHost(req)) return next();
1238
+ const prefix = req.path.startsWith('/builders') ? '/builders' : '';
1239
+ const qi = req.url.indexOf('?');
1240
+ const qs = qi >= 0 ? req.url.slice(qi) : '';
1241
+ res.redirect(301, `${prefix}/thinking${qs}`);
1242
+ });
1243
+
1230
1244
  // Onboarding diagram PNGs (V3.R76 #295) live in docs/onboarding/diagrams as the
1231
1245
  // single source of truth alongside their Mermaid sources. Serve them here so the
1232
1246
  // 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.1012'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
74
+ const CORE_VERSION = '1.19.1013'; // 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');
@@ -354,9 +354,22 @@ test('the module doc still records where the 2179 density pass landed', () => {
354
354
  // no shell.js, a private hardcoded palette that silently kept the WCAG-failing
355
355
  // accent when the pack moved (idea 1000600). Task 1002868 adopted them, so the
356
356
  // rule is now universal and exception-free.
357
+ // THE SKY is the one page that is not the hall's chrome, by the owner's decision
358
+ // (task 1004231, ADR 0331): modules/hall-ui/public/thinking.html is the
359
+ // prototype adopted as the design of record — a full-screen black surface with
360
+ // its own header, composer and legend — and its <style> is the pixel contract.
361
+ // It is not the fork this rule was written against: its palette is not a stale
362
+ // private copy of the hall's, it is a different surface's own, pinned by
363
+ // tests/hall_sky.mjs against the template. It still stamps the branding token
364
+ // and the pack's font links like every hall page (the server injects them).
365
+ const STANDALONE_EXEMPT = {
366
+ 'thinking.html': 'the sky (task 1004231, ADR 0331) — the design of record is its own surface, not the hall chrome',
367
+ };
368
+
357
369
  test('every hall page loads style.css and shell.js', () => {
358
370
  const missing = [];
359
371
  for (const f of pages()) {
372
+ if (STANDALONE_EXEMPT[f]) continue;
360
373
  const html = read(f);
361
374
  if (!/<link[^>]+href="[^"]*\bstyle\.css[^"]*"/.test(html)) missing.push(`${f}: no style.css link`);
362
375
  if (!/<script[^>]+src="[^"]*\bshell\.js[^"]*"/.test(html)) missing.push(`${f}: no shell.js`);
@@ -400,6 +413,15 @@ test('no page declares a private copy of a pack color var', () => {
400
413
  const LITERAL_EXEMPT = {
401
414
  'style.css': 'the token tier — this is the ONE file whose job is to hold the literals',
402
415
  'settings.css': 'the Discord button wears Discord\'s brand colour (#5865F2); a third-party mark is not a theme colour',
416
+ // The sky (task 1004231, ADR 0331) is the prototype the owner adopted as the
417
+ // design of record, verbatim: its sheet IS its token tier (a black void, one
418
+ // accent, the chart yellow, and the alpha-white ladder the canvas shares),
419
+ // and the two scripts that write inline colour do so with the template's own
420
+ // values. The atlas precedent: a page-scoped palette, pinned rather than
421
+ // re-tokened.
422
+ 'thinking.css': 'the sky\'s own sheet is its token tier (task 1004231, ADR 0331 — the design of record)',
423
+ 'sky-interaction.js': 'the sky\'s hover card writes the template\'s ring colour inline (task 1004231)',
424
+ 'sky-panel.js': 'the sky\'s panel writes the template\'s dead-bar colour inline (task 1004231)',
403
425
  };
404
426
  const COLOUR_PROPS = 'color|background|background-color|border|border-color|border-top-color|border-bottom-color|fill|stroke|outline|outline-color|box-shadow|text-shadow';
405
427
  const colourLiteralRe = () =>
@@ -465,6 +487,11 @@ test('the verdict classes and the scrim exist in the token tier, in both modes',
465
487
  // exceptions are selectors that are not keyboard stops at all.
466
488
  const OUTLINE_NONE_EXEMPT = {
467
489
  main: 'the skip link\'s programmatic target — main takes tabindex="-1" focus so the reader lands past the nav, and ringing the whole page would be noise, not information',
490
+ // The sky's composer (task 1004231, ADR 0331): the line sits inside a pill
491
+ // form, and the indication is the FORM's ring — `.composer form:focus-within`
492
+ // brightens its inset hairline — which is the template's own design. A ring
493
+ // on the input inside the pill would draw two.
494
+ '.composer input': 'the sky\'s composer line — the indication is the pill form\'s focus-within ring (thinking.css), the template\'s own',
468
495
  };
469
496
 
470
497
  // Innermost rule blocks only, so a rule inside @media is read as itself and the
@@ -68,12 +68,15 @@ const ORACLE = {
68
68
  // having a thought, and the page reads the VIEWER's own trail (the id comes
69
69
  // from GET /me, never from the URL), so a rank could protect nothing the
70
70
  // session does not already.
71
+ // Since task 1004231 (sky part 3, ADR 0331) that page IS the sky — the
72
+ // prototype the owner adopted, over GET /sky + GET /sky/mine, with the room
73
+ // inside it as the list drawer. Both reads are any-builder and the trail is
74
+ // the viewer's own, so sign-in is still the whole gate.
71
75
  '/builders/thinking': 'requireBuilderPage',
72
- // THE SKY (task 1004105 / BV2.R26) — the ambient view over the SAME trail the
73
- // room lists, drawn as a map. It reads the same endpoint with the same
74
- // viewer-derived id, so it sits on the same tier: a rank floor here would
75
- // protect on one page exactly what it admits on the other.
76
- '/builders/sky': 'requireBuilderPage',
76
+ // Gone: the old ambient sky (task 1004105) was folded into /thinking by task
77
+ // 1004231, and /builders/sky now 301s there (serve-internal.js). A redirect is
78
+ // not a page, so it carries no gate; GONE_PAGES keeps the row honest.
79
+ '/builders/sky': null,
77
80
  '/builders/collab': 'requireBuilderPage',
78
81
  // task 1002710: the orientation reading rooms moved DOWN to this tier from a
79
82
  // thetes+ gate. They are the onboarding documents, so gating them above xenos
@@ -301,6 +304,9 @@ const GONE_PAGES = {
301
304
  // Task 1003890 — the dev-box deprecation removed the fleet these two drove.
302
305
  '/builders/harbor': 'task 1003890 — the dev box is gone, so its Archon view is too',
303
306
  '/builders/pair': 'task 1003890 — the dev box is gone, so its pairing page is too',
307
+ // Task 1004231 — the ideator's sky replaced both the room and the old sky with
308
+ // one page at /thinking; /sky is a 301 to it, not a page.
309
+ '/builders/sky': 'task 1004231 — the sky lives at /thinking now; /sky redirects there',
304
310
  };
305
311
 
306
312
  test('a row that is null because the page left the hall serves no page', () => {