@bongos/core 1.21.22 → 1.21.24

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 (58) hide show
  1. package/.bongos-core.json +62 -62
  2. package/docs/copy-inventory.md +128 -123
  3. package/docs/copy-registry.json +222 -187
  4. package/docs/module-api-changelog.md +4 -0
  5. package/docs/page-readings.json +251 -248
  6. package/modules/hall-ui/public/atlas.html +1 -1
  7. package/modules/hall-ui/public/blockers.html +1 -1
  8. package/modules/hall-ui/public/board-room.html +1 -1
  9. package/modules/hall-ui/public/city.html +1 -1
  10. package/modules/hall-ui/public/collab.html +1 -1
  11. package/modules/hall-ui/public/commands.html +1 -1
  12. package/modules/hall-ui/public/copy-desk.html +1 -1
  13. package/modules/hall-ui/public/deploy.html +1 -1
  14. package/modules/hall-ui/public/diagrams.html +1 -1
  15. package/modules/hall-ui/public/drachmae.html +2 -2
  16. package/modules/hall-ui/public/fleet.html +1 -1
  17. package/modules/hall-ui/public/gate.html +1 -1
  18. package/modules/hall-ui/public/goal-map.html +1 -1
  19. package/modules/hall-ui/public/goals.html +1 -1
  20. package/modules/hall-ui/public/government.html +1 -1
  21. package/modules/hall-ui/public/idea.html +5 -7
  22. package/modules/hall-ui/public/ideas.html +1 -1
  23. package/modules/hall-ui/public/index.html +1 -1
  24. package/modules/hall-ui/public/modules.html +1 -1
  25. package/modules/hall-ui/public/primer.html +2 -2
  26. package/modules/hall-ui/public/profile.html +1 -1
  27. package/modules/hall-ui/public/project-settings.html +1 -1
  28. package/modules/hall-ui/public/ranks.html +1 -1
  29. package/modules/hall-ui/public/record.css +4 -19
  30. package/modules/hall-ui/public/roadmap.html +1 -1
  31. package/modules/hall-ui/public/roster.html +1 -1
  32. package/modules/hall-ui/public/sessions.css +16 -0
  33. package/modules/hall-ui/public/sessions.html +14 -2
  34. package/modules/hall-ui/public/sessions.js +111 -3
  35. package/modules/hall-ui/public/settings.html +1 -1
  36. package/modules/hall-ui/public/shell.js +56 -5
  37. package/modules/hall-ui/public/street.html +1 -1
  38. package/modules/hall-ui/public/studio.html +1 -1
  39. package/modules/hall-ui/public/task.html +6 -9
  40. package/modules/hall-ui/public/task.js +15 -50
  41. package/modules/hall-ui/public/task.states.json +1 -1
  42. package/modules/hall-ui/public/thinking.html +1 -1
  43. package/modules/hall-ui/public/tweak-editor.css +4 -0
  44. package/modules/hall-ui/public/tweak-editor.html +5 -3
  45. package/modules/hall-ui/public/watch.html +1 -1
  46. package/modules/hall-ui/public/work.html +1 -1
  47. package/modules/hall-ui/records/hall-navigation.md +7 -4
  48. package/modules/sessions/db.js +4 -0
  49. package/package-lock.json +2 -2
  50. package/package.json +1 -1
  51. package/release-notes.json +12 -0
  52. package/src/module-api.js +1 -1
  53. package/tests/hall_fullscreen.mjs +88 -12
  54. package/tests/hall_record_world.mjs +41 -63
  55. package/tests/hall_sessions_ledger.mjs +40 -0
  56. package/tests/hall_tweak_editor.mjs +3 -2
  57. package/tests/nav_permission_atoms.mjs +1 -1
  58. package/tests/session_transcript.mjs +5 -0
@@ -91,7 +91,7 @@
91
91
 
92
92
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
93
93
 
