@bongos/core 1.24.62 → 1.24.64

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.
@@ -314,6 +314,16 @@ select option { background: var(--bg-card); color: var(--ink); }
314
314
  color: var(--ink-soft);
315
315
  }
316
316
  .hall-dialog__body p:last-child { margin-bottom: 0; }
317
+ /* the prompt's one field (OTBDialog.prompt, task 1005136): the hall's field,
318
+ the dialog's full width, under its label */
319
+ .hall-dialog__label {
320
+ display: block;
321
+ margin: var(--ds-space-xs) 0 var(--ds-space-hair);
322
+ font-size: var(--ds-text-sm);
323
+ color: var(--ink-soft);
324
+ }
325
+ .hall-dialog__field { display: block; width: 100%; margin-top: var(--ds-space-xs); }
326
+ .hall-dialog__label + .hall-dialog__field { margin-top: 0; }
317
327
  .hall-dialog__actions {
318
328
  display: flex;
319
329
  flex-wrap: wrap;
@@ -284,7 +284,11 @@
284
284
  // questions. Turning it ON needs no reason — the allowlist already is one.
285
285
  let reason = null;
286
286
  if (on) {
287
- reason = window.prompt('Why are you turning the runner off? (shown to anyone who asks why it is idle)');
287
+ reason = await window.OTBDialog.prompt({
288
+ title: 'Why are you turning the runner off?',
289
+ label: 'Shown to anyone who asks why it is idle',
290
+ confirmLabel: 'Turn off',
291
+ });
288
292
  if (reason === null) return; // cancelled — not the same as an empty reason
289
293
  }
290
294
  try {
@@ -414,7 +418,12 @@
414
418
  const pausing = btn.getAttribute('data-paused') !== '1';
415
419
  let reason = null;
416
420
  if (pausing) {
417
- reason = window.prompt('Why are you pausing this builder’s runner? (shown to them)');
421
+ reason = await window.OTBDialog.prompt({
422
+ title: 'Why are you pausing this builder’s runner?',
423
+ label: 'Shown to them',
424
+ confirmLabel: 'Pause',
425
+ trigger: btn,
426
+ });
418
427
  if (reason === null) return;
419
428
  }
420
429
  try {
@@ -4,7 +4,8 @@
4
4
  The UI audit found six dialog builds (attn, cd, mw, aq, ov, st), each with
5
5
  its own scrim, layer, Escape handling and focus rules, and fourteen
6
6
  window.confirm() calls whose browser box neither looks like the hall nor
7
- reads in its voice. This is the one of each that replaces them.
7
+ reads in its voice. This is the one of each that replaces them; its prompt
8
+ replaces the browser's text box the same way (task 1005136).
8
9
 
9
10
  window.OTBDialog:
10
11
  confirm({ title, body, confirmLabel, cancelLabel, danger }) → Promise<boolean>
@@ -12,6 +13,14 @@
12
13
  starts a new paragraph. Resolves true only on the confirm button;
13
14
  Cancel, the close mark, Escape and a click on the scrim all resolve
14
15
  false. A `danger` confirm opens with focus on Cancel.
16
+ prompt({ title, body, label, value, placeholder, confirmLabel, cancelLabel, required, maxLength })
17
+ → Promise<string|null>
18
+ The branded prompt (task 1005136): the confirm with one text field.
19
+ Resolves the text as typed on the confirm button or Enter, and null
20
+ on Cancel, the close mark, Escape or the scrim — so a caller can tell
21
+ "cancelled" from "left empty". `required` keeps the confirm button
22
+ off while the field is blank. The field is labelled by `label`, or by
23
+ the title when there is none; it opens focused with its text selected.
15
24
  open({ title, body, actions, wide, className, initialFocus }) → { el, close(value), result }
16
25
  A dialog built here and removed on close; `result` resolves with the
17
26
  pressed action's `value` (or null when dismissed).
@@ -249,7 +258,46 @@
249
258
  return dlg.result.then((v) => v === true);
250
259
  }
251
260
 
252
- const api = { bind, open, confirm };
261
+ // The prompt's confirm action resolves to this, never to a value a caller could
262
+ // pass, so "use what was typed" cannot be confused with an action's own value.
263
+ const TYPED = {};
264
+ let fieldSeq = 0;
265
+
266
+ function prompt(opts = {}) {
267
+ const d = doc();
268
+ const fid = `hall-prompt-${++fieldSeq}`;
269
+ const form = d.createElement('form');
270
+ form.className = 'hall-dialog__form';
271
+ form.noValidate = true;
272
+ const label = opts.label ? `<label class="hall-dialog__label" for="${fid}">${esc(opts.label)}</label>` : '';
273
+ form.innerHTML = (opts.body ? bodyHtml(opts.body) : '') + label
274
+ + `<input type="text" class="hall-dialog__field" id="${fid}" autocomplete="off">`;
275
+ const field = form.querySelector('input');
276
+ if (!opts.label) field.setAttribute('aria-label', String(opts.title || ''));
277
+ if (opts.placeholder) field.setAttribute('placeholder', String(opts.placeholder));
278
+ if (opts.maxLength > 0) field.setAttribute('maxlength', String(opts.maxLength));
279
+ field.value = opts.value == null ? '' : String(opts.value);
280
+ const dlg = open({
281
+ title: opts.title,
282
+ body: form,
283
+ actions: [
284
+ { label: opts.cancelLabel || 'Cancel', value: null, kind: 'ghost' },
285
+ { label: opts.confirmLabel || 'Save', value: TYPED, kind: 'primary' },
286
+ ],
287
+ trigger: opts.trigger,
288
+ initialFocus: () => field,
289
+ });
290
+ const ok = dlg.el.querySelector('[data-dialog-value="1"]');
291
+ const sync = () => { if (opts.required) ok.disabled = !field.value.trim(); };
292
+ sync();
293
+ field.addEventListener('input', sync);
294
+ // Enter in the one field submits the form: the same as pressing the confirm.
295
+ form.addEventListener('submit', (ev) => { ev.preventDefault(); if (!ok.disabled) dlg.close(TYPED); });
296
+ if (typeof field.select === 'function') field.select();
297
+ return dlg.result.then((v) => (v === TYPED ? field.value : null));
298
+ }
299
+
300
+ const api = { bind, open, confirm, prompt };
253
301
  if (typeof window !== 'undefined') window.OTBDialog = api;
254
302
  if (typeof module !== 'undefined' && module.exports) module.exports = api;
255
303
  })();
