@bongos/core 1.21.182 → 1.22.1

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 (135) hide show
  1. package/.bongos-core.json +157 -137
  2. package/docs/copy-inventory.md +165 -165
  3. package/docs/copy-registry.json +184 -184
  4. package/docs/module-api-changelog.md +10 -0
  5. package/docs/page-inventory.json +35 -26
  6. package/docs/page-readings.json +389 -388
  7. package/modules/hall-ui/public/approval-queue.js +3 -3
  8. package/modules/hall-ui/public/atlas.html +2 -1
  9. package/modules/hall-ui/public/atlas.js +1 -1
  10. package/modules/hall-ui/public/blockers.html +3 -4
  11. package/modules/hall-ui/public/board-room.html +2 -2
  12. package/modules/hall-ui/public/brand-holes.js +3 -3
  13. package/modules/hall-ui/public/city.html +2 -1
  14. package/modules/hall-ui/public/collab.html +2 -2
  15. package/modules/hall-ui/public/commands.html +2 -2
  16. package/modules/hall-ui/public/commands.js +1 -2
  17. package/modules/hall-ui/public/copy-desk.html +2 -2
  18. package/modules/hall-ui/public/core-move-notice.js +1 -5
  19. package/modules/hall-ui/public/craft-mode.js +1 -5
  20. package/modules/hall-ui/public/criterion-uat-ui.js +3 -3
  21. package/modules/hall-ui/public/deploy.html +2 -2
  22. package/modules/hall-ui/public/diagrams.html +2 -1
  23. package/modules/hall-ui/public/dom-utils.js +15 -6
  24. package/modules/hall-ui/public/drachmae.html +2 -1
  25. package/modules/hall-ui/public/fleet-costs.js +3 -2
  26. package/modules/hall-ui/public/fleet.html +2 -2
  27. package/modules/hall-ui/public/founding-mark.js +1 -6
  28. package/modules/hall-ui/public/gate.html +2 -2
  29. package/modules/hall-ui/public/goal-map.html +2 -1
  30. package/modules/hall-ui/public/goals.html +2 -2
  31. package/modules/hall-ui/public/government.html +2 -2
  32. package/modules/hall-ui/public/hall-ask.js +3 -3
  33. package/modules/hall-ui/public/hall-kit.js +3 -5
  34. package/modules/hall-ui/public/hall-visual.js +3 -5
  35. package/modules/hall-ui/public/idea.html +2 -2
  36. package/modules/hall-ui/public/ideas.html +2 -2
  37. package/modules/hall-ui/public/index.html +2 -2
  38. package/modules/hall-ui/public/lightbulb.js +1 -5
  39. package/modules/hall-ui/public/md-reader.js +1 -8
  40. package/modules/hall-ui/public/modules.html +2 -2
  41. package/modules/hall-ui/public/my-agents.html +2 -2
  42. package/modules/hall-ui/public/my-agents.js +3 -1
  43. package/modules/hall-ui/public/page-notes.js +1 -5
  44. package/modules/hall-ui/public/paintbrush.js +1 -5
  45. package/modules/hall-ui/public/palette.js +1 -5
  46. package/modules/hall-ui/public/primer.html +2 -1
  47. package/modules/hall-ui/public/primer.js +1 -1
  48. package/modules/hall-ui/public/profile-crafts.js +3 -5
  49. package/modules/hall-ui/public/profile.html +2 -3
  50. package/modules/hall-ui/public/project-settings.html +2 -2
  51. package/modules/hall-ui/public/ranks.html +2 -3
  52. package/modules/hall-ui/public/roadmap.html +2 -2
  53. package/modules/hall-ui/public/roster.html +3 -4
  54. package/modules/hall-ui/public/sessions.html +2 -3
  55. package/modules/hall-ui/public/settings.html +2 -3
  56. package/modules/hall-ui/public/shell.js +1 -5
  57. package/modules/hall-ui/public/sky-data.js +1 -1
  58. package/modules/hall-ui/public/street.html +2 -1
  59. package/modules/hall-ui/public/studio.html +2 -2
  60. package/modules/hall-ui/public/task-detail.js +1 -6
  61. package/modules/hall-ui/public/task-lib.js +3 -8
  62. package/modules/hall-ui/public/task.html +2 -3
  63. package/modules/hall-ui/public/thinking.html +2 -1
  64. package/modules/hall-ui/public/tweak-editor.html +2 -2
  65. package/modules/hall-ui/public/vote-control.js +1 -1
  66. package/modules/hall-ui/public/watch.html +2 -3
  67. package/modules/hall-ui/public/welcome-boot.js +3 -5
  68. package/modules/hall-ui/public/work.html +2 -3
  69. package/modules/lifecycle/ship-card.js +1 -4
  70. package/modules/npm-release/preview/proxy.js +2 -3
  71. package/modules/npm-release/public/task-widget.js +1 -1
  72. package/modules/platform-identity/routes/sso.js +1 -3
  73. package/modules/provisioning/routes/callback-page.js +2 -6
  74. package/modules/public-landing/public/projects.html +3 -5
  75. package/modules/raise/raise.js +1 -1
  76. package/package-lock.json +2 -2
  77. package/package.json +1 -1
  78. package/release-notes.json +20 -12
  79. package/scripts/bongos/escape-helper-guard.js +147 -0
  80. package/scripts/bongos/fitness.js +3 -2
  81. package/scripts/bongos/key-card.js +1 -4
  82. package/scripts/bongos/review-sheet.js +1 -3
  83. package/scripts/bongos/start.js +1 -1
  84. package/scripts/bongos/status.js +1 -1
  85. package/scripts/gds/escape-helper-guard.js +6 -0
  86. package/scripts/status-mirror-sync.js +2 -3
  87. package/src/bongos/module-scope-map.js +1 -0
  88. package/src/bongos/platform-visibility-gate.js +2 -3
  89. package/src/bongos/prelaunch-gate.js +2 -3
  90. package/src/bongos/routes/auth.js +1 -5
  91. package/src/bongos/serve-internal.js +2 -6
  92. package/src/html-escape.js +26 -0
  93. package/src/module-api.js +9 -1
  94. package/tests/blockers_kit_pager.mjs +2 -2
  95. package/tests/blockers_page_ui.mjs +2 -2
  96. package/tests/core_move_notice.mjs +3 -1
  97. package/tests/core_update_banner.mjs +2 -0
  98. package/tests/core_upgrade_owner_door.mjs +2 -0
  99. package/tests/craft_mode.mjs +2 -0
  100. package/tests/craft_mode_nav.mjs +3 -0
  101. package/tests/craft_review_mode.mjs +3 -2
  102. package/tests/criterion_ref_status.mjs +7 -2
  103. package/tests/curve_kind_overlay.mjs +2 -1
  104. package/tests/founding_mode.mjs +10 -2
  105. package/tests/github_install_nudge.mjs +4 -1
  106. package/tests/goal_map_page.mjs +2 -2
  107. package/tests/hall_approval_queue.mjs +2 -2
  108. package/tests/hall_city.mjs +1 -1
  109. package/tests/hall_fullscreen.mjs +2 -0
  110. package/tests/hall_gov_real_site.mjs +2 -0
  111. package/tests/hall_kit_charts.mjs +2 -1
  112. package/tests/hall_my_agents.mjs +3 -2
  113. package/tests/hall_nav.mjs +2 -0
  114. package/tests/hall_palette.mjs +2 -2
  115. package/tests/hall_reading_world.mjs +3 -1
  116. package/tests/hall_reference_world.mjs +2 -1
  117. package/tests/hall_signout.mjs +2 -0
  118. package/tests/hall_street.mjs +1 -1
  119. package/tests/hall_studio_world.mjs +3 -1
  120. package/tests/hall_tweak_editor.mjs +2 -1
  121. package/tests/helpers.mjs +17 -2
  122. package/tests/html_escape.mjs +149 -0
  123. package/tests/lightbulb.mjs +2 -0
  124. package/tests/module_api.mjs +1 -0
  125. package/tests/module_store_howto.mjs +2 -1
  126. package/tests/nav_permission_atoms.mjs +1 -1
  127. package/tests/npm_release_where.mjs +2 -1
  128. package/tests/page_notes_panel.mjs +4 -3
  129. package/tests/paintbrush.mjs +2 -1
  130. package/tests/profile_connections_ui.mjs +2 -1
  131. package/tests/profile_role_headline.mjs +2 -2
  132. package/tests/profile_role_tabs.mjs +2 -2
  133. package/tests/projects_hub_repo_private.mjs +4 -0
  134. package/tests/terms_acceptance.mjs +1 -0
  135. package/tests/welcome_boot_browser.mjs +1 -1
