@bongos/core 1.24.51 → 1.24.52

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 (81) hide show
  1. package/.bongos-core.json +95 -85
  2. package/docs/copy-inventory.md +199 -215
  3. package/docs/copy-registry.json +230 -395
  4. package/docs/file-map.md +1 -1
  5. package/docs/module-api-changelog.md +2 -0
  6. package/docs/page-inventory.json +33 -0
  7. package/docs/page-readings.json +450 -463
  8. package/modules/hall-ui/public/ai-studio.html +1 -0
  9. package/modules/hall-ui/public/approval-queue.css +3 -11
  10. package/modules/hall-ui/public/approval-queue.js +13 -20
  11. package/modules/hall-ui/public/atlas.html +1 -0
  12. package/modules/hall-ui/public/blockers.html +1 -0
  13. package/modules/hall-ui/public/board-room.html +1 -0
  14. package/modules/hall-ui/public/bulk-toolbar.js +5 -1
  15. package/modules/hall-ui/public/city.html +1 -0
  16. package/modules/hall-ui/public/collab.html +1 -0
  17. package/modules/hall-ui/public/commands.html +1 -0
  18. package/modules/hall-ui/public/controls.css +100 -0
  19. package/modules/hall-ui/public/copy-desk.css +7 -11
  20. package/modules/hall-ui/public/copy-desk.html +17 -16
  21. package/modules/hall-ui/public/copy-desk.js +13 -9
  22. package/modules/hall-ui/public/deploy.html +1 -0
  23. package/modules/hall-ui/public/goal-inbox.js +32 -17
  24. package/modules/hall-ui/public/goal-map.html +1 -0
  25. package/modules/hall-ui/public/goals-page.js +7 -1
  26. package/modules/hall-ui/public/goals.html +1 -0
  27. package/modules/hall-ui/public/government.html +1 -0
  28. package/modules/hall-ui/public/government.js +10 -2
  29. package/modules/hall-ui/public/hall-dialog.js +255 -0
  30. package/modules/hall-ui/public/hall-kit.js +9 -2
  31. package/modules/hall-ui/public/idea.html +1 -0
  32. package/modules/hall-ui/public/ideas.html +1 -0
  33. package/modules/hall-ui/public/index.html +1 -0
  34. package/modules/hall-ui/public/market-watch-item.html +2 -1
  35. package/modules/hall-ui/public/market-watch-lib.js +16 -9
  36. package/modules/hall-ui/public/market-watch-rules.html +1 -0
  37. package/modules/hall-ui/public/market-watch.css +2 -3
  38. package/modules/hall-ui/public/market-watch.html +2 -1
  39. package/modules/hall-ui/public/modules.html +1 -0
  40. package/modules/hall-ui/public/money.html +1 -0
  41. package/modules/hall-ui/public/my-agents.html +1 -0
  42. package/modules/hall-ui/public/oversight.css +3 -33
  43. package/modules/hall-ui/public/primer.html +1 -0
  44. package/modules/hall-ui/public/profile.html +1 -0
  45. package/modules/hall-ui/public/project-settings.html +1 -0
  46. package/modules/hall-ui/public/roster.html +1 -0
  47. package/modules/hall-ui/public/settings-ai-keys.js +5 -1
  48. package/modules/hall-ui/public/settings-passkeys.js +5 -1
  49. package/modules/hall-ui/public/settings-sessions.js +10 -2
  50. package/modules/hall-ui/public/settings.html +1 -0
  51. package/modules/hall-ui/public/settings.js +5 -1
  52. package/modules/hall-ui/public/street.html +1 -0
  53. package/modules/hall-ui/public/studio.css +6 -32
  54. package/modules/hall-ui/public/studio.html +4 -3
  55. package/modules/hall-ui/public/studio.js +40 -35
  56. package/modules/hall-ui/public/style.css +2 -8
  57. package/modules/hall-ui/public/task.html +1 -0
  58. package/modules/hall-ui/public/task.js +26 -12
  59. package/modules/hall-ui/public/thinking.html +1 -0
  60. package/modules/hall-ui/public/tweak-editor.html +1 -0
  61. package/modules/hall-ui/public/watch.html +1 -0
  62. package/modules/hall-ui/public/work.html +1 -0
  63. package/modules/hall-ui/public/work.js +7 -1
  64. package/package-lock.json +2 -2
  65. package/package.json +1 -1
  66. package/release-notes.json +6 -0
  67. package/src/module-api.js +1 -1
  68. package/tests/bulk_toolbar.mjs +1 -1
  69. package/tests/hall_approval_queue.mjs +8 -2
  70. package/tests/hall_audit.mjs +1 -1
  71. package/tests/hall_city.mjs +1 -1
  72. package/tests/hall_collab_world.mjs +9 -6
  73. package/tests/hall_dialog.mjs +401 -0
  74. package/tests/hall_error_envelope.mjs +1 -1
  75. package/tests/hall_oversight_world.mjs +9 -7
  76. package/tests/hall_record_world.mjs +1 -1
  77. package/tests/hall_street.mjs +1 -1
  78. package/tests/hall_studio_home.mjs +1 -1
  79. package/tests/home_attention_cards.mjs +2 -0
  80. package/tests/kind_axis_unset.mjs +1 -1
  81. package/tests/market_watch_pages.mjs +3 -1