94
- <script src="/builders/shell.js?v=2026-10-02-theme"></script>
94
+ <script src="/builders/shell.js?v=2026-10-02-back"></script>
95
95
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
96
96
  <script src="/builders/palette.js" defer></script>
97
97
  <script src="/builders/atlas.js?v=2026-09-02-index" defer></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-10-02-theme"></script>
53
+ <script src="/builders/shell.js?v=2026-10-02-back"></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
@@ -60,7 +60,7 @@
60
60
 
61
61
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
62
62
 
63
- <script src="/builders/shell.js?v=2026-10-02-theme"></script>
63
+ <script src="/builders/shell.js?v=2026-10-02-back"></script>
64
64
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
65
65
  <script src="/builders/palette.js" defer></script>
66
66
  <script src="/builders/dom-utils.js?v=2026-06-16-board"></script>
@@ -83,7 +83,7 @@
83
83
  </div>
84
84
  </div>
85
85
 
86
- <script src="/builders/shell.js?v=2026-10-02-theme"></script>
86
+ <script src="/builders/shell.js?v=2026-10-02-back"></script>
87
87
  <script src="/builders/palette.js" defer></script>
88
88
  <script src="/builders/city-draw.js?v=2026-10-02-city1"></script>
89
89
  <script src="/builders/city.js?v=2026-10-02-city1"></script>
@@ -148,7 +148,7 @@
148
148
 
149
149
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
150
150
 
151
- <script src="/builders/shell.js?v=2026-10-02-theme"></script>
151
+ <script src="/builders/shell.js?v=2026-10-02-back"></script>
152
152
  <script src="/builders/palette.js" defer></script>
153
153
  <script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
154
154
  <script src="/builders/collab-lib.js?v=2026-09-24-archive"></script>
@@ -54,7 +54,7 @@
54
54
 
55
55
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
56
56
 
57
- <script src="/builders/shell.js?v=2026-10-02-theme"></script>
57
+ <script src="/builders/shell.js?v=2026-10-02-back"></script>
58
58
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
59
59
  <script src="/builders/palette.js" defer></script>
60
60
  <script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
@@ -177,7 +177,7 @@
177
177
 
178
178
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
179
179
 
180
- <script src="/builders/shell.js?v=2026-10-02-theme"></script>
180
+ <script src="/builders/shell.js?v=2026-10-02-back"></script>
181
181
  <script src="/builders/palette.js" defer></script>
182
182
  <script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
183
183
  <script src="/builders/hall-kit.js"></script>
@@ -74,7 +74,7 @@
74
74
 
75
75
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
76
76
 
77
- <script src="/builders/shell.js?v=2026-10-02-theme"></script>
77
+ <script src="/builders/shell.js?v=2026-10-02-back"></script>
78
78
  <script src="/builders/palette.js" defer></script>
79
79
  <script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
80
80
  <script src="/builders/hall-kit.js"></script>
@@ -97,7 +97,7 @@
97
97
 
98
98
  <!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
99
99
  time so the chrome exists before the deferred page scripts execute. -->
100
- <script src="/builders/shell.js?v=2026-10-02-theme"></script>
100
+ <script src="/builders/shell.js?v=2026-10-02-back"></script>
101
101
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
102
102
  <script src="/builders/palette.js" defer></script>
103
103
  <script src="/builders/diagram-viewer.js?v=2026-06-14-diagviewer-1" defer></script>
@@ -84,7 +84,7 @@
84
84
  <div class="primer-note primer-note--error scroll" id="primer-error" hidden>
85
85
  <p class="primer-note__label">The reference did not load</p>
86
86
  <p class="primer-note__body">The markdown behind this page could not be fetched. Reload to try again, or read it in the repo.</p>
87
- <p class="primer-note__acts"><a class="btn btn--ghost" href="/builders">Back to home</a></p>
87
+ <p class="primer-note__acts"><a class="btn btn--ghost" data-shell-exit href="/builders">Back to home</a></p>
88
88
  </div>