@@ -32,9 +32,9 @@
32
32
  // chip (brand-holes.js, task 1004381). Escaped text when it is not loaded.
33
33
  const H = window.OTBBrandHoles || null;
34
34
 
35
- function esc(s) {
36
- return String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
37
- }
35
+ // The shared escaper, window.OTB (dom-utils.js, task 1004876). Browser-only: this
36
+ // file reads window at the top of its IIFE, so it never loads under plain Node.
37
+ function esc(s) { return window.OTB.escapeHtml(s); }
38
38
 
39
39
  function errWords(data, fallback) {
40
40
  const e = data && data.error;
@@ -89,9 +89,10 @@
89
89
 
90
90
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
91
91
 
92
+ <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
92
93
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
93
94
  <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
94
- <script src="/builders/shell.js?v=2026-10-25-autobongos"></script>
95
+ <script src="/builders/shell.js?v=2026-10-25-escape"></script>
95
96
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
96
97
  <script src="/builders/palette.js" defer></script>
97
98
  <script src="/builders/atlas.js?v=2026-09-02-index" defer></script>
@@ -290,7 +290,7 @@
290
290
  if (conns.length) html += '<div class="ab-prow">Connects to</div><div class="ab-chips">' + conns.map(function (cc) { return '<button type="button" class="fact-chip ab-jump" data-node="' + esc(cc.id) + '">' + esc(cc.label) + '</button>'; }).join('') + '</div>';
291
291
  return html;
292
292
  }
