@bongos/core 1.21.22 → 1.21.23

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 (53) hide show
  1. package/.bongos-core.json +57 -57
  2. package/docs/copy-inventory.md +105 -108
  3. package/docs/copy-registry.json +129 -166
  4. package/docs/module-api-changelog.md +2 -0
  5. package/docs/page-readings.json +246 -243
  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.html +1 -1
  33. package/modules/hall-ui/public/settings.html +1 -1
  34. package/modules/hall-ui/public/shell.js +56 -5
  35. package/modules/hall-ui/public/street.html +1 -1
  36. package/modules/hall-ui/public/studio.html +1 -1
  37. package/modules/hall-ui/public/task.html +6 -9
  38. package/modules/hall-ui/public/task.js +15 -50
  39. package/modules/hall-ui/public/task.states.json +1 -1
  40. package/modules/hall-ui/public/thinking.html +1 -1
  41. package/modules/hall-ui/public/tweak-editor.css +4 -0
  42. package/modules/hall-ui/public/tweak-editor.html +5 -3
  43. package/modules/hall-ui/public/watch.html +1 -1
  44. package/modules/hall-ui/public/work.html +1 -1
  45. package/modules/hall-ui/records/hall-navigation.md +7 -4
  46. package/package-lock.json +2 -2
  47. package/package.json +1 -1
  48. package/release-notes.json +6 -0
  49. package/src/module-api.js +1 -1
  50. package/tests/hall_fullscreen.mjs +88 -12
  51. package/tests/hall_record_world.mjs +41 -63
  52. package/tests/hall_tweak_editor.mjs +3 -2
  53. package/tests/nav_permission_atoms.mjs +1 -1
@@ -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
@@ -99,7 +99,7 @@
99
99
 
100
100
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
101
101
  chrome exists before the page script runs. -->
102
- <script src="/builders/shell.js?v=2026-10-02-theme"></script>
102
+ <script src="/builders/shell.js?v=2026-10-02-back"></script>
103
103
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
104
104
  <script src="/builders/palette.js" defer></script>
105
105
  <!-- shared frontend toolkit (window.OTB) — loaded BEFORE sessions.js so it can pull from it. -->
@@ -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. -->
@@ -513,9 +513,16 @@
513
513
  // not a drill-down and shows none: the sidebar click leaves a one-shot note
514
514
  // (sessionStorage) naming its target, and the page it lands on reads it.
515
515
  // The origin is `?from=` (a link that knows where it came from), else the
516
- // same-origin referrer. A page drawing its own back link (.rec-back — task,
517
- // idea — or any element marked data-own-back, the tweak editor's "← studio")
518
- // keeps it and the bar adds no second one.
516
+ // same-origin referrer.
517
+ //
518
+ // task 1004477: this is the ONLY place a hall page's way back is decided —
519
+ // no page draws its own Back link any more. Two hooks cover what the page
520
+ // copies used to:
521
+ // - <body data-back-fallback="/work"> names where Back goes when there is
522
+ // no origin at all (a deep-linked task or idea, the tweak editor);
523
+ // - a link marked [data-shell-exit] is a second exit the page places (an
524
+ // error state's button, the tweak editor's bar in full screen, where the
525
+ // top bar is hidden), and the shell fills its address and its words.
519
526
  var NAV_HOP_KEY = 'hall-nav-hop';
520
527
  function hallPath(url) {
521
528
  return String(url.pathname).replace(/^\/builders(?=\/|$)/, '').replace(/\.html$/, '').replace(/\/index$/, '') || '/';
@@ -527,8 +534,10 @@
527
534
  window.sessionStorage.removeItem(NAV_HOP_KEY);
528
535
  } catch (_) { /* private mode: no note, so fall through */ }
529
536
  if (hop && hop === hallPath(location)) return null;