@@ -395,6 +395,7 @@
395
395
  <script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
396
396
  <script src="/builders/craft-mode.js?v=2026-11-05-goals"></script>
397
397
  <script src="/builders/shell.js?v=2026-11-07-brand-mark"></script>
398
+ <script src="/builders/hall-dialog.js"></script>
398
399
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
399
400
  <script src="/builders/palette.js" defer></script>
400
401
  <!-- shared component kit (window.OTBKit) — section rail (task 2166). -->
@@ -814,7 +814,11 @@
814
814
 
815
815
  async function removeArtKey() {
816
816
  const status = $('#artkey-status');
817
- if (!window.confirm('Remove your image-generation key? Art generation will stop until you add one again.')) return;
817
+ if (!(await window.OTBDialog.confirm({
818
+ title: 'Remove your image-generation key?',
819
+ body: 'Art generation will stop until you add one again.',
820
+ confirmLabel: 'Remove key', danger: true,
821
+ }))) return;
818
822
  if (status) status.textContent = 'Removing…';
819
823
  try {
820
824
  const res = await api.request('DELETE', `${API}/me/art-key/own`);
@@ -232,6 +232,7 @@
232
232
  <script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
233
233
  <script src="/builders/craft-mode.js?v=2026-11-05-goals"></script>
234
234
  <script src="/builders/shell.js?v=2026-11-07-brand-mark"></script>
235
+ <script src="/builders/hall-dialog.js"></script>
235
236
  <script src="/builders/palette.js" defer></script>
236
237
  <script src="/builders/gov-living.js?v=2026-10-09-positions1"></script>
237
238
  <script src="/builders/gov-feeds.js?v=2026-10-09-living3"></script>
@@ -73,7 +73,6 @@ body[data-page="studio"] {
73
73
  --st-panel: rgba(255, 255, 255, .075);
74
74
  --st-panel-edge: rgba(255, 255, 255, .18);
75
75
  --st-panel-rim: rgba(255, 255, 255, .22);
76
- --st-panel-drop: rgba(0, 0, 0, .55);
77
76
  --st-close: rgba(255, 255, 255, .08);
78
77
  --st-chip-edge: rgba(255, 255, 255, .16);
79
78
  --st-track: rgba(255, 255, 255, .1);
@@ -126,7 +125,6 @@ body[data-page="studio"][data-sky="day"] {
126
125
  --st-panel: rgba(255, 255, 255, .75);
127
126
  --st-panel-edge: rgba(60, 40, 20, .16);
128
127
  --st-panel-rim: rgba(255, 255, 255, .9);
129
- --st-panel-drop: rgba(60, 40, 20, .18);
130
128
  --st-close: rgba(60, 40, 20, .08);
131
129
  --st-chip-edge: rgba(60, 40, 20, .2);
132
130
  --st-track: rgba(60, 40, 20, .12);
@@ -150,7 +148,6 @@ body[data-page="studio"][data-sky="day"] .aq-overlay {
150
148
  --aq-glass: rgba(255, 255, 255, .75);
151
149
  --aq-glass-edge: rgba(60, 40, 20, .16);
152
150
  --aq-glass-rim: rgba(255, 255, 255, .9);
153
- --aq-drop: rgba(60, 40, 20, .18);
154
151
  --aq-ink: #1b1a1d;
155
152
  --aq-ink-soft: #4d4c52;
156
153
  --aq-ink-faint: #5a595f;
@@ -597,20 +594,13 @@ html.st-reading .st-glance { display: none; }
597
594
  page), and .88 on a phone.
598
595
  --------------------------------------------------------------------------- */
599
596
  .st-overlay {
600
- position: fixed;
601
- inset: 0;
602
- z-index: var(--z-modal, 160);
603
- display: flex;
604
- align-items: center;
605
- justify-content: center;
606
- padding: 24px;
607
- box-sizing: border-box;
597
+ /* the layer, the placement and the gutter are the shared .hall-scrim's
598
+ (controls.css, task 1005085); only the sky's tint is its own */
608
599
  background: var(--st-scrim);
609
600
  color: var(--st-ink);
610
601
  font-family: var(--st-face);
611
602
  color-scheme: dark;
612
603
  }
613
- .st-overlay[hidden] { display: none; }
614
604
  html[data-theme="dark"] body[data-page="studio"] { --st-scrim: rgba(4, 5, 9, .42); }
615
605
  @media (prefers-color-scheme: dark) {
616
606
  html:not([data-theme]) body[data-page="studio"] { --st-scrim: rgba(4, 5, 9, .42); }
@@ -627,7 +617,7 @@ html.st-open { overflow: hidden; }
627
617
  border: 1px solid var(--st-panel-edge);
628
618
  backdrop-filter: blur(30px) saturate(160%);
629
619
  -webkit-backdrop-filter: blur(30px) saturate(160%);
630
- box-shadow: inset 0 1px 0 var(--st-panel-rim), 0 40px 90px var(--st-panel-drop);
620
+ box-shadow: inset 0 1px 0 var(--st-panel-rim), var(--ds-shadow-pop);
631
621
  display: flex;
632
622
  flex-direction: column;
633
623
  gap: 22px;
@@ -797,28 +787,12 @@ html.st-open { overflow: hidden; }
797
787
  tokens, in a dialog that opens from the quiet door above.
798
788
  =========================================================================== */
799
789
  .st-reviews {
800
- position: fixed;
801
- inset: 0;
802
- z-index: var(--z-modal, 160);
803
- display: flex;
804
- align-items: center;
805
- justify-content: center;
806
- padding: 24px;
807
- box-sizing: border-box;
808
- background: var(--scrim);
790
+ /* the shared .hall-scrim (controls.css, task 1005085) is the layer and the
791
+ scrim; the panel inside is the shared .hall-dialog */
809
792
  color: var(--ink);
810
793
  font-family: var(--font-body);
811
794
  }
812
- .st-reviews[hidden] { display: none; }
813
- .st-reviews__dialog {
814
- width: min(780px, 100%);
815
- max-height: 100%;
816
- overflow-y: auto;
817
- box-sizing: border-box;
818
- padding: clamp(18px, 3vw, 28px);
819
- border-radius: var(--radius);
820
- background: var(--bg);
821
- }
795
+ .st-reviews__dialog { width: min(780px, 100%); }
822
796
  .st-reviews__head { display: flex; align-items: center; gap: 12px; margin-bottom: 6px; }
823
797
  .st-reviews__head .btn-ghost { margin-left: auto; }
824
798
  .st-reviews__title {
@@ -177,7 +177,7 @@
177
177
  <!-- The two small glass panels: the Tweak/CopyWrite Flow and Artist
178
178
  Review Status. The Approval queue is its own component
179
179
  (approval-queue.js), mounted by studio.js. -->
180
- <div class="st-overlay" id="st-panel" hidden>
180
+ <div class="hall-scrim st-overlay" id="st-panel" hidden>
181
181
  <div class="st-dialog" role="dialog" aria-modal="true" aria-labelledby="st-panel-title">
182
182
  <div class="st-dialog__head">
183
183
  <h2 class="st-dialog__title" id="st-panel-title" tabindex="-1">Work through tweaks</h2>
@@ -189,8 +189,8 @@
189
189
 
190
190
  <!-- The reviews filed before Tweak Mode (R04, retired in TW14): the
191
191
  garden, one piece at a time, answered here until none remain. -->
192
- <div class="st-reviews" id="st-reviews" hidden>
193
- <div class="st-reviews__dialog" role="dialog" aria-modal="true" aria-labelledby="st-reviews-title">
192
+ <div class="hall-scrim st-reviews" id="st-reviews" hidden>
193
+ <div class="hall-dialog hall-dialog--wide st-reviews__dialog" role="dialog" aria-modal="true" aria-labelledby="st-reviews-title">
194
194
  <div class="st-reviews__head">
195
195
  <h2 class="st-reviews__title" id="st-reviews-title" tabindex="-1">Reviews from before Tweak Mode</h2>
196
196
  <button type="button" class="btn-ghost" data-close-reviews>close</button>
@@ -226,6 +226,7 @@
226
226
  <script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
227
227
  <script src="/builders/craft-mode.js?v=2026-11-05-goals"></script>
228
228
  <script src="/builders/shell.js?v=2026-11-07-brand-mark"></script>
229
+ <script src="/builders/hall-dialog.js"></script>
229
230
  <script src="/builders/palette.js" defer></script>
230
231
  <!-- The kit, for its ONE renderer: the full-size ship-time visual figure
231
232
  (task 1003829), which the older reviews still show. -->
@@ -821,27 +821,33 @@
821
821
  body.innerHTML = home.panel === 'flow' ? flowHtml() : home.panel === 'site' ? siteHtml() : statusHtml();
822
822
  }
823
823
 
824
- function openPanel(kind, opener) {
824
+ // The two panels run on the hall's one dialog behaviour (hall-dialog.js, task
825
+ // 1005085): Escape closes the top one only, Tab stays inside, a click on the
826
+ // scrim closes, the scroll locks, and focus goes back to the door that opened it.
827
+ function panelModal() {
825
828
  const overlay = byId('st-panel');
826
- if (!overlay || !TITLES[kind]) return;
829
+ if (!overlay) return null;
830
+ return window.OTBDialog.bind(overlay, {
831
+ panel: overlay.querySelector('[role="dialog"]'),
832
+ lockClass: 'st-open',
833
+ initialFocus: () => byId('st-panel-title'),
834
+ onClose: () => { home.panel = null; },
835
+ });
836
+ }
837
+
838
+ function openPanel(kind, opener) {
839
+ const m = panelModal();
840
+ if (!m || !TITLES[kind]) return;
827
841
  home.panel = kind;
828
- home.opener = opener || null;
829
842
  home.picking = false;
830
843
  setText('st-panel-title', TITLES[kind]);
831
844
  paintPanel();
832
- overlay.hidden = false;
833
- document.documentElement.classList.add('st-open');
834
- const title = byId('st-panel-title');
835
- if (title) title.focus();
845
+ m.open(opener || null);
836
846
  }
837
847
 
838
848
  function closePanel() {
839
- const overlay = byId('st-panel');
840
- if (!overlay || overlay.hidden) return;
841
- overlay.hidden = true;
842
- home.panel = null;
843
- document.documentElement.classList.remove('st-open');
844
- if (home.opener && home.opener.focus) home.opener.focus();
849
+ const m = panelModal();
850
+ if (m) m.close();
845
851
  }
846
852
 
847
853
  // Release a round the reads cannot name (task 1004837). One line of why is
@@ -875,24 +881,29 @@
875
881
 
876
882
  // ---- the older reviews' dialog --------------------------------------------
877
883
 
878
- function openReviews(opener) {
884
+ function reviewsModal() {
879
885
  const box = byId('st-reviews');
880
- if (!box) return;
881
- home.opener = opener || null;
882
- box.hidden = false;
883
- document.documentElement.classList.add('st-open');
884
- const title = byId('st-reviews-title');
885
- if (title) title.focus();
886
+ if (!box) return null;
887
+ return window.OTBDialog.bind(box, {
888
+ panel: box.querySelector('[role="dialog"]'),
889
+ lockClass: 'st-open',
890
+ initialFocus: () => byId('st-reviews-title'),
891
+ // the door hides once the last review is answered: fall back to the flow door
892
+ returnFocus: (opener) => {
893
+ const door = byId('st-older');
894
+ return opener && !opener.hidden ? opener : (door && !door.hidden ? door : byId('st-flow'));
895
+ },
896
+ });
897
+ }
898
+
899
+ function openReviews(opener) {
900
+ const m = reviewsModal();
901
+ if (m) m.open(opener || null);
886
902
  }
887
903
 
888
904
  function closeReviews() {
889
- const box = byId('st-reviews');
890
- if (!box || box.hidden) return;
891
- box.hidden = true;
892
- document.documentElement.classList.remove('st-open');
893
- const door = byId('st-older');
894
- const back = home.opener && !home.opener.hidden ? home.opener : (door && !door.hidden ? door : byId('st-flow'));
895
- if (back && back.focus) back.focus();
905
+ const m = reviewsModal();
906
+ if (m) m.close();
896
907
  }
897
908
 
898
909
  // ---- the studio's own words, typed over in place ---------------------------
@@ -1072,7 +1083,7 @@
1072
1083
  const overlay = byId('st-panel');
1073
1084
  if (overlay) {
1074
1085
  overlay.addEventListener('click', (ev) => {
1075
- if (ev.target === overlay || ev.target.closest('[data-close-panel]')) { closePanel(); return; }
1086
+ if (ev.target.closest('[data-close-panel]')) { closePanel(); return; }
1076
1087
  const review = ev.target.closest('[data-review-page]');
1077
1088
  if (review && approvals) { ev.preventDefault(); const id = review.getAttribute('data-review-page'); closePanel(); approvals.open(id); return; }
1078
1089
  const rel = ev.target.closest('[data-release-page]');
@@ -1087,15 +1098,9 @@
1087
1098
  const reviews = byId('st-reviews');
1088
1099
  if (reviews) {
1089
1100
  reviews.addEventListener('click', (ev) => {
1090
- if (ev.target === reviews || ev.target.closest('[data-close-reviews]')) closeReviews();
1101
+ if (ev.target.closest('[data-close-reviews]')) closeReviews();
1091
1102
  });
1092
1103
  }
1093
- document.addEventListener('keydown', (ev) => {
1094
- if (ev.key !== 'Escape') return;
1095
- if (home.panel) { closePanel(); return; }
1096
- const box = byId('st-reviews');
1097
- if (box && !box.hidden) closeReviews();
1098
- });
1099
1104
 
1100
1105
  const hint = byId('st-hint');
1101
1106
  if (hint) hint.addEventListener('click', () => beginWords(null));
@@ -4604,14 +4604,8 @@ body[data-page="home"] #home-stats .stat-row { border-bottom-left-radius: 0; bor
4604
4604
  .attn-card__n { font-family: var(--font-display); font-size: 30px; font-weight: 300; line-height: 1; font-variant-numeric: tabular-nums; }
4605
4605
  .attn-card__t { font-weight: 600; }
4606
4606
  .attn-card__s { margin-top: auto; font-size: 12px; color: var(--ink-soft); }
4607
- .attn-dialog {
4608
- width: min(640px, calc(100vw - 32px)); max-height: 80vh; padding: 22px 24px; overflow: auto;
4609
- background: var(--bg-card); color: var(--ink); border: 0; border-radius: var(--radius);
4610
- }
4611
- .attn-dialog::backdrop { background: var(--scrim); }
4612
- .attn-dialog__head { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }
4613
- .attn-dialog__h { flex: 1; margin: 0; font-family: var(--font-display); font-weight: 500; font-size: 20px; }
4614
- .attn-dialog__x { min-width: 32px; min-height: 32px; background: none; border: 0; color: var(--ink-soft); font-size: 22px; cursor: pointer; }
4607
+ /* The window a card opens is the hall's one dialog (controls.css .hall-dialog,
4608
+ task 1005085); the slice's own heading is the dialog's title, so it hides. */
4615
4609
  .attn-dialog__body .ginbox__sec-h { display: none; }
4616
4610
 
4617
4611
  .home-split { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; align-items: start; }
@@ -60,6 +60,7 @@
60
60
  <script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
61
61
  <script src="/builders/craft-mode.js?v=2026-11-05-goals"></script>
62
62
  <script src="/builders/shell.js?v=2026-11-07-brand-mark"></script>
63
+ <script src="/builders/hall-dialog.js"></script>
63
64
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
64
65
  <script src="/builders/palette.js" defer></script>
65
66
  <!-- task-detail.js still owns the #/idea/N and #/blocker/N deep-link overlay
@@ -173,6 +173,19 @@
173
173
  }
174
174
  }
175
175
 
176
+ // Confirm a task. A verify-kind task's confirm attests the verification
177
+ // actually passed (the server requires { verified: true }), so the Archon
178
+ // affirms it in the hall's own confirm first (task 1005085).
179
+ async function confirmTask(t, btn) {
180
+ if (t.kind === 'verify' && !(await window.OTBDialog.confirm({
181
+ title: `Confirm that verification for #${t.id} actually passed?`,
182
+ body: 'The task is recorded as confirmed, with your word that its verification passed.',
183
+ confirmLabel: 'It passed', trigger: btn,
184
+ }))) return;
185
+ const body = t.kind === 'verify' ? { verified: true } : {};
186
+ runLifecycle(btn, `${API}/tasks/${encodeURIComponent(t.id)}/confirm`, body, `#${t.id} confirmed.`);
187
+ }
188
+
176
189
  // The two lifecycle DECISIONS (ship, confirm) as the city's ask card
177
190
  // (hall-ask.js; task 1004822, criterion wa6-hall-reads-like-home). Only a
178
191
  // decision the page waits on gets the card — claim, copy and retire stay plain
@@ -898,13 +911,7 @@
898
911
  }
899
912
  const confirmBtn = $('#confirm-btn');
900
913
  if (confirmBtn) {
901
- confirmBtn.addEventListener('click', () => {
902
- // A verify-kind task's confirm attests the verification actually passed
903
- // (the server requires { verified: true }); make the Archon affirm it.
904
- if (t.kind === 'verify' && !window.confirm(`Confirm that verification for #${t.id} actually passed?`)) return;
905
- const body = t.kind === 'verify' ? { verified: true } : {};
906
- runLifecycle(confirmBtn, `${API}/tasks/${encodeURIComponent(t.id)}/confirm`, body, `#${t.id} confirmed.`);
907
- });
914
+ confirmBtn.addEventListener('click', () => confirmTask(t, confirmBtn));
908
915
  }
909
916
 
910
917
  // task 1003063 (R10): PATCH goal_id, then a full reload so the goal line, the
@@ -995,13 +1002,20 @@
995
1002
  // reason is the record noting that someone did this and declining to say
996
1003
  // why. Asking costs one line.
997
1004
  if (!reason) { toast('Say why it is being retired — the reason goes in the record.'); return; }
998
- if (!window.confirm(`Retire task ${t.id}? It leaves the claimable queue. Promote restores it to the backlog.`)) return;
999
- abandonOpen = false;
1000
- runWrite(abandonBtn,
1001
- () => postJSON(`${API}/tasks/${encodeURIComponent(t.id)}/abandon`, { reason }),
1002
- `Task ${t.id} retired. Promote restores it to the backlog.`);
1005
+ retireTask(t, abandonBtn, reason);
1003
1006
  });
1004
1007
  }
1008
+ async function retireTask(task, btn, reason) {
1009
+ if (!(await window.OTBDialog.confirm({
1010
+ title: `Retire task ${task.id}?`,
1011
+ body: 'It leaves the claimable queue. Promote restores it to the backlog.',
1012
+ confirmLabel: 'Retire task', danger: true, trigger: btn,
1013
+ }))) return;
1014
+ abandonOpen = false;
1015
+ runWrite(btn,
1016
+ () => postJSON(`${API}/tasks/${encodeURIComponent(task.id)}/abandon`, { reason }),
1017
+ `Task ${task.id} retired. Promote restores it to the backlog.`);
1018
+ }
1005
1019
 
1006
1020
  const moveGoalBtn = $('#move-goal-btn');
1007
1021
  if (moveGoalBtn) {
@@ -142,6 +142,7 @@
142
142
  <script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
143
143
  <script src="/builders/craft-mode.js?v=2026-11-05-goals"></script>
144
144
  <script src="/builders/shell.js?v=2026-11-07-brand-mark"></script>
145
+ <script src="/builders/hall-dialog.js"></script>
145
146
  <script src="/builders/palette.js" defer></script>
146
147
 
147
148
  <script src="/builders/idea-objects.js?v=2026-09-27-split"></script>
@@ -146,6 +146,7 @@
146
146
  <script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
147
147
  <script src="/builders/craft-mode.js?v=2026-11-05-goals"></script>
148
148
  <script src="/builders/shell.js?v=2026-11-07-brand-mark"></script>
149
+ <script src="/builders/hall-dialog.js"></script>
149
150
  <script src="/builders/palette.js" defer></script>
150
151
  <script src="/builders/brand-holes.js?v=2026-09-29-aqfix"></script>
151
152
  <script src="/builders/tweak-editor-lib.js?v=2026-09-29-tw27"></script>
@@ -272,6 +272,7 @@
272
272
  <script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
273
273
  <script src="/builders/craft-mode.js?v=2026-11-05-goals"></script>
274
274
  <script src="/builders/shell.js?v=2026-11-07-brand-mark"></script>
275
+ <script src="/builders/hall-dialog.js"></script>
275
276
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
276
277
  <script src="/builders/palette.js" defer></script>
277
278
  <!-- shared component kit (window.OTBKit) — tabs, ledger, pager (task 2166). -->
@@ -175,6 +175,7 @@
175
175
  <script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
176
176
  <script src="/builders/craft-mode.js?v=2026-11-05-goals"></script>
177
177
  <script src="/builders/shell.js?v=2026-11-07-brand-mark"></script>
178
+ <script src="/builders/hall-dialog.js"></script>
178
179
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
179
180
  <script src="/builders/palette.js" defer></script>
180
181
  <!-- task 1004727: the ONE karma vote control every task/idea list renders -->
@@ -759,8 +759,14 @@
759
759
  async function doRelease(claimId, taskId, btn) {
760
760
  const key = `release:${claimId}`;
761
761
  if (pending.has(key)) return;
762
- if (!window.confirm(`Release your claim on #${taskId}? It returns to the queue and no ${curLabel()} are earned.`)) return;
762
+ // Held while the confirm is open too, so a second click cannot stack a second dialog.
763
763
  pending.add(key);
764
+ const yes = await window.OTBDialog.confirm({
765
+ title: `Release your claim on #${taskId}?`,
766
+ body: `It returns to the queue and no ${curLabel()} are earned.`,
767
+ confirmLabel: 'Release', danger: true, trigger: btn,
768
+ });
769
+ if (!yes) { pending.delete(key); return; }
764
770
  if (btn) { btn.disabled = true; btn.textContent = 'Releasing…'; }
765
771
  try {
766
772
  const out = await claimAction.release(claimId, taskId);
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.24.51",
3
+ "version": "1.24.52",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.24.51",
9
+ "version": "1.24.52",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.24.51",
3
+ "version": "1.24.52",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
@@ -11277,5 +11277,11 @@
11277
11277
  "id": "1005090",
11278
11278
  "text": "The public status page now talks plainly: 'Top builders', 'Recently shipped', 'Spending' instead of the old ancient-city words, and costs show in dollars."
11279
11279
  }
11280
+ ],
11281
+ "1.24.52": [
11282
+ {
11283
+ "id": "1005085",
11284
+ "text": "The hall's pop-up questions now use one Cloud Bongos dialog instead of the browser's grey box: every 'are you sure?' says plainly what will happen, and the six home-made pop-ups on different pages now share one look and the sa"
11285
+ }
11280
11286
  ]
11281
11287
  }
package/src/module-api.js CHANGED
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
81
81
  // MAJOR (see allowBoxScope below): passes the request through untouched.
82
82
  function deprecatedNoopMiddleware(_req, _res, next) { next(); }
83
83
 
84
- const CORE_VERSION = '1.24.51'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
84
+ const CORE_VERSION = '1.24.52'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
85
85
 
86
86
  // A namespaced logger so a module's log lines are attributable + consistent.
87
87
  // Usage: const log = api.logger('discord'); log.info('mounted');
@@ -46,7 +46,7 @@ function stubPage(present) {
46
46
  const els = {};
47
47
  for (const id of present) els[id] = { hidden: true, disabled: false, textContent: '', title: '', listeners: {}, addEventListener(t, fn) { this.listeners[t] = fn; } };
48
48
  globalThis.document = { getElementById: (id) => els[id] || null };
49
- globalThis.window = { confirm: () => true };
49
+ globalThis.window = { OTBDialog: { confirm: async () => true } }; // the branded confirm (task 1005085)
50
50
  globalThis.location = loc;
51
51
  return els;
52
52
  }
@@ -90,6 +90,9 @@ function bootPanel({ answers = {}, writes = null, mode = 'light' } = {}) {
90
90
  getElementById: byId,
91
91
  createElement: (tag) => augment(makeEl(tag, {})),
92
92
  get activeElement() { return boot.focused || null; },
93
+ // the shared dialog (hall-dialog.js, task 1005085) hears its keys on the document
94
+ listeners: {},
95
+ addEventListener(type, fn) { (this.listeners[type] = this.listeners[type] || []).push(fn); },
93
96
  };
94
97
  const api = {
95
98
  // The REAL client's shape: request(method, path, { body }). This fake once
@@ -110,6 +113,7 @@ function bootPanel({ answers = {}, writes = null, mode = 'light' } = {}) {
110
113
  win.window = win;
111
114
  vm.runInContext(read('brand-holes.js'), ctx, { filename: 'brand-holes.js' });
112
115
  vm.runInContext(read('approval-queue-lib.js'), ctx, { filename: 'approval-queue-lib.js' });
116
+ vm.runInContext(read('hall-dialog.js'), ctx, { filename: 'hall-dialog.js' }); // the shared dialog behaviour (task 1005085)
113
117
  vm.runInContext(read('approval-queue.js'), ctx, { filename: 'approval-queue.js' });
114
118
  const panel = win.OTBApprovalQueue.mount({
115
119
  api,
@@ -124,7 +128,9 @@ function bootPanel({ answers = {}, writes = null, mode = 'light' } = {}) {
124
128
  const q = (sel) => scrim.querySelector(sel);
125
129
  const qa = (sel) => scrim.querySelectorAll(sel);
126
130
  const click = async (el) => { assert.ok(el, 'the control exists'); scrim.fire('click', { target: el }); await settle(); await settle(); };
127
- boot = { body, scrim, q, qa, click, requests, counts, panel, classes, win, focused: null };
131
+ boot = { body, scrim, q, qa, click, requests, counts, panel, classes, win, focused: null,
132
+ // a key pressed anywhere in the page reaches the document, where the shared dialog listens
133
+ key: (ev) => (documentObj.listeners.keydown || []).forEach((fn) => fn({ preventDefault() {}, stopPropagation() {}, ...ev })) };
128
134
  return boot;
129
135
  }
130
136
 
@@ -330,7 +336,7 @@ await test('empty, closed with Escape, and focus goes back where it was', async
330
336
  await b.panel.open(); await settle();
331
337
  assert.equal(text(b.q('.aq-title')), 'Nothing waiting');
332
338
  assert.match(text(b.q('.aq-frame__note')), /Nothing is waiting for you/);
333
- b.scrim.fire('keydown', { key: 'Escape', preventDefault() {}, target: b.scrim });
339
+ b.key({ key: 'Escape', target: b.scrim });
334
340
  assert.equal(b.scrim.hidden, true);
335
341
  assert.equal(b.focused, opener);
336
342
  assert.ok(!b.classes.has('aq-open'));
@@ -510,7 +510,7 @@ test('the verdict classes and the scrim exist in the token tier, in both modes',
510
510
  assert.match(css, /\.ledger__verdict--danger\s*\{[^}]*var\(--danger\)/, 'fell short → --danger');
511
511
  assert.match(css, /\.ledger__verdict--none\s*\{[^}]*var\(--ink-soft\)/, 'not graded → --ink-soft');
512
512
  assert.match(css, /--scrim:/, 'the modal scrim is a token, not a sheet-local literal');
513
- assert.match(read('copy-desk.css'), /::backdrop\s*\{[^}]*var\(--scrim\)/, 'the copy-desk backdrop takes it');
513
+ assert.match(read('controls.css'), /\.hall-dialog::backdrop \{ background: var\(--scrim\); \}/, 'the one dialog backdrop takes it (task 1005085)');
514
514
  });
515
515
 
516
516
  // ---- 10. an outline: none always names its replacement (task 1003496) -----
@@ -288,7 +288,7 @@ test('the city sits in the hall chrome, behind the shell\'s full-screen switch',
288
288
  // task 1004685 (the living city): gov-living.js, the model under the drawing, and
289
289
  // gov-feeds.js, its one reader per page (shared with the shell's tally), load before it
290
290
  // task 1004702: the card answers its first ask in place, so the deck's card and sender (and the desk rows under them) load before the page
291
- assert.deepEqual(scripts, ['dom-utils', 'shell-trail', 'craft-mode', 'shell', 'palette', 'gov-living', 'gov-feeds', 'city-draw', 'hall-kit', 'street-draw', 'street-card', 'street-inside', 'street-desk', 'street-deck', 'city-decide', 'city-switchboard', 'city'], 'the shell first, as on every hall page');
291
+ assert.deepEqual(scripts, ['dom-utils', 'shell-trail', 'craft-mode', 'shell', 'hall-dialog', 'palette', 'gov-living', 'gov-feeds', 'city-draw', 'hall-kit', 'street-draw', 'street-card', 'street-inside', 'street-desk', 'street-deck', 'city-decide', 'city-switchboard', 'city'], 'the shell first, then the shared dialog (task 1005085), as on every hall page');
292
292
  // task 1004925: every script but dom-utils.js is deferred, so none holds up the
293
293
  // parser; defer runs them in document order, which is why the order above still
294
294
  // holds. dom-utils.js stays synchronous: the one-escaper guard
@@ -53,7 +53,9 @@ test('the page sheets spend only the fifteen, in px, and never opacity', () => {
53
53
  const cd = read('copy-desk.css');
54
54
  const literals = cd.match(/rgba?\([^)]*\)/g) || [];
55
55
  assert.deepEqual(literals, [], 'not even the scrim is a literal here any more');
56
- assert.match(cd, /\.cd-dialog::backdrop \{ background: var\(--scrim\); \}/, 'the backdrop spends the token');
56
+ // task 1005085: the dialogs are the hall's one dialog, whose backdrop lives in controls.css
57
+ assert.doesNotMatch(cd, /::backdrop/, 'no backdrop of its own');
58
+ assert.match(read('controls.css'), /\.hall-dialog::backdrop \{ background: var\(--scrim\); \}/, 'the shared backdrop spends the token');
57
59
  });
58
60
 
59
61
  test('every list is the family\'s row, and the card-in-a-card is gone', () => {
@@ -335,15 +337,16 @@ test('the filters and the search are the kit\'s bar; the duplicates are paged th
335
337
  assert.match(read('copy-desk.html'), /src="\/builders\/hall-kit\.js/, 'the page loads the kit');
336
338
  });
337
339
 
338
- test('the three dialogs are the family modal on a native <dialog>, with the number input in a field', () => {
340
+ test('the three dialogs are the hall\'s one dialog on a native <dialog>, with the number input in a field', () => {
339
341
  const html = read('copy-desk.html');
340
- // Three since task 1003114 (R03): flag, close, and the proposal composer.
341
- assert.equal((html.match(/<dialog id="cd-(close-dialog|dialog|propose-dialog)" class="ov-modal cd-dialog">/g) || []).length, 3);
342
+ // Three since task 1003114 (R03): flag, close, and the proposal composer; the
343
+ // shared .hall-dialog since task 1005085, each named by its own title.
344
+ assert.equal((html.match(/<dialog id="cd-(close-dialog|dialog|propose-dialog)" class="hall-dialog cd-dialog" aria-labelledby="cd-(close|dialog|propose)-h">/g) || []).length, 3);
342
345
  assert.match(html, /<div class="ov-field ov-field--wide">\s*<label class="ov-field__l" for="cd-close-task">[^<]*<\/label>\s*<input type="number" id="cd-close-task"/, 'the number input sits in the family field, which restates the 30px pill');
343
346
  assert.ok(!/<label[^>]*>[^<]*<span class="cd-dialog__hint"/.test(html), 'no hint lives inside a label (the label voice would uppercase it)');
344
347
  assert.equal((html.match(/class="ov-form__why"/g) || []).length, 4, 'the four hints are the form\'s own explainer line');
345
- assert.match(html, /class="btn-accent" id="cd-submit"/, 'the accent door files the flag');
346
- assert.match(html, /class="btn-accent" id="cd-propose-submit"/, 'and the proposal composer has its own');
348
+ assert.match(html, /class="btn" id="cd-submit"/, 'the accent door files the flag');
349
+ assert.match(html, /class="btn" id="cd-propose-submit"/, 'and the proposal composer has its own');
347
350
  });
348
351
 
349
352
  test('the proposal composer is prefilled, and says what filing it does and does not do', () => {