293
- function esc(s) { return String(s).replace(/[&<>"]/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]; }); }
293
+ function esc(s) { return window.OTB.escapeHtml(s); }
294
294
 
295
295
  function openPopup(n, g) {
296
296
  selected = n.id; lightNode(n.id);
@@ -51,14 +51,13 @@
51
51
 
52
52
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
53
53
  chrome exists before the page script runs. -->
54
+ <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
54
55
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
55
56
  <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
56
- <script src="/builders/shell.js?v=2026-10-25-autobongos"></script>
57
+ <script src="/builders/shell.js?v=2026-10-25-escape"></script>
57
58
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
58
59
  <script src="/builders/palette.js" defer></script>
59
- <!-- shared frontend toolkit (window.OTB — rank labels, number formats) + the
60
- branding pack's currency label, loaded BEFORE blockers.js. -->
61
- <script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
60
+ <!-- the branding pack's currency label, loaded BEFORE blockers.js. -->
62
61
  <script src="/builders/brand-apply.js"></script>
63
62
  <script src="/builders/hall-kit.js"></script>
64
63
  <!-- task-detail.js still owns the #/blocker/N deep-link overlay every row's
@@ -58,12 +58,12 @@
58
58
 
59
59
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
60
60
 
61
+ <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
61
62
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
62
63
  <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
63
- <script src="/builders/shell.js?v=2026-10-25-autobongos"></script>
64
+ <script src="/builders/shell.js?v=2026-10-25-escape"></script>
64
65
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
65
66
  <script src="/builders/palette.js" defer></script>
66
- <script src="/builders/dom-utils.js?v=2026-06-16-board"></script>
67
67
  <script src="/builders/hall-kit.js"></script>
68
68
  <!-- task 1003761: the vocabulary this page and /government both speak. Must load
69
69
  BEFORE board-room.js, which destructures window.OTBBoardLib at IIFE entry. -->
@@ -26,9 +26,9 @@
26
26
 
27
27
  const HOLE_RE = /\{\{([A-Za-z][A-Za-z0-9_]*)\}\}/g;
28
28
 
29
- function esc(s) {
30
- return String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
31
- }
29
+ // The shared escaper: window.OTB in the page; under Node (this file is also require()d as a
30
+ // CommonJS lib — see its module.exports and its tests) dom-utils.js's own export (task 1004876).
31
+ function esc(s) { return (typeof window !== 'undefined' ? window.OTB : require('./dom-utils.js')).escapeHtml(s); }
32
32
 
33
33
  function splitHoles(text) {
34
34
  const s = String(text == null ? '' : text);
@@ -105,9 +105,10 @@
105
105
  </div>
106
106
  </div>
107
107
 
108
+ <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
108
109
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
109
110
  <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
110
- <script src="/builders/shell.js?v=2026-10-25-autobongos"></script>
111
+ <script src="/builders/shell.js?v=2026-10-25-escape"></script>
111
112
  <script src="/builders/palette.js" defer></script>
112
113
  <script src="/builders/gov-living.js?v=2026-10-09-positions1"></script>
113
114
  <script src="/builders/gov-feeds.js?v=2026-10-09-living3"></script>
@@ -148,11 +148,11 @@
148
148
 
149
149
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
150
150
 
151
+ <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
151
152
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
152
153
  <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
153
- <script src="/builders/shell.js?v=2026-10-25-autobongos"></script>
154
+ <script src="/builders/shell.js?v=2026-10-25-escape"></script>
154
155
  <script src="/builders/palette.js" defer></script>
155
- <script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
156
156
  <script src="/builders/collab-lib.js?v=2026-09-24-archive"></script>
157
157
  <script src="/builders/collab.js?v=2026-09-24-archive"></script>
158
158
  </body>
@@ -51,12 +51,12 @@
51
51
 
52
52
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
53
53
 
54
+ <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
54
55
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
55
56
  <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
56
- <script src="/builders/shell.js?v=2026-10-25-autobongos"></script>
57
+ <script src="/builders/shell.js?v=2026-10-25-escape"></script>
57
58
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
58
59
  <script src="/builders/palette.js" defer></script>
59
- <script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
60
60
  <script src="/builders/hall-kit.js"></script>
61
61
  <script src="/builders/commands-lib.js"></script>
62
62
  <script src="/builders/commands.js"></script>
@@ -14,8 +14,7 @@
14
14
  'use strict';
15
15
 
16
16
  const lib = window.OTBCommandsLib;
17
- const esc = (window.OTBKit && window.OTBKit.esc) || ((s) => String(s == null ? '' : s)
18
- .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;'));
17
+ const esc = (s) => window.OTB.escapeHtml(s);
19
18
 
20
19
  async function getJson(url) {
21
20
  const r = await fetch(url, { credentials: 'same-origin' });
@@ -177,11 +177,11 @@
177
177
 
178
178
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
179
179
 
180
+ <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
180
181
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
181
182
  <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
182
- <script src="/builders/shell.js?v=2026-10-25-autobongos"></script>
183
+ <script src="/builders/shell.js?v=2026-10-25-escape"></script>
183
184
  <script src="/builders/palette.js" defer></script>
184
- <script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
185
185
  <script src="/builders/hall-kit.js"></script>
186
186
  <script src="/builders/copy-desk.js?v=2026-09-01-redesign"></script>
187
187
  </body>
@@ -18,11 +18,7 @@
18
18
  'use strict';
19
19
 
20
20
  var SEEN_KEY = 'bongos.coreMove.seen';
21
- function esc(s) {
22
- return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
23
- return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
24
- });
25
- }
21
+ function esc(s) { return window.OTB.escapeHtml(s); }
26
22
 
27
23
  /** Draw the notice under the top bar. `opts.api` is shell.js's client; `opts.href` Settings. */
28
24
  function render(moved, opts) {
@@ -509,11 +509,7 @@
509
509
  // localStorage: the mode to keep when "Ask me again next session" is unticked.
510
510
  var KEEP_KEY = 'hall-craft-keep';
511
511
 
512
- function esc(s) {
513
- return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
514
- return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
515
- });
516
- }
512
+ function esc(s) { return window.OTB.escapeHtml(s); }
517
513
  function icon(id, cls) {
518
514
  return '<svg class="' + cls + '" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' + ICONS[id] + '</svg>';
519
515
  }
@@ -43,9 +43,9 @@
43
43
  const MAX_BYTES = 100 * 1024 * 1024;
44
44
  const TYPES = ['video/mp4', 'video/webm'];
45
45
 
46
- function esc(s) {
47
- return String(s == null ? '' : s).replace(/[&<>"']/g, (ch) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[ch]));
48
- }
46
+ // The shared escaper: window.OTB in the page; under Node (this file is also require()d as a
47
+ // CommonJS lib — see its module.exports and its tests) dom-utils.js's own export (task 1004876).
48
+ function esc(s) { return (typeof window !== 'undefined' ? window.OTB : require('./dom-utils.js')).escapeHtml(s); }
49
49
 
50
50
  // The state a row reads as. A server older than the UAT rule sends no
51
51
  // uat_state; then satisfied/open is the whole truth and no UAT control shows.
@@ -76,11 +76,11 @@
76
76
 
77
77
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
78
78
 
79
+ <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
79
80
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
80
81
  <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
81
- <script src="/builders/shell.js?v=2026-10-25-autobongos"></script>
82
+ <script src="/builders/shell.js?v=2026-10-25-escape"></script>
82
83
  <script src="/builders/palette.js" defer></script>
83
- <script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
84
84
  <script src="/builders/hall-kit.js"></script>
85
85
  <!-- the shared ask card the move is drawn with (task 1004829) -->
86
86
  <script src="/builders/hall-ask.js"></script>
@@ -94,9 +94,10 @@
94
94
 
95
95
  <!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
96
96
  time so the chrome exists before the deferred page scripts execute. -->
97
+ <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
97
98
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
98
99
  <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
99
- <script src="/builders/shell.js?v=2026-10-25-autobongos"></script>
100
+ <script src="/builders/shell.js?v=2026-10-25-escape"></script>
100
101
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
101
102
  <script src="/builders/palette.js" defer></script>
102
103
  <script src="/builders/diagram-viewer.js?v=2026-06-14-diagviewer-1" defer></script>
@@ -1,7 +1,12 @@
1
1
  // public-builders/dom-utils.js — shared frontend toolkit for the builders' hall
2
2
  // (GDS-V4 C4 / task 1088). Loaded as the FIRST hall <script> on each page so the
3
3
  // per-page IIFEs can pull these from one place instead of redefining them.
4
- // Attaches a single global, `window.OTB`.
4
+ // Attaches a single global, `window.OTB` (and is its module.exports under Node).
5
+ //
6
+ // escapeHtml here is THE browser HTML escaper (task 1004876): every hall script
7
+ // — and the landing's projects page — escapes user text through it, and the
8
+ // fitness check scripts/bongos/escape-helper-guard.js fails on a third copy and
9
+ // on any hall page that does not load this file first, without defer.
5
10
  //
6
11
  // NOT a consumer: modules/status-ui/public/ (the off-box status mirror) stays
7
12
  // self-contained by design — do not wire this there. Network helpers
@@ -19,11 +24,11 @@
19
24
  (() => {
20
25
  'use strict';
21
26
 
22
- // HTML-escape for text interpolated into innerHTML. (8 byte-equivalent copies.)
27
+ // HTML-escape for text interpolated into innerHTML: & < > " ' ; null/undefined → ''.
28
+ // The server twin is src/html-escape.js; tests/html_escape.mjs holds them equal.
29
+ const ESCAPES = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' };
23
30
  function escapeHtml(s) {
24
- return String(s ?? '').replace(/[&<>"']/g, (c) => ({
25
- '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;',
26
- }[c]));
31
+ return String(s ?? '').replace(/[&<>"']/g, (c) => ESCAPES[c]);
27
32
  }
28
33
 
29
34
  // querySelector shorthand. (7 copies; pair.js's single-arg form is a subset.)
@@ -460,7 +465,7 @@
460
465
  } catch (err) { return null; }
461
466
  }
462
467
 
463
- window.OTB = {
468
+ const OTB = {
464
469
  escapeHtml, $, fmtNum, num, fmtInt, fmtCompact, fmtDate, rankLabel, toast, moduleOn, disciplines,
465
470
  // task 1003936 — ONE word for the role axis, all six surfaces
466
471
  roleAxisLabel, roleLabel, roleWord, roleOptions, roleIsUnset, ROLE_UNSET,
@@ -472,4 +477,8 @@
472
477
  categoryChip, // task 1003276 — shared by the Goals page, the Tasks board and the task page
473
478
  hubOrigin, // task 1003485 — the hub account page's origin, for Settings and the profile nudge
474
479
  };
480
+ if (typeof window !== 'undefined') window.OTB = OTB;
481
+ // Node: the hall libs that also load under require() (hall-kit.js, task-lib.js, …)
482
+ // escape through this same function there (task 1004876).
483
+ if (typeof module !== 'undefined' && module.exports) module.exports = OTB;
475
484
  })();
@@ -94,9 +94,10 @@
94
94
 
95
95
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
96
96
 
97
+ <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
97
98
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
98
99
  <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
99
- <script src="/builders/shell.js?v=2026-10-25-autobongos"></script>
100
+ <script src="/builders/shell.js?v=2026-10-25-escape"></script>
100
101
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
101
102
  <script src="/builders/palette.js" defer></script>
102
103
  <script src="/builders/md-reader.js?v=2026-09-02-mdreader-2"></script>
@@ -20,8 +20,9 @@
20
20
  'co-tenant': 'Shared server (retired)',
21
21
  };
22
22
 
23
- const ESC = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' };
24
- const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ESC[c]);
23
+ // The shared escaper: window.OTB in the page; under Node (this file is also require()d as a
24
+ // CommonJS lib — see its module.exports and its tests) dom-utils.js's own export (task 1004876).
25
+ const esc = (s) => (typeof window !== 'undefined' ? window.OTB : require('./dom-utils.js')).escapeHtml(s);
25
26
  const isPriced = (v) => typeof v === 'number' && Number.isFinite(v);
26
27
  const quiet = (text) => `<span class="ledger__est">${text}</span>`;
27
28
 
@@ -55,11 +55,11 @@
55
55
 
56
56
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
57
57
 
58
+ <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
58
59
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
59
60
  <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
60
- <script src="/builders/shell.js?v=2026-10-25-autobongos"></script>
61
+ <script src="/builders/shell.js?v=2026-10-25-escape"></script>
61
62
  <script src="/builders/palette.js" defer></script>
62
- <script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
63
63
  <script src="/builders/hall-kit.js"></script>
64
64
  <script src="/builders/fleet-costs.js"></script>
65
65
  <script src="/builders/fleet.js"></script>
@@ -18,12 +18,7 @@
18
18
  charter: 'charter', legalize: 'legalize', legislation: 'legislation',
19
19
  };
20
20
 
21
- function esc(s) {
22
- if (window.OTB && typeof window.OTB.escapeHtml === 'function') return window.OTB.escapeHtml(s);
23
- return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
24
- return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
25
- });
26
- }
21
+ function esc(s) { return window.OTB.escapeHtml(s); }
27
22
 
28
23
  // One request per page, shared by every surface on it. Any failure (signed out, the
29
24
  // onboarding module off, a network blip) is an empty list: no marks, today's page.
@@ -62,12 +62,12 @@
62
62
 
63
63
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
64
64
 
65
+ <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
65
66
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
66
67
  <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
67
- <script src="/builders/shell.js?v=2026-10-25-autobongos"></script>
68
+ <script src="/builders/shell.js?v=2026-10-25-escape"></script>
68
69
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
69
70
  <script src="/builders/palette.js" defer></script>
70
- <script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
71
71
  <script src="/builders/hall-kit.js"></script>
72
72
  <script src="/builders/gate.js?v=2026-10-25-autobongos"></script>
73
73
  </body>
@@ -137,9 +137,10 @@
137
137
  </div>
138
138
  </div>
139
139
 
140
+ <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
140
141
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
141
142
  <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
142
- <script src="/builders/shell.js?v=2026-10-25-autobongos"></script>
143
+ <script src="/builders/shell.js?v=2026-10-25-escape"></script>
143
144
  <script src="/builders/palette.js" defer></script>
144
145
 
145
146
  <script src="/builders/idea-objects.js?v=2026-09-27-split"></script>
@@ -234,12 +234,12 @@
234
234
 
235
235
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
236
236
 
237
+ <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
237
238
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
238
239
  <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
239
- <script src="/builders/shell.js?v=2026-10-25-autobongos"></script>
240
+ <script src="/builders/shell.js?v=2026-10-25-escape"></script>
240
241
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
241
242
  <script src="/builders/palette.js" defer></script>
242
- <script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
243
243
  <!-- task 1004727: the ONE karma vote control every task/idea list renders -->
244
244
  <script src="/builders/vote-control.js"></script>
245
245
  <!-- shared component kit (window.OTBKit) — pager, filter bar, tabs, empty/skeleton (task 2166). -->
@@ -132,12 +132,12 @@
132
132
 
133
133
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
134
134
 
135
+ <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
135
136
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
136
137
  <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
137
- <script src="/builders/shell.js?v=2026-10-25-autobongos"></script>
138
+ <script src="/builders/shell.js?v=2026-10-25-escape"></script>
138
139
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
139
140
  <script src="/builders/palette.js" defer></script>
140
- <script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
141
141
  <script src="/builders/hall-kit.js"></script>
142
142
  <!-- task 1003761: the vocabulary this page and /board-room both speak. Must load
143
143
  BEFORE government.js, which destructures window.OTBBoardLib at IIFE entry. -->
@@ -16,9 +16,9 @@
16
16
  // Every string is escaped here; `saidHtml` is the one pre-built slot, for a
17
17
  // caller whose facts are already escaped markup (blockerFacts).
18
18
  (function () {
19
- const esc = (s) => String(s == null ? '' : s)
20
- .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
21
- .replace(/"/g, '&quot;').replace(/'/g, '&#39;');
19
+ // The shared escaper: window.OTB in the page; under Node (this file is also require()d as a
20
+ // CommonJS lib — see its module.exports and its tests) dom-utils.js's own export (task 1004876).
21
+ const esc = (s) => (typeof window !== 'undefined' ? window.OTB : require('./dom-utils.js')).escapeHtml(s);
22
22
  const attrsOf = (a) => Object.keys(a || {})
23
23
  .filter((k) => a[k] != null && a[k] !== false)
24
24
  .map((k) => ` ${k}="${esc(a[k])}"`).join('');
@@ -27,11 +27,9 @@
27
27
  'use strict';
28
28
 
29
29
  // HTML-escape for text interpolated into innerHTML.
30
- function esc(s) {
31
- return String(s ?? '').replace(/[&<>"']/g, (c) => ({
32
- '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;',
33
- }[c]));
34
- }
30
+ // The shared escaper: window.OTB in the page; under Node (this file is also require()d as a
31
+ // CommonJS lib — see its module.exports and its tests) dom-utils.js's own export (task 1004876).
32
+ function esc(s) { return (typeof window !== 'undefined' ? window.OTB : require('./dom-utils.js')).escapeHtml(s); }
35
33
  const $id = (id) => document.getElementById(id);
36
34
 
37
35
  // ---- pager ----------------------------------------------------------------
@@ -27,11 +27,9 @@
27
27
  'use strict';
28
28
 
29
29
  // HTML-escape for text interpolated into innerHTML.
30
- function esc(s) {
31
- return String(s ?? '').replace(/[&<>"']/g, (c) => ({
32
- '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;',
33
- }[c]));
34
- }
30
+ // The shared escaper: window.OTB in the page; under Node (this file is also require()d as a
31
+ // CommonJS lib — see its module.exports and its tests) dom-utils.js's own export (task 1004876).
32
+ function esc(s) { return (typeof window !== 'undefined' ? window.OTB : require('./dom-utils.js')).escapeHtml(s); }
35
33
 
36
34
  // Same-origin, SERVER-MINTED paths only. The URL arrives from our own API, but
37
35
  // it lands in an href and a src, so it is matched literally against the shape
@@ -50,11 +50,11 @@
50
50
 
51
51
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
52
52
 
53
+ <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
53
54
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
54
55
  <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
55
- <script src="/builders/shell.js?v=2026-10-25-autobongos"></script>
56
+ <script src="/builders/shell.js?v=2026-10-25-escape"></script>
56
57
  <script src="/builders/palette.js" defer></script>
57
- <script src="/builders/dom-utils.js?v=2026-06-23-contrib" defer></script>
58
58
  <!-- task-detail.js still owns the #/idea/N and #/blocker/N overlay, so a ref
59
59
  written inside an idea's own prose keeps resolving from this page. -->
60
60
  <script src="/builders/task-detail.js?v=2026-07-03-taskdetail2" defer></script>
@@ -225,11 +225,11 @@
225
225
 
226
226
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
227
227
 
228
+ <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
228
229
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
229
230
  <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
230
- <script src="/builders/shell.js?v=2026-10-25-autobongos"></script>
231
+ <script src="/builders/shell.js?v=2026-10-25-escape"></script>
231
232
  <script src="/builders/palette.js" defer></script>
232
- <script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
233
233
  <!-- task 1004727: the ONE karma vote control every task/idea list renders -->
234
234
  <script src="/builders/vote-control.js"></script>
235
235
  <!-- task 1003069 (R08) — the quick-filing form's routed result links a task
@@ -106,7 +106,7 @@
106
106
  / ADR 0069 — no client lib to load (EventSource is built into the browser). -->
107
107
  <!-- shared frontend toolkit (window.OTB) — loaded FIRST so every consuming
108
108
  IIFE below can pull escapeHtml/$/fmtNum/... from one place. -->
109
- <script src="/builders/dom-utils.js?v=2026-06-14-domutils" defer></script>
109
+ <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
110
110
  <script src="/builders/task-detail.js?v=2026-07-03-taskdetail" defer></script>
111
111
  <!-- R56 (task 1195): apply the instance currency label from window.__BRANDING__
112
112
  to [data-currency-label]/[data-currency-tmpl] markup + expose OTBBranding.
@@ -119,7 +119,7 @@
119
119
  <script src="/builders/welcome-boot.js?v=2026-10-23-city1" defer></script>
120
120
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab" defer></script>
121
121
  <script src="/builders/craft-mode.js?v=2026-10-09-living3" defer></script>
122
- <script src="/builders/shell.js?v=2026-10-25-autobongos" defer></script>
122
+ <script src="/builders/shell.js?v=2026-10-25-escape" defer></script>
123
123
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
124
124
  <script src="/builders/palette.js" defer></script>
125
125
  <!-- task 2166: the shared component kit (window.OTBKit) — pager/ledger/rail.
@@ -20,11 +20,7 @@
20
20
 
21
21
  var sheet = null, returnFocus = null, saveTimer = null, busy = false;
22
22
 
23
- function esc(s) {
24
- return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
25
- return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
26
- });
27
- }
23
+ function esc(s) { return window.OTB.escapeHtml(s); }
28
24
  function plural(n, one, many) { return n + ' ' + (n === 1 ? one : many); }