530
- if (!document.querySelector || document.querySelector('.rec-back, [data-own-back]')) return null;
531
- var here = hallPath(location) + (location.search || '');
537
+ return linkOrigin() || fallbackOrigin();
538
+ }
539
+ // The page this one was opened from: ?from=, then a same-origin referrer.
540
+ function linkOrigin() {
532
541
  var from = null;
533
542
  var viaReferrer = false;
534
543
  try {
@@ -544,9 +553,48 @@
544
553
  var params = new URLSearchParams(from.search);
545
554
  params.delete('from');
546
555
  var search = params.toString() ? '?' + params.toString() : '';
556
+ // This page's address without its own ?from=, so `?from=` naming the page
557
+ // itself (a loop, not a way back) is caught like any other self-link.
558
+ var ownParams = new URLSearchParams(location.search || '');
559
+ ownParams.delete('from');
560
+ var here = hallPath(location) + (ownParams.toString() ? '?' + ownParams.toString() : '');
547
561
  if (path + search === here) return null;
548
562
  return { href: path + search + (from.hash || ''), path: path, label: backLabel(path, from.hash), viaReferrer: viaReferrer };
549
563
  }
564
+ // Where Back goes with no origin: the page's own <body data-back-fallback>,
565
+ // honoured only as a path on this host (one leading slash, never the API).
566
+ function fallbackOrigin() {
567
+ var f = document.body && document.body.dataset && document.body.dataset.backFallback;
568
+ if (!f || !/^\/(?!\/)/.test(f) || /[\\\s]/.test(f)) return null;
569
+ var u;
570
+ try { u = new URL(f, location.origin); } catch (_) { return null; }
571
+ if (u.origin !== location.origin) return null;
572
+ var path = hallPath(u);
573
+ if (/^\/(api|auth|lib)(\/|$)/.test(path) || path === hallPath(location)) return null;
574
+ return { href: path + (u.search || '') + (u.hash || ''), path: path, label: backLabel(path, u.hash), viaReferrer: false };
575
+ }
576
+ // "Back to tasks", "Back to goal 12": a nav label reads as a place mid-sentence,
577
+ // so it is lowercased unless it carries an acronym ("API docs").
578
+ function exitWords(label) {
579
+ return 'Back to ' + (/[A-Z]{2}/.test(label) ? label : label.toLowerCase());
580
+ }
581
+ // Fill every [data-shell-exit] link under `root` with the way back. With no
582
+ // way back at all the exit still works: it goes home. A page script that
583
+ // renders an exit later (task.js's failure states) calls this once it has.
584
+ function fillExits(root) {
585
+ var scope = root || document;
586
+ if (!scope.querySelectorAll) return;
587
+ var target = BACK || { href: '/', label: 'Home', viaReferrer: false };
588
+ var links = scope.querySelectorAll('[data-shell-exit]');
589
+ for (var i = 0; i < links.length; i++) {
590
+ links[i].setAttribute('href', target.href);
591
+ links[i].textContent = exitWords(target.label);
592
+ if (!links[i].dataset.shellExitWired) {
593
+ links[i].dataset.shellExitWired = '1';
594
+ links[i].addEventListener('click', onBackClick);
595
+ }
596
+ }
597
+ }
550
598
  function backLabel(path, hash) {
551
599
  if (path === '/') {
552
600
  var m = /^#\/(task|idea|blocker)\/(\d+)/.exec(hash || '');
@@ -1381,6 +1429,7 @@
1381
1429
  applyFullscreen(storedFullscreen());
1382
1430
  applyAccess(null); // default: everything gated until access is known
1383
1431
  wire();
1432
+ fillExits();
1384
1433
  if (IS_INDEX) {
1385
1434
  route({ scroll: false });
1386
1435
  } else {
@@ -1407,5 +1456,7 @@
1407
1456
  // task 1004544: a page with its own light/dark switch (the governor city
1408
1457
  // and street) repaints the top bar's after writing 'hall-theme'.
1409
1458
  applyTheme: applyTheme,
1459
+ // task 1004477: a page that renders an exit after boot asks the shell to fill it.
1460
+ fillExits: fillExits,
1410
1461
  };
1411
1462
  })();
@@ -203,7 +203,7 @@
203
203
  </div>
204
204
  </div>
205
205
 
206
- <script src="/builders/shell.js?v=2026-10-02-theme"></script>
206
+ <script src="/builders/shell.js?v=2026-10-02-back"></script>
207
207
  <script src="/builders/palette.js" defer></script>
208
208
  <script src="/builders/city-draw.js?v=2026-10-02-street1"></script>
209
209
  <script src="/builders/street-draw.js?v=2026-10-02-street1"></script>
@@ -176,7 +176,7 @@
176
176
 
177
177
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
178
178
 
179
- <script src="/builders/shell.js?v=2026-10-02-theme"></script>
179
+ <script src="/builders/shell.js?v=2026-10-02-back"></script>
180
180
  <script src="/builders/palette.js" defer></script>
181
181
  <script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
182
182
  <!-- The kit, for its ONE renderer: the full-size ship-time visual figure
@@ -15,7 +15,7 @@
15
15
  <link rel="stylesheet" href="/builders/record.css">
16
16
  <link rel="stylesheet" href="/builders/task.css">
17
17
  </head>
18
- <body data-page="task" data-page-title="Task">
18
+ <body data-page="task" data-page-title="Task" data-back-fallback="/work">
19
19
 
20
20
  <div class="app">
21
21
  <aside class="app-sidebar" id="app-sidebar" aria-label="Primary"></aside>
@@ -24,14 +24,11 @@
24
24
  <header class="app-topbar" id="app-topbar"></header>
25
25
  <div class="app-content">
26
26
  <div class="page-head">
27
- <!-- task 2175: the breadcrumb sits in the page head, above the card, so a
28
- deep-linked visitor (this page is deliberately not in the nav) has a
29
- way back before the task body has loaded. task 1003468: task.js
30
- points it at where the reader came from (?from=, then the
31
- referrer); /work is only the fallback it ships with. -->
27
+ <!-- task 1004477: the way back is the shell's top-bar Back link, set
28
+ before the task body loads. This page is deliberately not in the
29
+ nav, so <body data-back-fallback> gives a deep-linked visitor one
30
+ even with no ?from= and no referrer. -->
32
31
  <div class="rec-crumb">
33
- <a class="rec-back" id="task-back" href="/work">
34
- <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><span id="task-back-label">Back to tasks</span></a>
35
32
  <p class="rec-sub" id="task-sub">Loading task</p>
36
33
  </div>
37
34
  </div>
@@ -57,7 +54,7 @@
57
54
 
58
55
  <!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
59
56
  time so the chrome exists before the deferred page scripts execute. -->
60
- <script src="/builders/shell.js?v=2026-10-02-theme"></script>
57
+ <script src="/builders/shell.js?v=2026-10-02-back"></script>
61
58
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
62
59
  <script src="/builders/palette.js" defer></script>
63
60
  <!-- shared frontend toolkit (window.OTB) — loaded FIRST so task.js can pull from it. -->
@@ -182,57 +182,20 @@
182
182
  return m ? m[1] : null;
183
183
  }