89
89
 
90
90
  </div>
@@ -98,7 +98,7 @@
98
98
 
99
99
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
100
100
 
101
- <script src="/builders/shell.js?v=2026-10-02-theme"></script>
101
+ <script src="/builders/shell.js?v=2026-10-02-back"></script>
102
102
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
103
103
  <script src="/builders/palette.js" defer></script>
104
104
  <script src="/builders/md-reader.js?v=2026-09-02-mdreader-2"></script>
@@ -54,7 +54,7 @@
54
54
 
55
55
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
56
56
 
57
- <script src="/builders/shell.js?v=2026-10-02-theme"></script>
57
+ <script src="/builders/shell.js?v=2026-10-02-back"></script>
58
58
  <script src="/builders/palette.js" defer></script>
59
59
  <script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
60
60
  <script src="/builders/hall-kit.js"></script>
@@ -77,7 +77,7 @@
77
77
  <div id="gate-modal-root"></div>
78
78
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
79
79
 
80
- <script src="/builders/shell.js?v=2026-10-02-theme"></script>
80
+ <script src="/builders/shell.js?v=2026-10-02-back"></script>
81
81
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
82
82
  <script src="/builders/palette.js" defer></script>
83
83
  <script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
@@ -137,7 +137,7 @@
137
137
  </div>
138
138
  </div>
139
139
 
140
- <script src="/builders/shell.js?v=2026-10-02-theme"></script>
140
+ <script src="/builders/shell.js?v=2026-10-02-back"></script>
141
141
  <script src="/builders/palette.js" defer></script>
142
142
 
143
143
  <script src="/builders/idea-objects.js?v=2026-09-27-split"></script>
@@ -230,7 +230,7 @@
230
230
 
231
231
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
232
232
 
233
- <script src="/builders/shell.js?v=2026-10-02-theme"></script>
233
+ <script src="/builders/shell.js?v=2026-10-02-back"></script>
234
234
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
235
235
  <script src="/builders/palette.js" defer></script>
236
236
  <script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
@@ -134,7 +134,7 @@
134
134
 
135
135
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
136
136
 
137
- <script src="/builders/shell.js?v=2026-10-02-theme"></script>
137
+ <script src="/builders/shell.js?v=2026-10-02-back"></script>
138
138
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
139
139
  <script src="/builders/palette.js" defer></script>
140
140
  <script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
@@ -14,7 +14,7 @@
14
14
  <link rel="stylesheet" href="/builders/record.css">
15
15
  <link rel="stylesheet" href="/builders/idea.css">
16
16
  </head>
17
- <body data-page="idea" data-page-title="Idea">
17
+ <body data-page="idea" data-page-title="Idea" data-back-fallback="/thinking">
18
18
 
19
19
  <div class="app">
20
20
  <aside class="app-sidebar" id="app-sidebar" aria-label="Primary"></aside>
@@ -23,12 +23,10 @@
23
23
  <header class="app-topbar" id="app-topbar"></header>
24
24
  <div class="app-content">
25
25
  <div class="page-head">
26
- <!-- This page is deliberately not in the nav (it is always reached from a
27
- link), so a deep-linked visitor needs a way back before the body loads
28
- — the task.html precedent. -->
26
+ <!-- task 1004477: the way back is the shell's top-bar Back link. This
27
+ page is deliberately not in the nav, so <body data-back-fallback>
28
+ sends a deep-linked visitor to their thinking. -->
29
29
  <div class="rec-crumb">
30
- <a class="rec-back" href="/thinking">
31
- <svg class="rec-back__ic" viewBox="0 0 16 16" aria-hidden="true"><path d="M9.4 3.6 5 8l4.4 4.4"/><path d="M5 8h7.2"/></svg>Back to your thinking</a>
32
30
  <p class="rec-sub" id="idea-sub">Loading idea</p>
33
31
  </div>