29
25
  function day(iso) {
30
26
  var d = iso ? new Date(iso) : null;
@@ -21,14 +21,7 @@
21
21
  (function () {
22
22
  'use strict';
23
23
 
24
- function escapeHtml(s) {
25
- return String(s)
26
- .replace(/&/g, '&amp;')
27
- .replace(/</g, '&lt;')
28
- .replace(/>/g, '&gt;')
29
- .replace(/"/g, '&quot;')
30
- .replace(/'/g, '&#39;');
31
- }
24
+ function escapeHtml(s) { return window.OTB.escapeHtml(s); }
32
25
 
33
26
  // Stable slug for heading anchors — lowercase, strip punctuation, dash-join.
34
27
  // Matches the primer's slugify so in-doc anchor links behave identically.
@@ -125,12 +125,12 @@
125
125
 
126
126
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
127
127
 
128
+ <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
128
129
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
129
130
  <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
130
- <script src="/builders/shell.js?v=2026-10-25-autobongos"></script>
131
+ <script src="/builders/shell.js?v=2026-10-25-escape"></script>
131
132
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
132
133
  <script src="/builders/palette.js" defer></script>
133
- <script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
134
134
  <script src="/builders/brand-apply.js"></script>
135
135
  <script src="/builders/hall-kit.js"></script>
136
136
  <script src="/builders/md-reader.js"></script>
@@ -59,12 +59,12 @@
59
59
 
60
60
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
61
61
 
62
+ <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
62
63
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
63
64
  <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
64
- <script src="/builders/shell.js?v=2026-10-25-autobongos"></script>
65
+ <script src="/builders/shell.js?v=2026-10-25-escape"></script>
65
66
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
66
67
  <script src="/builders/palette.js" defer></script>
67
- <script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
68
68
  <script src="/builders/gov-living.js?v=2026-10-10-real1"></script>
69
69
  <script src="/builders/gov-positions.js?v=2026-10-09-positions1"></script>
70
70
  <script src="/builders/my-agents.js?v=2026-10-17-tidy"></script>
@@ -310,7 +310,9 @@
310
310
  const API = '/api/bongos';
311
311
  const api = root.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false });
312
312
  const $ = (id) => document.getElementById(id);
313
- const esc = (s) => str(s).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
313
+ // The shared escaper: window.OTB in the page; under Node (this file is also require()d as a
314
+ // CommonJS lib — see its module.exports and its tests) dom-utils.js's own export (task 1004876).
315
+ const esc = (s) => (typeof window !== 'undefined' ? window.OTB : require('./dom-utils.js')).escapeHtml(s);
314
316
  const toast = (msg) => { if (root.OTB && root.OTB.toast) root.OTB.toast(msg); };
315
317
  const brand = root.__BRANDING__ || {};
316
318
  const PROJECT = (brand.identity && (brand.identity.productName || brand.identity.worldName)) || 'The project';
@@ -21,11 +21,7 @@
21
21
 
22
22
  var sheet = null, returnFocus = null, state = null, saveTimer = null, pollTimer = null, docked = false;
23
23
 
24
- function esc(s) {
25
- return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
26
- return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
27
- });
28
- }
24
+ function esc(s) { return window.OTB.escapeHtml(s); }
29
25
  // Where the panel is open. The hall needs no setting; the public site's
30
26
  // Artist review button (task 1004499, artist-review.js) calls
31
27
  // configure({ surface: 'site', hall: <the hall's origin> }) before it opens.