184
184
 
185
- // task 1003468 — where the reader came from, so the record can send them back
186
- // there rather than always to the work board. Precedence: `?from=` (written
187
- // by the list that opened the record — a query param so it survives a reload
188
- // and a copied link) → a same-origin referrer → the work board. A value is
189
- // honoured only as a path on THIS host: one leading slash (a second is another
190
- // origin), no backslash (browsers read `/\host` as `//host`), no whitespace,
191
- // never the API and never this same record.
192
- const EXIT_MAX_LEN = 2048;
193
- function safeHallPath(v) {
194
- if (typeof v !== 'string' || !v || v.length > EXIT_MAX_LEN) return null;
195
- if (!/^\/(?!\/)/.test(v) || /[\\\s]/.test(v)) return null;
196
- if (/^(\/builders)?\/(api|lib)\//.test(v)) return null;
197
- if (v.split(/[?#]/)[0] === location.pathname) return null;
198
- return v;
199
- }
200
- function referrerPath() {
201
- let u;
202
- try { u = new URL(document.referrer); } catch (_) { return null; }
203
- if (u.origin !== location.origin) return null;
204
- return safeHallPath(u.pathname + u.search + u.hash);
205
- }
206
- // "goal 1000081", "task 12", "the work board", "sessions" — said the way the
207
- // sidebar says it, so the exit reads as a place and not as a URL.
208
- function exitLabel(href) {
209
- const rel = href.replace(/^\/builders(?=[/?#]|$)/, '');
210
- let m;
211
- if ((m = /#\/goal\/(\d+)/.exec(rel))) return `goal ${m[1]}`;
212
- if ((m = /^\/(task|idea)\/(\d+)/.exec(rel))) return `${m[1]} ${m[2]}`;
213
- const seg = (/^\/([a-z][a-z-]*)/.exec(rel) || [])[1];
214
- if (!seg) return 'home';
215
- if (seg === 'work') return 'the work board';
216
- return seg.replace(/-/g, ' ');
217
- }
218
- function exitTarget() {
219
- let from = null;
220
- try { from = new URLSearchParams(location.search).get('from'); } catch (_) { /* no query */ }
221
- const href = safeHallPath(from) || referrerPath() || `${hallBase()}/work`;
222
- return { href, label: exitLabel(href) };
223
- }
185
+ // task 1004477 — the way back is the SHELL's, not this page's. task 1003468
186
+ // worked out where the reader came from here (?from=, then the referrer, then
187
+ // the board); shell.js now decides that for every hall page, and this page
188
+ // only places the exit: a [data-shell-exit] link the shell fills with the same
189
+ // address and words as its top-bar Back link. The static href is what a
190
+ // reader without the shell (a failed script) still gets.
224
191
  function exitLinkHtml() {
225
- const { href, label } = exitTarget();
226
- return `<a class="td-btn" href="${escapeHtml(href)}">Back to ${escapeHtml(label)}</a>`;
192
+ return `<a class="td-btn" data-shell-exit href="${hallBase()}/work">Back to tasks</a>`;
227
193
  }
228
- // The crumb in the page head is set before the fetch, so the way back exists
229
- // in every state — loading, not found, failed — not just the happy one.
230
- function applyExit() {
231
- const { href, label } = exitTarget();
232
- const back = $('#task-back');
233
- if (back) back.href = href;
234
- const lbl = $('#task-back-label');
235
- if (lbl) lbl.textContent = `Back to ${label}`;
194
+ // Called after every render that places an exit, so the button and the top
195
+ // bar never disagree about where Back goes.
196
+ function fillExits(root) {
197
+ const shell = window.OTBShell;
198
+ if (shell && typeof shell.fillExits === 'function') shell.fillExits(root);
236
199
  }
237
200
 
238
201
  function rankText(rr) {
@@ -852,6 +815,7 @@
852
815
 
853
816
  const body = $('#task-body');
854
817
  if (body) body.innerHTML = parts.join('');
818
+ fillExits(body);
855
819
  announceShown(t);
856
820
 
857
821
  const copyBtn = $('#copy-prompt-btn');
@@ -1043,6 +1007,7 @@
1043
1007
  `;
1044
1008
  const sub = $('#task-sub');
1045
1009
  if (sub) sub.textContent = 'Not found';
1010
+ fillExits(body);
1046
1011
  }
1047
1012
 
1048
1013
  function renderError(id) {
@@ -1055,10 +1020,10 @@
1055
1020
  `;
1056
1021
  const sub = $('#task-sub');
1057
1022
  if (sub) sub.textContent = 'Could not load';
1023
+ fillExits(body);
1058
1024
  }
1059
1025
 
1060
1026
  async function load() {
1061
- applyExit();
1062
1027
  const id = taskIdFromPath();
1063
1028
  if (!id) { renderNotFound(''); return; }
1064
1029
  try {