34
32
  </div>
@@ -52,7 +50,7 @@
52
50
 
53
51
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
54
52
 
55
- <script src="/builders/shell.js?v=2026-10-02-theme"></script>
53
+ <script src="/builders/shell.js?v=2026-10-02-back"></script>
56
54
  <script src="/builders/palette.js" defer></script>
57
55
  <script src="/builders/dom-utils.js?v=2026-06-23-contrib" defer></script>
58
56
  <!-- task-detail.js still owns the #/idea/N and #/blocker/N overlay, so a ref
@@ -225,7 +225,7 @@
225
225
 
226
226
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
227
227
 
228
- <script src="/builders/shell.js?v=2026-10-02-theme"></script>
228
+ <script src="/builders/shell.js?v=2026-10-02-back"></script>
229
229
  <script src="/builders/palette.js" defer></script>
230
230
  <script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
231
231
  <!-- task 1003069 (R08) — the quick-filing form's routed result links a task
@@ -106,7 +106,7 @@
106
106
  <script src="/builders/brand-apply.js?v=2026-06-27-r75" defer></script>
107
107
  <!-- The app shell (sidebar + top bar + view router). Dependency-free; loaded
108
108
  BEFORE builders.js so the chrome ids exist when events are wired. -->
109
- <script src="/builders/shell.js?v=2026-10-02-theme" defer></script>
109
+ <script src="/builders/shell.js?v=2026-10-02-back" defer></script>
110
110
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
111
111
  <script src="/builders/palette.js" defer></script>
112
112
  <!-- task 2166: the shared component kit (window.OTBKit) — pager/ledger/rail.
@@ -101,7 +101,7 @@
101
101
 
102
102
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
103
103
 
104
- <script src="/builders/shell.js?v=2026-10-02-theme"></script>
104
+ <script src="/builders/shell.js?v=2026-10-02-back"></script>
105
105
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
106
106
  <script src="/builders/palette.js" defer></script>
107
107
  <script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
@@ -87,7 +87,7 @@
87
87
  <div class="primer-note primer-note--error scroll" id="primer-error" hidden>
88
88
  <p class="primer-note__label">The primer did not load</p>
89
89
  <p class="primer-note__body">The markdown behind this page could not be fetched. Reload to try again, or read it in the repo.</p>
90
- <p class="primer-note__acts"><a class="btn btn--ghost" href="/builders">Back to home</a></p>
90
+ <p class="primer-note__acts"><a class="btn btn--ghost" data-shell-exit href="/builders">Back to home</a></p>
91
91
  </div>
92
92
 
93
93
  <!-- Footer ack module. The button is soft-gated with aria-disabled
@@ -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-10-02-theme"></script>
118
+ <script src="/builders/shell.js?v=2026-10-02-back"></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
@@ -206,7 +206,7 @@
206
206
 
207
207
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
208
208
  chrome exists before the page script runs. -->
209
- <script src="/builders/shell.js?v=2026-10-02-theme"></script>
209
+ <script src="/builders/shell.js?v=2026-10-02-back"></script>
210
210
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
211
211
  <script src="/builders/palette.js" defer></script>
212
212
  <!-- 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-10-02-theme"></script>
61
+ <script src="/builders/shell.js?v=2026-10-02-back"></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>
@@ -77,7 +77,7 @@
77
77
 
78
78
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
79
79
  chrome exists before the page script runs. -->
80
- <script src="/builders/shell.js?v=2026-10-02-theme"></script>
80
+ <script src="/builders/shell.js?v=2026-10-02-back"></script>
81
81
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
82
82
  <script src="/builders/palette.js" defer></script>
83
83
  <!-- shared frontend toolkit (window.OTB) — loaded before ranks.js, which pulls from it. -->
@@ -23,12 +23,10 @@
23
23
  spending one is legal CSS that quietly opts this sheet back out of the world.