@@ -438,13 +438,18 @@
438
438
  if (v) act(() => call('PATCH', `/${v.id}`, { page: views.page, is_default: !v.is_default }),
439
439
  v.is_default ? `“${v.name}” is no longer your default.` : `This page now opens with “${v.name}”.`);
440
440
  } else if (t.hasAttribute('data-rename')) {
441
- const v = pick('data-rename');
442
- const name = v && window.prompt('New name for this view', v.name);
443
- if (name && name.trim() && name.trim() !== v.name) act(() => call('PATCH', `/${v.id}`, { page: views.page, name }), 'Renamed.');
441
+ renameView(pick('data-rename'), t);
444
442
  } else if (t.hasAttribute('data-delete')) {
445
443
  deleteView(pick('data-delete'), t);
446
444
  }
447
445
  });
446
+ async function renameView(v, trigger) {
447
+ if (!v) return;
448
+ const name = await window.OTBDialog.prompt({
449
+ title: 'New name for this view', value: v.name, confirmLabel: 'Rename', required: true, trigger,
450
+ });
451
+ if (name && name.trim() && name.trim() !== v.name) act(() => call('PATCH', `/${v.id}`, { page: views.page, name }), 'Renamed.');
452
+ }
448
453
  async function deleteView(v, trigger) {
449
454
  if (!v || !(await window.OTBDialog.confirm({
450
455
  title: `Delete the view “${v.name}”?`,
@@ -247,11 +247,19 @@
247
247
  var btn = e.target && e.target.closest ? e.target.closest('[data-roster-status]') : null;
248
248
  if (!btn) return;
249
249
  var status = btn.getAttribute('data-roster-status');
250
- var reason = status === 'inactive' ? (window.prompt('Reason for offboarding (optional):', '') || null) : null;
251
- btn.disabled = true;
252
- patchBuilder(btn.getAttribute('data-id'), 'status', { status: status, reason: reason })
253
- .then(function () { toast(status === 'inactive' ? 'Builder offboarded.' : 'Builder reactivated.'); return refreshGov(); })
254
- .catch(function (err) { btn.disabled = false; toast('Could not change standing: ' + (err.message || err)); });
250
+ // Offboarding asks for a reason in the hall's own dialog (task 1005136). The reason
251
+ // stays optional; Cancel now stops the offboarding, where the browser box's Cancel
252
+ // used to offboard anyway with no reason.
253
+ var asked = status === 'inactive'
254
+ ? window.OTBDialog.prompt({ title: 'Reason for offboarding (optional)', confirmLabel: 'Offboard', trigger: btn })
255
+ : Promise.resolve('');
256
+ asked.then(function (reason) {
257
+ if (reason === null) return;
258
+ btn.disabled = true;
259
+ patchBuilder(btn.getAttribute('data-id'), 'status', { status: status, reason: reason || null })
260
+ .then(function () { toast(status === 'inactive' ? 'Builder offboarded.' : 'Builder reactivated.'); return refreshGov(); })
261
+ .catch(function (err) { btn.disabled = false; toast('Could not change standing: ' + (err.message || err)); });
262
+ });
255
263
  });
256
264
  }
257
265
 
@@ -42,7 +42,10 @@
42
42
 
43
43
  async function rename(id, row) {
44
44
  const current = row.querySelector('strong').textContent;
45
- const next = window.prompt('Name this card', current === 'Unnamed card' ? '' : current);
45
+ const next = await window.OTBDialog.prompt({
46
+ title: 'Name this card', value: current === 'Unnamed card' ? '' : current, confirmLabel: 'Save',
47
+ trigger: row.querySelector('.passkeys-rename'),
48
+ });
46
49
  if (next === null) return;
47
50
  try {
48
51
  const res = await api.request('POST', `${API}/me/passkeys/rename`, { body: { credential_id: id, nickname: next } });
@@ -856,8 +856,12 @@
856
856
  // round goes back to /tweak if it still holds its batch, else it is closed.
857
857
  async function releasePage(pageId, taskId, button) {
858
858
  if (!lib || !lib.PAGE_ID_RE.test(String(pageId || ''))) return;
859
- const note = window.prompt('Why is this round being released? One line, for whoever picks the page up next.',
860
- 'Stuck in a state the Studio could not read; released so the page can be tweaked again.');
859
+ const note = await window.OTBDialog.prompt({
860
+ title: 'Why is this round being released?',
861
+ label: 'One line, for whoever picks the page up next.',
862
+ value: 'Stuck in a state the Studio could not read; released so the page can be tweaked again.',
863
+ confirmLabel: 'Release', required: true, trigger: button,
864
+ });
861
865
  if (note == null) return;
862
866
  if (button) button.disabled = true;
863
867
  const body = { note };
@@ -191,7 +191,12 @@
191
191
 
192
192
  async function release(version) {
193
193
  if (releasing) return;
194
- if (!window.confirm(`Release ${version} to every project?\n\nEvery other project will be offered ${version} and everything merged before it. This cannot be pressed back; a newer version can be released after it.`)) return;
194
+ // the hall's own confirm (hall-dialog.js; the deploy page loads it after the shell)
195
+ if (!(await window.OTBDialog.confirm({
196
+ title: `Release ${version} to every project?`,
197
+ body: `Every other project will be offered ${version} and everything merged before it. This cannot be pressed back; a newer version can be released after it.`,
198
+ confirmLabel: 'Release',
199
+ }))) return;
195
200
  releasing = version;
196
201
  releaseNote = null;
197
202
  paint();
@@ -343,22 +343,25 @@ h1{font-family:var(--font-display);font-weight:700;font-size:clamp(24px,3.4vw,32
343
343
  .lede a{color:var(--link-ink);font-weight:700;text-decoration:none;}
344
344
  .lede a:hover{text-decoration:underline;}
345
345
 
346
- /* ── buttons ── */
347
- .btn{display:inline-flex;align-items:center;justify-content:center;gap:9px;cursor:pointer;font-family:var(--font-display);font-weight:700;font-size:15px;color:var(--accent-on);background:var(--accent);border:none;border-radius:999px;padding:11px 22px;text-decoration:none;box-shadow:0 10px 22px -8px color-mix(in srgb,var(--accent) 70%,transparent);transition:transform .15s ease, box-shadow .15s ease, background .15s ease;}
346
+ /* ── buttons ──
347
+ On the shared scales (task 1005136): the pill corner, the type step and the fast
348
+ duration and ease every surface spends. The modifier pairs answer to the hall's
349
+ names too (.btn--ghost, .btn--danger), which is what the shared dialog builds. */
350
+ .btn{display:inline-flex;align-items:center;justify-content:center;gap:9px;cursor:pointer;font-family:var(--font-display);font-weight:700;font-size:var(--ds-text-lg);color:var(--accent-on);background:var(--accent);border:none;border-radius:var(--ds-radius-pill);padding:11px 22px;text-decoration:none;box-shadow:0 10px 22px -8px color-mix(in srgb,var(--accent) 70%,transparent);transition:transform var(--ds-dur-fast) var(--ds-ease), box-shadow var(--ds-dur-fast) var(--ds-ease), background var(--ds-dur-fast) var(--ds-ease);}
348
351
  .btn:hover{transform:translateY(-1px);box-shadow:0 14px 28px -8px color-mix(in srgb,var(--accent) 80%,transparent);}
349
352
  .btn:active{transform:translateY(0) scale(.98);}
350
353
  .btn:disabled{background:color-mix(in srgb,var(--ink) 12%,var(--bg-deep));color:color-mix(in srgb,var(--ink) 40%,var(--bg-deep));box-shadow:none;cursor:not-allowed;transform:none;}
351
- .btn.ghost{background:transparent;color:var(--ink-soft);box-shadow:none;border:2px solid var(--line);}
352
- .btn.ghost:hover{border-color:var(--accent);color:var(--accent-deep);}
354
+ .btn.ghost,.btn.btn--ghost{background:transparent;color:var(--ink-soft);box-shadow:none;border:2px solid var(--line);}
355
+ .btn.ghost:hover,.btn.btn--ghost:hover{border-color:var(--accent);color:var(--accent-deep);}
353
356
  /* a disabled ghost button must LOOK disabled — the same .btn:disabled-vs-.btn.ghost
354
357
  specificity tie .btn.danger:disabled fixed below, applied to "Applied" on the
355
358
  map's card (R10, task 1002286: it kept full-strength ghost styling and only the
356
359
  cursor said it was inert, task 1003774). */
357
360
  .btn.ghost:disabled{background:transparent;color:color-mix(in srgb,var(--ink) 40%,var(--bg-deep));border-color:color-mix(in srgb,var(--ink) 18%,transparent);box-shadow:none;cursor:not-allowed;transform:none;}
358
361
  .btn.ghost:disabled:hover{border-color:color-mix(in srgb,var(--ink) 18%,transparent);color:color-mix(in srgb,var(--ink) 40%,var(--bg-deep));}
359
- .btn.small{font-size:13.5px;padding:8px 16px;}
360
- .btn.danger{background:transparent;color:var(--err);box-shadow:none;border:2px solid color-mix(in srgb,var(--err) 35%,transparent);}
361
- .btn.danger:hover{background:var(--err-soft);border-color:var(--err);}
362
+ .btn.small{font-size:var(--ds-text-sm-plus);padding:8px 16px;}
363
+ .btn.danger,.btn.btn--danger{background:transparent;color:var(--err);box-shadow:none;border:2px solid color-mix(in srgb,var(--err) 35%,transparent);}
364
+ .btn.danger:hover,.btn.btn--danger:hover{background:var(--err-soft);border-color:var(--err);}
362
365
  /* a disabled danger button must LOOK disabled. .btn.danger is declared AFTER
363
366
  .btn:disabled at equal specificity, so without these it keeps full-strength
364
367
  --err and only the cursor says the control is inert — on a destructive button
@@ -1152,8 +1155,8 @@ p.sec-p{font-size:var(--fs-lede);font-weight:500;color:var(--ink-soft);line-heig
1152
1155
  The scrim is a plain rgba black in BOTH modes on purpose, so it needs no dark
1153
1156
  override at all — a token here would have to be ink, and ink is near-white in
1154
1157
  dark, which paints a white sheet over the page instead of dimming it. */
1155
- .cardScrim{position:fixed;inset:0;z-index:60;display:flex;align-items:center;justify-content:center;
1156
- padding:24px;background:rgba(0,0,0,.55);backdrop-filter:blur(3px);}
1158
+ .cardScrim{position:fixed;inset:0;z-index:var(--ds-z-modal);display:flex;align-items:center;justify-content:center;
1159
+ padding:var(--ds-space-md);background:rgba(0,0,0,.55);backdrop-filter:blur(3px);}
1157
1160
  /* THE POSTER (task 1003301; the v2 pick, direction §7): a specimen sheet in the
1158
1161
  world, not a panel. min(560px,100%) on bg-card closed by one rule hairline,
1159
1162
  no shadow (the world has no shadow vocabulary for UI). A flex column ordered
@@ -1166,13 +1169,13 @@ p.sec-p{font-size:var(--fs-lede);font-weight:500;color:var(--ink-soft);line-heig
1166
1169
  / relationship, the faces, the facts as a mono hairline ledger, the doors. */
1167
1170
  .projCard{position:relative;container-type:inline-size;display:flex;flex-direction:column;
1168
1171
  width:min(560px,100%);max-height:calc(100vh - 48px);overflow:auto;text-align:left;
1169
- background:var(--bg-card);border:1px solid var(--rule);border-radius:var(--radius);box-shadow:none;padding:0 0 26px;}
1172
+ background:var(--bg-card);border:var(--ds-line) solid var(--rule);border-radius:var(--ds-radius-panel);box-shadow:none;padding:0 0 26px;}
1170
1173
  .projCard:not(.has-art){padding-top:26px;}
1171
1174
  .projCard .cardArt{order:0;display:block;width:100%;aspect-ratio:5/2;object-fit:cover;
1172
- border-radius:var(--radius) var(--radius) 0 0;}
1175
+ border-radius:var(--ds-radius-panel) var(--ds-radius-panel) 0 0;}
1173
1176
  .projCard .cardX{position:absolute;top:12px;right:12px;z-index:4;display:inline-flex;align-items:center;
1174
- justify-content:center;width:30px;height:30px;border:0;border-radius:var(--radius-pill);
1175
- background:var(--glass-strong);color:var(--ink);cursor:pointer;font-size:14px;line-height:1;}
1177
+ justify-content:center;width:30px;height:30px;border:0;border-radius:var(--ds-radius-pill);
1178
+ background:var(--glass-strong);color:var(--ink);cursor:pointer;font-size:var(--ds-text-md);line-height:1;}
1176
1179
  .projCard .cardX:hover{background:var(--glass-edge);}
1177
1180
  /* on art the close control takes the void pill: the one legal literal is black with alpha */
1178
1181
  .projCard.has-art .cardX{background:var(--void-pill);color:var(--void-ink);}
@@ -1740,3 +1743,23 @@ p.sec-p{font-size:var(--fs-lede);font-weight:500;color:var(--ink-soft);line-heig
1740
1743
  #view-new .inviteBox{margin-top:34px;}
1741
1744
  #view-new .inviteBox .finePrint{margin-top:10px;}
1742
1745
  #view-new #wizInviteLater{margin-top:14px;}
1746
+
1747
+ /* ── the shared dialog (hall-dialog.js; task 1005136) ──
1748
+ The hub's one question ("start another project while one is building?") is the
1749
+ hall's own dialog, OTBDialog.confirm, not the browser's box. Its frame is the
1750
+ project card's: the card ground, one hairline, the panel corner, on the shared
1751
+ modal layer, over the same black scrim, and like the card no shadow (the world
1752
+ has no shadow vocabulary for UI). Its buttons are this sheet's .btn (the
1753
+ --ghost/--danger names above). */
1754
+ .hall-dialog{z-index:var(--ds-z-modal);box-sizing:border-box;width:min(480px,calc(100vw - 2 * var(--ds-space-sm)));
1755
+ max-height:calc(100dvh - 2 * var(--ds-space-md));overflow-y:auto;margin:auto;padding:var(--ds-space-md);
1756
+ color:var(--ink);background:var(--bg-card);border:var(--ds-line) solid var(--rule);
1757
+ border-radius:var(--ds-radius-panel);box-shadow:none;text-align:left;}
1758
+ .hall-dialog::backdrop{background:rgba(0,0,0,.55);}
1759
+ .hall-dialog__head{display:flex;align-items:flex-start;gap:var(--ds-space-xs);margin:0 0 var(--ds-space-xs);}
1760
+ .hall-dialog__title{flex:1;margin:0;font-family:var(--font-display);font-size:var(--ds-text-3xl);font-weight:500;line-height:1.25;color:var(--ink);}
1761
+ .hall-dialog__x{flex:none;min-width:32px;min-height:32px;padding:0;font:inherit;font-size:var(--ds-text-4xl);line-height:1;
1762
+ color:var(--ink-soft);background:transparent;border:0;border-radius:var(--ds-radius-pill);cursor:pointer;}
1763
+ .hall-dialog__x:hover{color:var(--ink);}
1764
+ .hall-dialog__body p{margin:0 0 var(--ds-space-xs);font-size:var(--ds-text-md);line-height:1.5;color:var(--ink-soft);}
1765
+ .hall-dialog__actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--ds-space-xs);margin-top:var(--ds-space-md);}
@@ -504,7 +504,9 @@ body{
504
504
  @media (max-width:900px){
505
505
  .topbar{height:var(--top-h);padding:0 12px;gap:10px}
506
506
  .barLeft{gap:12px}
507
- .barRight{gap:12px}
507
+ /* the shared step, not 12: at 320 signed out the bar was 1px wider than the room, and the
508
+ mark slid under the projects trigger by that pixel (task 1005136) */
509
+ .barRight{gap:var(--ds-space-xs)}
508
510
  /* the wordmark folds away so the bar holds ONE row down to 320; the link keeps its name */
509
511
  .barMark{min-width:44px;justify-content:center}
510
512
  .mark-word{display:none}
@@ -515,6 +517,10 @@ body{
515
517
  .pill{min-height:42px;font-size:13.5px}
516
518
  .signin,.newacct{padding:0 14px}
517
519
  .newacct{margin-right:6px}
520
+ /* signed in, the bar carries four doors and the mark: the second-account door folds
521
+ into the profile band (its bandFold twin) so the bar holds one row with no overlap
522
+ down to 320 (task 1005136) */
523
+ .barRight:has(.who) .newacct{display:none}
518
524
  .bandIn{padding:0 12px 16px}
519
525
  .bandRow{grid-template-columns:1fr;gap:0}
520
526
  .bandT{font-size:20px}
@@ -749,6 +755,8 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
749
755
  Apex-only because the page is — /account is served only where
750
756
  platform-identity runs, so a child instance loses the item whole. -->
751
757
  <!--apex-only:start--><li class="bandCell"><a class="bandT" href="/invitations">Invitations</a></li><li class="bandCell"><a class="bandT" href="/connections">Connections</a></li><li class="bandCell"><a class="bandT" href="/account">Account</a></li><!--apex-only:end-->
758
+ <!-- the bar's second-account door, folded here on a narrow bar (task 1005136) -->
759
+ <li class="bandCell bandFold"><a class="bandT" href="/api/bongos/auth/web/start?return=/&amp;repos=1&amp;switch=1" title="Sign in with a different GitHub account">New account</a></li>
752
760
  <!-- the honest hover target; the script upgrades the click to the POST -->
753
761
  <li class="bandCell"><a class="bandT signout" href="/api/bongos/auth/logout">Sign out</a></li>
754
762
  </ul>
@@ -2185,6 +2185,8 @@ summary{min-height:24px;padding:3px 0;}
2185
2185
  <footer class="foot"><!--apex-only:start--><span>A non-profit, capture-resistant commons. </span><!--apex-only:end-->Built by agents, on <span data-brand-product-inline>Cloud&nbsp;Bongos</span>. <a href="https://www.npmjs.com/package/@bongos/core" target="_blank" rel="noopener">Open source</a> (<a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank" rel="noopener">AGPL-3.0</a>).<nav class="footNav" aria-label="Site"><a href="/#about">About</a><a href="/docs">Docs</a><a href="/privacy">Privacy</a><a href="/terms">Terms</a><a href="/contact">Contact</a><!--apex-only:start--><a data-link-hall href="{{buildersOrigin}}">Builders' hall</a><!--apex-only:end--></nav></footer>
2186
2186
 
2187
2187
  <script src="/modules/hall-ui/public/dom-utils.js"></script>
2188
+ <!-- the hall's one dialog (task 1005136): the hub asks its question in it, never in the browser's box -->
2189
+ <script src="/modules/hall-ui/public/hall-dialog.js"></script>
2188
2190
  <script>
2189
2191
  /* Brand-pack fill (R57): the server injects window.__BRANDING__ ahead of this
2190
2192
  script; baked markup above is the standalone fallback. */
@@ -7855,8 +7857,14 @@ summary{min-height:24px;padding:3px 0;}
7855
7857
  var again = document.createElement('button');
7856
7858
  again.type = 'button'; again.className = 'again'; again.textContent = 'Start another project';
7857
7859
  again.addEventListener('click', function () {
7858
- /* a standup still being watched is not abandoned by accident */
7859
- if (instanceId && pollTimer && !confirm((state.name || state.slug) + ' keeps building — you’ll find it on your projects page. Start a new one?')) return;
7860
+ /* a standup still being watched is not abandoned by accident — asked in the
7861
+ shared dialog (hall-dialog.js), never the browser's box (task 1005136) */
7862
+ var asked = instanceId && pollTimer
7863
+ ? window.OTBDialog.confirm({ title: 'Start a new one?', body: (state.name || state.slug) + ' keeps building — you’ll find it on your projects page.', confirmLabel: 'Start a new one', trigger: again })
7864
+ : Promise.resolve(true);
7865
+ asked.then(function (yes) { if (yes) startAnother(); });
7866
+ });
7867
+ function startAnother() {
7860
7868
  resetForNewProject();
7861
7869
  /* the deliberate goodbye: the record goes too, so nothing offers to resume
7862
7870
  the project the founder has just said they are finished watching */
@@ -7868,7 +7876,7 @@ summary{min-height:24px;padding:3px 0;}
7868
7876
  try { sessionStorage.removeItem(CREATED_KEY); } catch (e) {}
7869
7877
  paintResumeOffer(null);
7870
7878
  show(1, true);
7871
- });
7879
+ }
7872
7880
  $('doneActs').insertBefore(again, $('hopLink'));
7873
7881
  }
7874
7882
  wireAgain();
@@ -131,7 +131,9 @@ async function renderPage({ editor }) {
131
131
  return R.fill(view('page.html'), {
132
132
  base: BASE,
133
133
  assetVersion: v,
134
- editorAssets: editor ? `<script src="${BASE}/assets/edit.js?v=${v}" defer></script>` : '',
134
+ // the editor asks its questions in the hall's one dialog: the shared escaper first,
135
+ // then the dialog, then the editor (task 1005136)
136
+ editorAssets: editor ? `<script src="/modules/hall-ui/public/dom-utils.js" defer></script><script src="/modules/hall-ui/public/hall-dialog.js" defer></script><script src="${BASE}/assets/edit.js?v=${v}" defer></script>` : '',
135
137
  draftBanner: c.draft ? '<div class="draft" role="note">DRAFT</div>' : '',
136
138
  editBar: editor ? editBar(editor, c) : '',
137
139
  entity: R.esc(c.entity),
@@ -120,10 +120,17 @@
120
120
 
121
121
  startBtn.addEventListener('click', function () { status.textContent = ''; setEditing(true); });
122
122
 
123
+ // Cancel with edits asks in the hall's own dialog (hall-dialog.js, loaded with this
124
+ // file for an editor; task 1005136), never the browser's box.
123
125
  cancelBtn.addEventListener('click', function () {
124
- if (changes().count && !confirm('Discard your unsaved changes?')) return;
125
- editing = false;
126
- location.reload();
126
+ var asked = changes().count
127
+ ? window.OTBDialog.confirm({ title: 'Discard your unsaved changes?', confirmLabel: 'Discard', trigger: cancelBtn })
128
+ : Promise.resolve(true);
129
+ asked.then(function (yes) {
130
+ if (!yes) return;
131
+ editing = false;
132
+ location.reload();
133
+ });
127
134
  });
128
135
 
129
136
  saveBtn.addEventListener('click', function () {
@@ -66,9 +66,9 @@ p { margin: 0 0 16px; max-width: 64ch; }
66
66
 
67
67
  .btn {
68
68
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
69
- font: 600 16px var(--font); color: var(--on-accent); background: var(--accent);
70
- border: 0; border-radius: 999px; padding: 13px 26px; cursor: pointer;
71
- transition: transform .12s ease, filter .12s ease;
69
+ font: 600 var(--ds-text-xl) var(--font); color: var(--on-accent); background: var(--accent);
70
+ border: 0; border-radius: var(--ds-radius-pill); padding: 13px 26px; cursor: pointer;
71
+ transition: transform var(--ds-dur-fast) var(--ds-ease), filter var(--ds-dur-fast) var(--ds-ease);
72
72
  }
73
73
  .btn:hover:not(:disabled) { filter: brightness(1.05); transform: translateY(-1px); }
74
74
  .btn:disabled { background: var(--deep); color: var(--ink-faint); cursor: not-allowed; }
@@ -198,7 +198,7 @@ section { padding: 72px 0; border-bottom: 1px solid var(--rule-soft); }
198
198
  .draft { letter-spacing: .2em; }
199
199
 
200
200
  /* ── team: Sign in with Bongos + in-place editing ───────── */
201
- .btn--ghost { background: transparent; color: var(--ink); border: 1px solid var(--rule); padding: 10px 20px; font-size: 15px; }
201
+ .btn--ghost { background: transparent; color: var(--ink); border: var(--ds-line) solid var(--rule); padding: 10px 20px; font-size: var(--ds-text-lg); }
202
202
  .btn--ghost:hover { border-color: var(--accent); }
203
203
  .gate__team { margin-top: 36px; padding-top: 24px; border-top: 1px solid var(--rule-soft); display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; font-size: 14px; color: var(--ink-soft); text-decoration: none; }
204
204
  .gate__team a { text-decoration: none; }
@@ -232,3 +232,30 @@ section { padding: 72px 0; border-bottom: 1px solid var(--rule-soft); }
232
232
  .tok { white-space: nowrap; }
233
233
  .is-editing .tok, .is-editing .cite { background: color-mix(in srgb, var(--sea) 18%, transparent); border-radius: 3px; padding: 0 2px; cursor: default; }
234
234
  .is-editing [data-kind="para"], .is-editing [data-kind="list"] { padding: 4px 6px; margin-left: -6px; margin-right: -6px; }
235
+
236
+ /* ── the dialog ─────────────────────────────────────────────
237
+ The editor's "discard your changes?" is the hall's one dialog (hall-dialog.js,
238
+ OTBDialog.confirm; task 1005136), drawn here on this page's tokens and the shared
239
+ scales: the card ground, one hairline, the panel corner, the one floating shadow,
240
+ the modal layer, over a black scrim (the one literal the Fifteen Rule allows). */
241
+ .hall-dialog {
242
+ z-index: var(--ds-z-modal); box-sizing: border-box;
243
+ width: min(480px, calc(100vw - 2 * var(--ds-space-sm)));
244
+ max-height: calc(100dvh - 2 * var(--ds-space-md)); overflow-y: auto;
245
+ margin: auto; padding: var(--ds-space-md);
246
+ color: var(--ink); background: var(--card);
247
+ border: var(--ds-line) solid var(--rule); border-radius: var(--ds-radius-panel);
248
+ box-shadow: var(--ds-shadow-pop);
249
+ }
250
+ .hall-dialog::backdrop { background: rgba(0, 0, 0, .55); }
251
+ .hall-dialog__head { display: flex; align-items: flex-start; gap: var(--ds-space-xs); margin: 0 0 var(--ds-space-xs); }
252
+ .hall-dialog__title { flex: 1; margin: 0; font-size: var(--ds-text-3xl); font-weight: 500; line-height: 1.25; }
253
+ .hall-dialog__x {
254
+ flex: none; min-width: 32px; min-height: 32px; padding: 0;
255
+ font: inherit; font-size: var(--ds-text-4xl); line-height: 1; color: var(--ink-soft);
256
+ background: transparent; border: 0; border-radius: var(--ds-radius-pill); cursor: pointer;
257
+ }
258
+ .hall-dialog__x:hover { color: var(--ink); }
259
+ .hall-dialog__body p { margin: 0 0 var(--ds-space-xs); font-size: var(--ds-text-md); line-height: 1.5; color: var(--ink-soft); }
260
+ .hall-dialog__actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--ds-space-xs); margin-top: var(--ds-space-md); }
261
+ .hall-dialog__actions .btn { font-size: var(--ds-text-lg); padding: 10px 20px; }
@@ -197,7 +197,7 @@
197
197
  });
198
198
 
199
199
  // Step two. Closes over `p`, the press that was shown, so what is sent is what was displayed.
200
- const confirm = () => {
200
+ const confirmAction = () => {
201
201
  const p = state.pending;
202
202
  if (!p) return;
203
203
  return run(async () => {
@@ -218,7 +218,7 @@
218
218
  const rb = t.closest('[data-rollback]');
219
219
  if (rb) { state.pending = { action: 'rollback', deployId: rb.getAttribute('data-rollback'), liveWas: state.hist.liveDeployId }; state.error = null; state.done = null; return paint(); }
220
220
  if (t.closest('#rd-cancel')) { state.pending = null; return paint(); }
221
- if (t.closest('#rd-confirm')) return confirm();
221
+ if (t.closest('#rd-confirm')) return confirmAction();
222
222
  const v = t.closest('[data-view]');
223
223
  if (v) { state.viewing = v.getAttribute('data-view'); return paint(); }
224
224
  if (t.closest('#rd-unview')) { state.viewing = null; return paint(); }
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.24.62",
3
+ "version": "1.24.64",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.24.62",
9
+ "version": "1.24.64",
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.62",
3
+ "version": "1.24.64",
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",
@@ -11347,5 +11347,17 @@
11347
11347
  "id": "1005137",
11348
11348
  "text": "On a phone the hall's top bar now fits without cutting off buttons, the Studio stays a night room in dark mode, the sky page's dots no longer cover its labels, and the components reference page is readable in dark mode."
11349
11349
  }
11350
+ ],
11351
+ "1.24.63": [
11352
+ {
11353
+ "id": "1005136",
11354
+ "text": "The last browser pop-up boxes are gone: every question the site asks (rename, give a reason, are you sure) now opens in Cloud Bongos's own dialog, and on a phone the signed-in front page no longer squeezes the logo under the m"
11355
+ }
11356
+ ],
11357
+ "1.24.64": [
11358
+ {
11359
+ "id": "1005177",
11360
+ "text": "The next version is scoped. The builders' hall becomes a game you are inside rather than a website you navigate: three characters to play as, a city that grows as the project grows, and work claimed as quest chains instead of"
11361
+ }
11350
11362
  ]
11351
11363
  }
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.62'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
84
+ const CORE_VERSION = '1.24.64'; // 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');