24
24
  `--line` is a WIDTH; the hairline's colour is --rule / --rule-soft. */
25
25
 
26
- /* ---- the way back ------------------------------------------------------- */
27
- /* These two pages are deliberately not in the sidebar (both are always reached
28
- from a link), so a deep-linked visitor needs a way back BEFORE the record has
29
- loaded — which is why the crumb is static markup and not rendered by the page
30
- script. 3px of padding buys the 24px control floor without changing the line's
31
- own leading; the link measured 21px on task.html and 20px on idea.html. */
26
+ /* ---- the crumb ----------------------------------------------------------- */
27
+ /* task 1004477: the way back left this line for the shell's top-bar Back link
28
+ (with <body data-back-fallback> for a deep-linked visitor), so the crumb is
29
+ the record's address and nothing else. */
32
30
  .rec-crumb {
33
31
  display: flex;
34
32
  align-items: baseline;
@@ -36,19 +34,6 @@
36
34
  flex-wrap: wrap;
37
35
  margin: 0 0 4px;
38
36
  }
39
- .rec-back {
40
- display: inline-flex;
41
- align-items: center;
42
- gap: 7px;
43
- min-height: 24px;
44
- padding: 3px 0;
45
- color: var(--ink-soft);
46
- font-size: 13.5px;
47
- text-transform: lowercase;
48
- text-decoration: none;
49
- }
50
- .rec-back:hover { color: var(--ink); text-decoration: underline; }
51
- .rec-back__ic { width: 14px; height: 14px; flex: none; stroke: currentColor; fill: none; stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }
52
37
  /* The sub line is the record's address, so it is tabular: an id that re-renders
53
38
  must not re-space the line it sits in. */
54
39
  .rec-sub {
@@ -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-10-02-theme"></script>
44
+ <script src="/builders/shell.js?v=2026-10-02-back"></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>
@@ -53,7 +53,7 @@
53
53
 
54
54
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
55
55
  chrome exists before the page script runs. -->
56
- <script src="/builders/shell.js?v=2026-10-02-theme"></script>
56
+ <script src="/builders/shell.js?v=2026-10-02-back"></script>
57
57
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
58
58
  <script src="/builders/palette.js" defer></script>
59
59
  <!-- shared frontend toolkit (window.OTB — rank labels, number formats) + the
@@ -36,3 +36,19 @@
36
36
  white-space: nowrap — task 1003470 removed the blanket nowrap that broke this
37
37
  table on a phone, and "Automated run" is short enough to wrap harmlessly. */
38
38
  .ledger-kind { color: var(--ink-soft); }
39
+
40
+ /* task 1002830 — the transcript reader. Prose first: a conversation is read, not
41
+ scanned, so turns are a plain stack with the speaker as a quiet label rather than
42
+ a table. Tool calls and results are folded (<details>) because they are the bulk
43
+ of a transcript and the least interesting part of the story. pre-wrap keeps the
44
+ builder's own line breaks; overflow-wrap stops a long path pushing the card wide. */
45
+ .ledger-read { margin-left: 8px; font-size: 12px; }
46
+ .transcript { display: flex; flex-direction: column; gap: var(--sp-sm); }
47
+ .turn { border-left: 3px solid var(--rule); padding: 4px 0 4px 12px; }
48
+ .turn--user { border-left-color: var(--accent-ink); }
49
+ .turn__who { font-size: 12px; color: var(--ink-faint); margin: 0 0 2px; }
50
+ .turn__text { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
51
+ .turn details { margin-top: 4px; font-size: 13px; color: var(--ink-soft); }
52
+ .turn pre { margin: 4px 0 0; white-space: pre-wrap; overflow-wrap: anywhere; font-family: var(--font-mono); font-size: 12px; }
53
+ .turn__err { color: var(--danger); }
54
+ .transcript__note { color: var(--ink-faint); font-size: 13px; }
@@ -88,6 +88,18 @@
88
88
  <div id="sessions-pager-mount"></div>
89
89
  <p class="ledger__note" id="ledger-foot"></p>
90
90
  </section>
91
+
92
+ <!-- task 1002830: the narrative. Opened from a row's Read door; the SAME page
93
+ and the same gate as the ledger above (ADR 0051 — no second page). The
94
+ server decides who may read a transcript and records an administrator's
95
+ cross-builder read BEFORE serving it; this panel only displays. -->
96
+ <section class="scroll" id="transcript-scroll" aria-labelledby="transcript-h" hidden>
97
+ <h2 id="transcript-h" class="scroll__h">Transcript</h2>
98
+ <p class="scroll__lede" id="transcript-sub"></p>
99
+ <p><button type="button" class="btn" id="transcript-close">Close transcript</button></p>
100
+ <div id="transcript-body" class="transcript"></div>
101
+ <p><button type="button" class="btn" id="transcript-more" hidden>Load more turns</button></p>
102
+ </section>
91
103
  </main>
92
104
 
93
105
  <footer class="app-foot" id="app-foot"></footer>
@@ -99,7 +111,7 @@
99
111
 
100
112
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
101
113
  chrome exists before the page script runs. -->
102
- <script src="/builders/shell.js?v=2026-10-02-theme"></script>
114
+ <script src="/builders/shell.js?v=2026-10-02-back"></script>
103
115
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
104
116
  <script src="/builders/palette.js" defer></script>
105
117
  <!-- shared frontend toolkit (window.OTB) — loaded BEFORE sessions.js so it can pull from it. -->
@@ -110,6 +122,6 @@
110
122
  sessions.js, which reads window.OTBKit at boot. -->
111
123
  <script src="/builders/hall-kit.js"></script>
112
124
  <script src="/builders/brand-apply.js?v=2026-06-19-r56"></script>
113
- <script src="/builders/sessions.js?v=2026-08-28-kitpager"></script>
125
+ <script src="/builders/sessions.js?v=2026-10-02-transcript"></script>
114
126
  </body>
115
127
  </html>
@@ -8,7 +8,10 @@
8
8
  // returns; it is not the authority. On a 401 it redirects into the web sign-in
9
9
  // flow, like the other hall pages. Redesigned in task 1003470 (goal 1000081):
10
10
  // the totals on the shell's top strip, the ledger without its nowrap + wrapper
11
- // pair, every id on a row a door, the floor named from the pack.
11
+ // pair, every id on a row a door, the floor named from the pack. Task 1002830 added
12
+ // the narrative: a row's Read door opens the session's full transcript (ADR 0051)
13
+ // on THIS page, under the same gate — the owner or an Archon, the latter audited
14
+ // by the server before it serves a word.
12
15
 
13
16
  (() => {
14
17
  'use strict';
@@ -159,7 +162,7 @@
159
162
  const errs = num(s.tool_errors);
160
163
  return (
161
164
  '<tr>' +
162
- `<td><span class="ov-mono" title="${escapeHtml(s.session_id)}">${escapeHtml(shortSid(s.session_id))}</span></td>` +
165
+ `<td><span class="ov-mono" title="${escapeHtml(s.session_id)}">${escapeHtml(shortSid(s.session_id))}</span>${readDoor(s)}</td>` +
163
166
  `<td>${whoHtml}` +
164
167
  (s.rank ? ` <span class="ledger-rank">${escapeHtml(rankLabel(s.rank))}</span>` : '') +
165
168
  '</td>' +
@@ -178,6 +181,105 @@
178
181
  .join('');
179
182
  }
180
183
 
184
+ // ---- the transcript reader (task 1002830, ADR 0051) -----------------------
185
+
186
+ // A Read door only where there is something to read AND the server will serve it.
187
+ function canRead(s) {
188
+ if (!s.has_transcript || !viewer) return false;
189
+ return viewer.rank === 'archon' || String(s.builder_id) === String(viewer.id);
190
+ }
191
+ function readDoor(s) {
192
+ return canRead(s)
193
+ ? ` <button type="button" class="btn ledger-read" data-read="${escapeHtml(String(s.id))}" data-sid="${escapeHtml(shortSid(s.session_id))}">Read</button>`
194
+ : '';
195
+ }
196
+
197
+ // One turn as markup. EVERY string goes through escapeHtml — the stored text is
198
+ // scrubbed of secrets, not of markup, and it is arbitrary builder + tool output.
199
+ function turnHtml(t) {
200
+ const user = t.role === 'user';
201
+ let html = `<div class="turn turn--${user ? 'user' : 'assistant'}"><p class="turn__who">${user ? 'Builder' : 'Claude'}${t.ts ? ' · ' + escapeHtml(fmtDate(t.ts)) : ''}</p>`;
202
+ if (t.text) html += `<p class="turn__text">${escapeHtml(t.text)}</p>`;
203
+ for (const c of Array.isArray(t.tool_calls) ? t.tool_calls : []) {
204
+ html += `<details><summary>Ran ${escapeHtml(String(c.name || 'a tool'))}</summary><pre>${escapeHtml(c.input || '')}</pre></details>`;
205
+ }
206
+ for (const r of Array.isArray(t.tool_results) ? t.tool_results : []) {
207
+ html += `<details><summary${r.is_error ? ' class="turn__err"' : ''}>${r.is_error ? 'Tool error' : 'Tool result'}</summary><pre>${escapeHtml(r.text || '')}</pre></details>`;
208
+ }
209
+ return html + '</div>';
210
+ }
211
+
212
+ // `seq` is the open-reader generation: Read on another row while a fetch is in
213
+ // flight bumps it, and the older fetch then drops its answer instead of painting
214
+ // one session's turns into another's panel (the same idiom as `inFlight` above).
215
+ const reader = { id: null, sid: '', offset: 0, total: 0, busy: false, seq: 0 };
216
+
217
+ async function loadTranscript(reset) {
218
+ if (reader.id == null) return;
219
+ // Load-more while a page is already loading is a no-op; opening a DIFFERENT
220
+ // session is not (openTranscript bumps seq and clears busy).
221
+ if (reader.busy && !reset) return;
222
+ const seq = reader.seq;
223
+ const id = reader.id;
224
+ const sid = reader.sid;
225
+ reader.busy = true;
226
+ const more = $('#transcript-more');
227
+ more.disabled = true;
228
+ try {
229
+ const res = await api.request('GET', `${API}/sessions/${encodeURIComponent(reader.id)}/transcript?offset=${reset ? 0 : reader.offset}`);
230
+ if (seq !== reader.seq) return;
231
+ if (res.status === 401) { window.location.assign(`${API}/auth/web/start`); return; }
232
+ if (res.status === 404) {
233
+ $('#transcript-sub').textContent = `Session ${sid}.`;
234
+ $('#transcript-body').innerHTML = '<p class="transcript__note">There is no transcript you can read for this session.</p>';
235
+ more.hidden = true;
236
+ return;
237
+ }
238
+ if (!res.ok) throw new Error(`transcript → ${res.status}`);
239
+ const tr = res.data && res.data.transcript;
240
+ const turns = (tr && Array.isArray(tr.turns)) ? tr.turns : [];
241
+ reader.total = num(tr && tr.turn_count);
242
+ const body = $('#transcript-body');
243
+ if (reset) body.innerHTML = '';
244
+ body.insertAdjacentHTML('beforeend', turns.map(turnHtml).join(''));
245
+ reader.offset += turns.length;
246
+ more.hidden = !(turns.length > 0 && reader.offset < reader.total);
247
+ const trunc = tr && tr.truncated ? ' The middle of this very long session was left out when it was recorded.' : '';
248
+ $('#transcript-sub').textContent =
249
+ `Session ${sid} — showing ${fmtInt(reader.offset)} of ${fmtInt(reader.total)} turns.${trunc}`;
250
+ } catch (err) {
251
+ console.error('[sessions] failed to load transcript', err);
252
+ toast('Could not load the transcript.');
253
+ } finally {
254
+ if (seq === reader.seq) { reader.busy = false; more.disabled = false; }
255
+ }
256
+ }
257
+
258
+ function openTranscript(id, sid) {
259
+ reader.seq += 1; reader.busy = false;
260
+ reader.id = id; reader.sid = sid; reader.offset = 0; reader.total = 0;
261
+ $('#transcript-scroll').hidden = false;
262
+ $('#transcript-body').innerHTML = '';
263
+ $('#transcript-sub').textContent = `Session ${sid} — loading…`;
264
+ $('#transcript-more').hidden = true;
265
+ $('#transcript-scroll').scrollIntoView({ behavior: 'smooth', block: 'start' });
266
+ loadTranscript(true);
267
+ }
268
+
269
+ function wireReader() {
270
+ $('#sessions-body').addEventListener('click', (e) => {
271
+ const b = e.target.closest && e.target.closest('[data-read]');
272
+ if (b) openTranscript(b.getAttribute('data-read'), b.getAttribute('data-sid'));
273
+ });
274
+ $('#transcript-more').addEventListener('click', () => loadTranscript(false));
275
+ $('#transcript-close').addEventListener('click', () => {
276
+ reader.seq += 1; reader.busy = false;
277
+ reader.id = null;
278
+ $('#transcript-scroll').hidden = true;
279
+ $('#transcript-body').innerHTML = '';
280
+ });
281
+ }
282
+
181
283
  // The floor is written from the pack's rank label (ADR 0157: metic and up),
182
284
  // never a hardcoded rank name — the page used to say "Archons only".
183
285
  function showSealed(message) {
@@ -243,6 +345,9 @@
243
345
  } catch { /* no-op: the filter still works for this visit */ }
244
346
  }
245
347
  let loading = false;
348
+ // The viewer, kept so a row only offers Read where the server will answer: a
349
+ // transcript is the owner's, or an Archon's (audited). Cosmetic — the route is the wall.
350
+ let viewer = null;
246
351
  // Two clicks can be in flight at once; only the newest may paint (the profile
247
352
  // activity-log idiom, task 1003281).
248
353
  let inFlight = 0;
@@ -338,7 +443,7 @@
338
443
  $('#ledger-sub').textContent =
339
444
  `${fmtInt(totals.total)}${scope} session${num(totals.total) === 1 ? '' : 's'} across ${fmtInt(totals.builders)} builder${num(totals.builders) === 1 ? '' : 's'}.`;
340
445
  $('#ledger-foot').textContent =
341
- 'Most recent first. Each load of this page is itself recorded in the audit log.';
446
+ `Most recent first. Each load of this page is itself recorded in the audit log, and so is every transcript ${rankLabel('archon')} opens.`;
342
447
  return clamped;
343
448
  }
344
449
 
@@ -445,6 +550,9 @@
445
550
  return;
446
551
  }
447
552
 
553
+ viewer = { id: builder.id, rank: builder.rank };
554
+ wireReader();
555
+
448
556
  if (!kit) {
449
557
  console.error('[sessions] OTBKit did not load — the pager cannot be built');
450
558
  $('#ledger-sub').textContent = 'The session log could not be loaded. Try again.';
@@ -435,7 +435,7 @@
435
435
 
436
436
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
437
437
  chrome exists before the page scripts run. -->
438
- <script src="/builders/shell.js?v=2026-10-02-theme"></script>
438
+ <script src="/builders/shell.js?v=2026-10-02-back"></script>
439
439
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
440
440
  <script src="/builders/palette.js" defer></script>
441
441
  <!-- shared frontend toolkit (window.OTB) — loaded before consuming IIFEs. -->