@bongos/core 1.24.51 → 1.24.53
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.
- package/.bongos-core.json +125 -95
- package/docs/copy-inventory.md +225 -242
- package/docs/copy-registry.json +278 -452
- package/docs/file-map.md +1 -1
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +33 -0
- package/docs/page-readings.json +515 -531
- package/modules/discord/webhook.js +28 -6
- package/modules/hall-ui/public/ai-studio.html +1 -0
- package/modules/hall-ui/public/approval-queue.css +3 -11
- package/modules/hall-ui/public/approval-queue.js +13 -20
- package/modules/hall-ui/public/atlas.html +1 -0
- package/modules/hall-ui/public/blockers.html +1 -0
- package/modules/hall-ui/public/board-room.html +1 -0
- package/modules/hall-ui/public/bulk-toolbar.js +5 -1
- package/modules/hall-ui/public/city.html +1 -0
- package/modules/hall-ui/public/collab.html +1 -0
- package/modules/hall-ui/public/commands.html +1 -0
- package/modules/hall-ui/public/controls.css +100 -0
- package/modules/hall-ui/public/copy-desk.css +7 -11
- package/modules/hall-ui/public/copy-desk.html +17 -16
- package/modules/hall-ui/public/copy-desk.js +13 -9
- package/modules/hall-ui/public/deploy.html +1 -0
- package/modules/hall-ui/public/goal-inbox.js +32 -17
- package/modules/hall-ui/public/goal-map.html +1 -0
- package/modules/hall-ui/public/goals-page.js +7 -1
- package/modules/hall-ui/public/goals.html +1 -0
- package/modules/hall-ui/public/government.html +1 -0
- package/modules/hall-ui/public/government.js +10 -2
- package/modules/hall-ui/public/hall-dialog.js +255 -0
- package/modules/hall-ui/public/hall-kit.js +9 -2
- package/modules/hall-ui/public/idea.html +1 -0
- package/modules/hall-ui/public/ideas.html +1 -0
- package/modules/hall-ui/public/index.html +1 -0
- package/modules/hall-ui/public/market-watch-item.html +2 -1
- package/modules/hall-ui/public/market-watch-lib.js +16 -9
- package/modules/hall-ui/public/market-watch-rules.html +1 -0
- package/modules/hall-ui/public/market-watch.css +2 -3
- package/modules/hall-ui/public/market-watch.html +2 -1
- package/modules/hall-ui/public/modules.html +1 -0
- package/modules/hall-ui/public/money.html +1 -0
- package/modules/hall-ui/public/my-agents.html +1 -0
- package/modules/hall-ui/public/oversight.css +3 -33
- package/modules/hall-ui/public/primer.html +1 -0
- package/modules/hall-ui/public/profile.html +1 -0
- package/modules/hall-ui/public/project-settings.html +1 -0
- package/modules/hall-ui/public/roster.html +1 -0
- package/modules/hall-ui/public/settings-ai-keys.js +5 -1
- package/modules/hall-ui/public/settings-passkeys.js +5 -1
- package/modules/hall-ui/public/settings-sessions.js +10 -2
- package/modules/hall-ui/public/settings.html +1 -0
- package/modules/hall-ui/public/settings.js +5 -1
- package/modules/hall-ui/public/street.html +1 -0
- package/modules/hall-ui/public/studio.css +6 -32
- package/modules/hall-ui/public/studio.html +4 -3
- package/modules/hall-ui/public/studio.js +40 -35
- package/modules/hall-ui/public/style.css +2 -8
- package/modules/hall-ui/public/task.html +1 -0
- package/modules/hall-ui/public/task.js +26 -12
- package/modules/hall-ui/public/thinking.html +1 -0
- package/modules/hall-ui/public/tweak-editor.html +1 -0
- package/modules/hall-ui/public/watch.html +1 -0
- package/modules/hall-ui/public/work.html +1 -0
- package/modules/hall-ui/public/work.js +7 -1
- package/modules/public-landing/public/index.html +11 -8
- package/modules/ui-design/public/brand/og-card.png +0 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-docs/index.html +107 -12
- package/release-notes.json +12 -0
- package/scripts/bongos/gen-brand.js +40 -6
- package/scripts/discord/lib.js +17 -3
- package/scripts/status-mirror-sync.js +11 -7
- package/src/bongos/serve-internal.js +3 -2
- package/src/error-page.js +120 -0
- package/src/link-preview.js +71 -0
- package/src/module-api.js +1 -1
- package/src/platform-server.js +22 -8
- package/tests/brand_surfaces.mjs +213 -0
- package/tests/bulk_toolbar.mjs +1 -1
- package/tests/discord_board_broadcast.mjs +23 -10
- package/tests/docs_openapi_route.mjs +2 -1
- package/tests/hall_approval_queue.mjs +8 -2
- package/tests/hall_audit.mjs +1 -1
- package/tests/hall_city.mjs +1 -1
- package/tests/hall_collab_world.mjs +9 -6
- package/tests/hall_dialog.mjs +401 -0
- package/tests/hall_error_envelope.mjs +1 -1
- package/tests/hall_oversight_world.mjs +9 -7
- package/tests/hall_record_world.mjs +1 -1
- package/tests/hall_street.mjs +1 -1
- package/tests/hall_studio_home.mjs +1 -1
- package/tests/home_attention_cards.mjs +2 -0
- package/tests/kind_axis_unset.mjs +1 -1
- package/tests/market_watch_pages.mjs +3 -1
|
@@ -71,16 +71,36 @@ function capContent(text) {
|
|
|
71
71
|
return String(text).slice(0, 2000);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
// THE BRAND ON A POST (task 1005091). A broadcast goes out as an EMBED, not as bare
|
|
75
|
+
// message text: the same words as the embed's description, with the pack's accent as
|
|
76
|
+
// the embed's colour bar, so every post in a channel reads as the product's. The
|
|
77
|
+
// description cap is 4096 (an embed's own limit). Mentions inside an embed never ring,
|
|
78
|
+
// which is #871's rule anyway; allowed_mentions stays parse:[] belt-and-braces.
|
|
79
|
+
// The pack's accent as Discord's integer colour, or null (no bar) when it is not a hex.
|
|
80
|
+
function brandColor(brand) {
|
|
81
|
+
let accent = '';
|
|
82
|
+
try { accent = ((brand || api.clientBranding()).theme || {}).colors.accent || ''; } catch { accent = ''; }
|
|
83
|
+
const m = /^#([0-9a-f]{6})$/i.exec(String(accent).trim());
|
|
84
|
+
return m ? parseInt(m[1], 16) : null;
|
|
85
|
+
}
|
|
86
|
+
// The body of a broadcast post: one embed carrying the text, coloured with the accent.
|
|
87
|
+
function embedBody(text, brand) {
|
|
88
|
+
const embed = { description: String(text).slice(0, 4096) };
|
|
89
|
+
const color = brandColor(brand);
|
|
90
|
+
if (color !== null) embed.color = color;
|
|
91
|
+
return { embeds: [embed], allowed_mentions: { parse: [] } };
|
|
92
|
+
}
|
|
93
|
+
|
|
74
94
|
// R22 (the board mirror) needs the id of the message it just posted so the close
|
|
75
95
|
// can be edited onto it, so the POST asks for the created message back
|
|
76
96
|
// (`?wait=true` — Discord then answers 200 + the message JSON instead of 204 with
|
|
77
97
|
// no body). The extra field is additive: every existing caller ignores the return.
|
|
78
|
-
async function postCore(channel,
|
|
98
|
+
async function postCore(channel, body) {
|
|
79
99
|
const url = resolveWebhookUrl(channel);
|
|
80
100
|
const res = await fetch(`${url}${url.includes('?') ? '&' : '?'}wait=true`, {
|
|
81
101
|
method: 'POST',
|
|
82
102
|
headers: { 'Content-Type': 'application/json' },
|
|
83
|
-
body: JSON.stringify(
|
|
103
|
+
body: JSON.stringify(body),
|
|
84
104
|
});
|
|
85
105
|
if (!res.ok) {
|
|
86
106
|
const body = await res.text().catch(() => '');
|
|
@@ -96,7 +116,7 @@ async function postCore(channel, text, allowedMentions) {
|
|
|
96
116
|
// attacker-influenceable; allowed_mentions:{parse:[]} renders text verbatim but
|
|
97
117
|
// fires zero notifications. (Discord defaults to parsing mentions when omitted.)
|
|
98
118
|
async function postWebhook(channel, text) {
|
|
99
|
-
return postCore(channel, text
|
|
119
|
+
return postCore(channel, embedBody(text));
|
|
100
120
|
}
|
|
101
121
|
|
|
102
122
|
// postWebhookMentions — the ONE sanctioned exception to #871's blanket mention
|
|
@@ -104,10 +124,12 @@ async function postWebhook(channel, text) {
|
|
|
104
124
|
// notice"). Still parse:[] — nothing typed INTO the content can ever ping — but
|
|
105
125
|
// an explicit `users` allowlist lets exactly the named ids ring. The ids must
|
|
106
126
|
// come from the board-membership resolve (server data), NEVER from message
|
|
107
|
-
// text; the cap of 20 is a sanity bound far above any real board.
|
|
127
|
+
// text; the cap of 20 is a sanity bound far above any real board. It stays
|
|
128
|
+
// MESSAGE TEXT, not an embed: a mention inside an embed never rings, and ringing
|
|
129
|
+
// is this post's whole job (R22's edit rewrites the same text in place).
|
|
108
130
|
async function postWebhookMentions(channel, text, userIds = []) {
|
|
109
131
|
const users = [...new Set(userIds.filter((u) => /^\d+$/.test(String(u))))].slice(0, 20).map(String);
|
|
110
|
-
return postCore(channel, text, { parse: [], users });
|
|
132
|
+
return postCore(channel, { content: capContent(text), allowed_mentions: { parse: [], users } });
|
|
111
133
|
}
|
|
112
134
|
|
|
113
135
|
// editWebhook — rewrite a message this webhook already posted, in place (R22 of
|
|
@@ -149,5 +171,5 @@ function hasWebhook(channel) {
|
|
|
149
171
|
|
|
150
172
|
module.exports = {
|
|
151
173
|
CHANNELS, WEBHOOKS_PATH, loadWebhooks, resolveWebhookUrl, hasWebhook,
|
|
152
|
-
postWebhook, postWebhookMentions, editWebhook,
|
|
174
|
+
postWebhook, postWebhookMentions, editWebhook, embedBody,
|
|
153
175
|
};
|
|
@@ -90,6 +90,7 @@
|
|
|
90
90
|
<script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
|
|
91
91
|
<script src="/builders/craft-mode.js?v=2026-11-05-goals"></script>
|
|
92
92
|
<script src="/builders/shell.js?v=2026-11-07-brand-mark"></script>
|
|
93
|
+
<script src="/builders/hall-dialog.js"></script>
|
|
93
94
|
<script src="/builders/palette.js" defer></script>
|
|
94
95
|
<script src="/builders/tweak-editor-lib.js?v=2026-09-28-tw23"></script>
|
|
95
96
|
<script src="/builders/studio-lib.js?v=2026-10-09-day"></script>
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
--aq-glass: rgba(255, 255, 255, .075);
|
|
27
27
|
--aq-glass-edge: rgba(255, 255, 255, .18);
|
|
28
28
|
--aq-glass-rim: rgba(255, 255, 255, .22);
|
|
29
|
-
--aq-drop: rgba(0, 0, 0, .55);
|
|
30
29
|
--aq-ink: #ffffff;
|
|
31
30
|
--aq-ink-soft: #c0c0c0;
|
|
32
31
|
--aq-ink-faint: #a8a8a8;
|
|
@@ -44,14 +43,8 @@
|
|
|
44
43
|
--aq-rule: rgba(255, 255, 255, .12);
|
|
45
44
|
--aq-face: var(--font-body, Manrope, "Segoe UI", system-ui, sans-serif);
|
|
46
45
|
--aq-mono: var(--font-mono, "JetBrains Mono", ui-monospace, Consolas, monospace);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
z-index: var(--z-modal, 160);
|
|
50
|
-
display: flex;
|
|
51
|
-
align-items: center;
|
|
52
|
-
justify-content: center;
|
|
53
|
-
padding: 24px;
|
|
54
|
-
box-sizing: border-box;
|
|
46
|
+
/* the layer, the placement and the gutter are the shared .hall-scrim's
|
|
47
|
+
(controls.css, task 1005085); only the artboard's tint is its own */
|
|
55
48
|
background: var(--aq-scrim);
|
|
56
49
|
color: var(--aq-ink);
|
|
57
50
|
font-family: var(--aq-face);
|
|
@@ -60,7 +53,6 @@ html[data-theme="dark"] .aq-overlay { --aq-scrim: rgba(4, 5, 9, .42); }
|
|
|
60
53
|
@media (prefers-color-scheme: dark) {
|
|
61
54
|
html:not([data-theme]) .aq-overlay { --aq-scrim: rgba(4, 5, 9, .42); }
|
|
62
55
|
}
|
|
63
|
-
.aq-overlay[hidden] { display: none; }
|
|
64
56
|
html.aq-open { overflow: hidden; }
|
|
65
57
|
|
|
66
58
|
/* FITS THE SCREEN (task 1004381). The artboard's 1180 by 820 is a ceiling, never
|
|
@@ -80,7 +72,7 @@ html.aq-open { overflow: hidden; }
|
|
|
80
72
|
border: 1px solid var(--aq-glass-edge);
|
|
81
73
|
backdrop-filter: blur(30px) saturate(160%);
|
|
82
74
|
-webkit-backdrop-filter: blur(30px) saturate(160%);
|
|
83
|
-
box-shadow: inset 0 1px 0 var(--aq-glass-rim),
|
|
75
|
+
box-shadow: inset 0 1px 0 var(--aq-glass-rim), var(--ds-shadow-pop);
|
|
84
76
|
display: flex;
|
|
85
77
|
flex-direction: column;
|
|
86
78
|
gap: 16px;
|
|
@@ -79,11 +79,16 @@
|
|
|
79
79
|
};
|
|
80
80
|
|
|
81
81
|
const scrim = document.createElement('div');
|
|
82
|
-
|
|
82
|
+
// The shared overlay layer (controls.css .hall-scrim, task 1005085); the
|
|
83
|
+
// glass inside is the artboard's own.
|
|
84
|
+
scrim.className = 'hall-scrim aq-overlay';
|
|
83
85
|
scrim.hidden = true;
|
|
84
86
|
scrim.innerHTML = '<div class="aq-dialog" role="dialog" aria-modal="true" aria-labelledby="aq-title" tabindex="-1"></div>';
|
|
85
87
|
root.appendChild(scrim);
|
|
86
88
|
const dialog = scrim.querySelector('.aq-dialog');
|
|
89
|
+
// The hall's one dialog behaviour (hall-dialog.js, task 1005085): Escape, the
|
|
90
|
+
// focus trap, a click on the scrim, the scroll lock and focus back to the opener.
|
|
91
|
+
const modal = window.OTBDialog.bind(scrim, { panel: dialog, lockClass: 'aq-open', initialFocus: () => dialog, onClose: closed });
|
|
87
92
|
|
|
88
93
|
const current = () => state.queue[state.idx] || null;
|
|
89
94
|
|
|
@@ -296,14 +301,11 @@
|
|
|
296
301
|
// `pageId` (optional) lands on that page's card when it is in the queue, so a
|
|
297
302
|
// row elsewhere that says "needs to be reviewed" opens its own card.
|
|
298
303
|
async function open(pageId, { reload = true } = {}) {
|
|
299
|
-
state.opener = document.activeElement;
|
|
300
304
|
state.open = true;
|
|
301
305
|
state.error = null;
|
|
302
306
|
state.noteOpen = false;
|
|
303
|
-
scrim.hidden = false;
|
|
304
|
-
document.documentElement.classList.add('aq-open');
|
|
305
307
|
paint();
|
|
306
|
-
|
|
308
|
+
modal.open();
|
|
307
309
|
if (reload || !state.loaded) await loadQueue();
|
|
308
310
|
if (typeof pageId === 'string') {
|
|
309
311
|
const at = state.queue.findIndex((q) => q.page_id === pageId);
|
|
@@ -315,17 +317,17 @@
|
|
|
315
317
|
if (first) first.focus();
|
|
316
318
|
}
|
|
317
319
|
|
|
318
|
-
function close() {
|
|
320
|
+
function close() { modal.close(); }
|
|
321
|
+
|
|
322
|
+
// Whatever closed it (the close button, Escape, the scrim), the queue settles here.
|
|
323
|
+
function closed() {
|
|
319
324
|
state.open = false;
|
|
320
|
-
scrim.hidden = true;
|
|
321
|
-
document.documentElement.classList.remove('aq-open');
|
|
322
325
|
// A decided page leaves the queue for good; the next open starts fresh.
|
|
323
326
|
if (state.outcome.size) {
|
|
324
327
|
state.queue = state.queue.filter((q) => !state.outcome.has(q.task_id));
|
|
325
328
|
state.outcome.clear();
|
|
326
329
|
state.idx = 0;
|
|
327
330
|
}
|
|
328
|
-
if (state.opener && state.opener.focus) state.opener.focus();
|
|
329
331
|
}
|
|
330
332
|
|
|
331
333
|
// A switch repaints the dialog, so the pressed pill is focused again: a
|
|
@@ -338,7 +340,6 @@
|
|
|
338
340
|
}
|
|
339
341
|
|
|
340
342
|
scrim.addEventListener('click', (ev) => {
|
|
341
|
-
if (ev.target === scrim) return close();
|
|
342
343
|
const t = ev.target.closest('button');
|
|
343
344
|
if (!t || t.disabled) return undefined;
|
|
344
345
|
if (('close' in t.dataset)) return close();
|
|
@@ -353,17 +354,9 @@
|
|
|
353
354
|
return undefined;
|
|
354
355
|
});
|
|
355
356
|
|
|
357
|
+
// Escape and the Tab trap are the shared dialog's (OTBDialog.bind above).
|
|
356
358
|
scrim.addEventListener('keydown', (ev) => {
|
|
357
|
-
if (ev.key === '
|
|
358
|
-
if (ev.key === 'Enter' && ev.target && ev.target.id === 'aq-note') { ev.preventDefault(); decide('sent'); return; }
|
|
359
|
-
if (ev.key !== 'Tab') return;
|
|
360
|
-
// Keep focus inside the dialog while it is open (aria-modal).
|
|
361
|
-
const f = [...dialog.querySelectorAll('button:not([disabled]), input, [tabindex="0"]')];
|
|
362
|
-
if (!f.length) return;
|
|
363
|
-
const first = f[0];
|
|
364
|
-
const last = f[f.length - 1];
|
|
365
|
-
if (ev.shiftKey && document.activeElement === first) { ev.preventDefault(); last.focus(); }
|
|
366
|
-
else if (!ev.shiftKey && document.activeElement === last) { ev.preventDefault(); first.focus(); }
|
|
359
|
+
if (ev.key === 'Enter' && ev.target && ev.target.id === 'aq-note') { ev.preventDefault(); decide('sent'); }
|
|
367
360
|
});
|
|
368
361
|
|
|
369
362
|
scrim.addEventListener('input', (ev) => {
|
|
@@ -93,6 +93,7 @@
|
|
|
93
93
|
<script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
|
|
94
94
|
<script src="/builders/craft-mode.js?v=2026-11-05-goals"></script>
|
|
95
95
|
<script src="/builders/shell.js?v=2026-11-07-brand-mark"></script>
|
|
96
|
+
<script src="/builders/hall-dialog.js"></script>
|
|
96
97
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
97
98
|
<script src="/builders/palette.js" defer></script>
|
|
98
99
|
<script src="/builders/atlas.js?v=2026-09-02-index" defer></script>
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
<script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
|
|
63
63
|
<script src="/builders/craft-mode.js?v=2026-11-05-goals"></script>
|
|
64
64
|
<script src="/builders/shell.js?v=2026-11-07-brand-mark"></script>
|
|
65
|
+
<script src="/builders/hall-dialog.js"></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
68
|
<!-- the branding pack's currency label, loaded BEFORE blockers.js. -->
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
<script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
|
|
65
65
|
<script src="/builders/craft-mode.js?v=2026-11-05-goals"></script>
|
|
66
66
|
<script src="/builders/shell.js?v=2026-11-07-brand-mark"></script>
|
|
67
|
+
<script src="/builders/hall-dialog.js"></script>
|
|
67
68
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
68
69
|
<script src="/builders/palette.js" defer></script>
|
|
69
70
|
<script src="/builders/hall-kit.js"></script>
|
|
@@ -246,8 +246,12 @@
|
|
|
246
246
|
if (!entries.length) return;
|
|
247
247
|
const label = entries.length === 1 ? `#${entries[0][1]}` : `${entries.length} tasks`;
|
|
248
248
|
const pronoun = entries.length === 1 ? 'It returns' : 'They return';
|
|
249
|
-
if (!window.confirm(`Release ${label}? ${pronoun} to the queue and no ${curLabel()} are earned.`)) return;
|
|
250
249
|
const btn = el('release');
|
|
250
|
+
if (!(await window.OTBDialog.confirm({
|
|
251
|
+
title: `Release ${label}?`,
|
|
252
|
+
body: `${pronoun} to the queue and no ${curLabel()} are earned.`,
|
|
253
|
+
confirmLabel: 'Release', danger: true, trigger: btn,
|
|
254
|
+
}))) return;
|
|
251
255
|
if (btn) { btn.disabled = true; btn.textContent = 'Releasing…'; }
|
|
252
256
|
try {
|
|
253
257
|
const out = await claimAction.releaseMany(entries);
|
|
@@ -109,6 +109,7 @@
|
|
|
109
109
|
<script src="/builders/shell-trail.js?v=2026-11-05-numbers" defer></script>
|
|
110
110
|
<script src="/builders/craft-mode.js?v=2026-11-05-goals" defer></script>
|
|
111
111
|
<script src="/builders/shell.js?v=2026-11-07-brand-mark" defer></script>
|
|
112
|
+
<script src="/builders/hall-dialog.js" defer></script>
|
|
112
113
|
<script src="/builders/palette.js" defer></script>
|
|
113
114
|
<script src="/builders/gov-living.js?v=2026-10-09-positions1" defer></script>
|
|
114
115
|
<script src="/builders/gov-feeds.js?v=2026-10-09-living3" defer></script>
|
|
@@ -163,6 +163,7 @@
|
|
|
163
163
|
<script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
|
|
164
164
|
<script src="/builders/craft-mode.js?v=2026-11-05-goals"></script>
|
|
165
165
|
<script src="/builders/shell.js?v=2026-11-07-brand-mark"></script>
|
|
166
|
+
<script src="/builders/hall-dialog.js"></script>
|
|
166
167
|
<script src="/builders/palette.js" defer></script>
|
|
167
168
|
<script src="/builders/collab-lib.js?v=2026-09-24-archive"></script>
|
|
168
169
|
<script src="/builders/collab.js?v=2026-09-24-archive"></script>
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
<script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
|
|
56
56
|
<script src="/builders/craft-mode.js?v=2026-11-05-goals"></script>
|
|
57
57
|
<script src="/builders/shell.js?v=2026-11-07-brand-mark"></script>
|
|
58
|
+
<script src="/builders/hall-dialog.js"></script>
|
|
58
59
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
59
60
|
<script src="/builders/palette.js" defer></script>
|
|
60
61
|
<script src="/builders/hall-kit.js"></script>
|
|
@@ -221,3 +221,103 @@ select:disabled, input[type="text"]:disabled, input[type="search"]:disabled, tex
|
|
|
221
221
|
cursor: not-allowed;
|
|
222
222
|
}
|
|
223
223
|
select option { background: var(--bg-card); color: var(--ink); }
|
|
224
|
+
|
|
225
|
+
/* ---------- the danger button ----------
|
|
226
|
+
The one fill a confirm spends on an act that cannot be taken back (delete a
|
|
227
|
+
rank, end every session). The status red, with the card's own ground as its
|
|
228
|
+
text: --danger is measured at 4.5:1 against --bg-card in both modes
|
|
229
|
+
(tests/hall_tokens.mjs), so the pair holds whichever way round it is read. */
|
|
230
|
+
.btn--danger {
|
|
231
|
+
color: var(--bg-card);
|
|
232
|
+
background: var(--danger);
|
|
233
|
+
border-color: transparent;
|
|
234
|
+
}
|
|
235
|
+
.btn--danger:hover:not(:disabled) {
|
|
236
|
+
color: var(--bg-card);
|
|
237
|
+
background: color-mix(in srgb, var(--danger) 86%, var(--ink));
|
|
238
|
+
text-decoration: none;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/* ---------- the dialog (task 1005085) ----------
|
|
242
|
+
THE HALL'S ONE DIALOG. hall-dialog.js builds it (OTBDialog.open / .confirm)
|
|
243
|
+
or binds a page's own <dialog class="hall-dialog"> (OTBDialog.bind). It is
|
|
244
|
+
DESIGN.md's panel lifted over the page: bg-card, one hairline, the panel
|
|
245
|
+
corner, and the one shadow the hall spends, on an element that floats over
|
|
246
|
+
the scrim. A native dialog opens with showModal(), so it sits in the top
|
|
247
|
+
layer; the z-index is the ladder's modal step for any reader that does not.
|
|
248
|
+
.hall-scrim is the same layer for a div overlay (the approval queue's and the
|
|
249
|
+
studio's glass): fixed over the page, centred, darkened by --scrim. */
|
|
250
|
+
.hall-dialog {
|
|
251
|
+
z-index: var(--ds-z-modal);
|
|
252
|
+
box-sizing: border-box;
|
|
253
|
+
width: min(480px, calc(100vw - 2 * var(--ds-space-sm)));
|
|
254
|
+
max-height: calc(100dvh - 2 * var(--ds-space-md));
|
|
255
|
+
overflow-y: auto;
|
|
256
|
+
margin: auto;
|
|
257
|
+
padding: var(--ds-space-md);
|
|
258
|
+
color: var(--ink);
|
|
259
|
+
background: var(--bg-card);
|
|
260
|
+
border: var(--line) solid var(--rule);
|
|
261
|
+
border-radius: var(--ds-radius-panel);
|
|
262
|
+
box-shadow: var(--ds-shadow-pop);
|
|
263
|
+
}
|
|
264
|
+
.hall-dialog--wide { width: min(720px, calc(100vw - 2 * var(--ds-space-sm))); }
|
|
265
|
+
.hall-dialog::backdrop { background: var(--scrim); }
|
|
266
|
+
.hall-scrim {
|
|
267
|
+
position: fixed;
|
|
268
|
+
inset: 0;
|
|
269
|
+
z-index: var(--ds-z-modal);
|
|
270
|
+
display: flex;
|
|
271
|
+
align-items: center;
|
|
272
|
+
justify-content: center;
|
|
273
|
+
box-sizing: border-box;
|
|
274
|
+
padding: var(--ds-space-md);
|
|
275
|
+
background: var(--scrim);
|
|
276
|
+
}
|
|
277
|
+
.hall-scrim[hidden] { display: none; }
|
|
278
|
+
.hall-scrim > .hall-dialog { max-height: 100%; }
|
|
279
|
+
.hall-dialog__head {
|
|
280
|
+
display: flex;
|
|
281
|
+
align-items: flex-start;
|
|
282
|
+
gap: var(--ds-space-xs);
|
|
283
|
+
margin: 0 0 var(--ds-space-xs);
|
|
284
|
+
}
|
|
285
|
+
.hall-dialog__title {
|
|
286
|
+
flex: 1;
|
|
287
|
+
margin: 0;
|
|
288
|
+
font-family: var(--font-display);
|
|
289
|
+
font-size: var(--ds-text-3xl);
|
|
290
|
+
font-weight: 500;
|
|
291
|
+
line-height: 1.25;
|
|
292
|
+
color: var(--ink);
|
|
293
|
+
}
|
|
294
|
+
.hall-dialog__x {
|
|
295
|
+
flex: none;
|
|
296
|
+
min-width: 32px;
|
|
297
|
+
min-height: 32px;
|
|
298
|
+
margin: -4px -6px 0 0;
|
|
299
|
+
padding: 0;
|
|
300
|
+
font: inherit;
|
|
301
|
+
font-size: var(--ds-text-4xl);
|
|
302
|
+
line-height: 1;
|
|
303
|
+
color: var(--ink-soft);
|
|
304
|
+
background: transparent;
|
|
305
|
+
border: 0;
|
|
306
|
+
border-radius: var(--radius-pill);
|
|
307
|
+
cursor: pointer;
|
|
308
|
+
}
|
|
309
|
+
.hall-dialog__x:hover { color: var(--ink); }
|
|
310
|
+
.hall-dialog__body p {
|
|
311
|
+
margin: 0 0 var(--ds-space-xs);
|
|
312
|
+
font-size: var(--ds-text-md);
|
|
313
|
+
line-height: 1.5;
|
|
314
|
+
color: var(--ink-soft);
|
|
315
|
+
}
|
|
316
|
+
.hall-dialog__body p:last-child { margin-bottom: 0; }
|
|
317
|
+
.hall-dialog__actions {
|
|
318
|
+
display: flex;
|
|
319
|
+
flex-wrap: wrap;
|
|
320
|
+
justify-content: flex-end;
|
|
321
|
+
gap: var(--ds-space-xs);
|
|
322
|
+
margin-top: var(--ds-space-md);
|
|
323
|
+
}
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
R02 of goal 1000074), rebuilt on the hall's row archetype by task 1003471:
|
|
3
3
|
the counts are the shell's glass .stat-row, every list is a run of
|
|
4
4
|
oversight.css's .ov-row, the filters and the search are the kit's .filters,
|
|
5
|
-
the
|
|
6
|
-
left here is what only this page has: the quoted
|
|
7
|
-
reason as prose, the places a duplicate lives
|
|
5
|
+
the three dialogs are the hall's one dialog (controls.css .hall-dialog,
|
|
6
|
+
task 1005085). What is left here is what only this page has: the quoted
|
|
7
|
+
string as evidence, the reason as prose, the places a duplicate lives.
|
|
8
8
|
|
|
9
9
|
px and tokens throughout, and state is COLOUR — the pill's verification
|
|
10
10
|
vocabulary — never opacity. Opacity dims the panel behind the text as well
|
|
@@ -67,16 +67,12 @@
|
|
|
67
67
|
.cd-find .filter input[type="search"] { width: 100%; }
|
|
68
68
|
.cd-search-count { margin: 0 0 14px; }
|
|
69
69
|
|
|
70
|
-
/* The three dialogs are the
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
white on black). That is why it was a literal; task 1003495 gave the value a
|
|
74
|
-
home in style.css as --scrim instead, so the sheet keeps none. */
|
|
75
|
-
.cd-dialog { width: min(480px, calc(100vw - 32px)); color: var(--ink); }
|
|
76
|
-
.cd-dialog::backdrop { background: var(--scrim); }
|
|
70
|
+
/* The three dialogs are the hall's one dialog (controls.css .hall-dialog: the
|
|
71
|
+
panel, the scrim, the shadow, task 1005085). What stays is the form inside. */
|
|
72
|
+
.cd-dialog .cd-dialog__target { margin: var(--ds-space-xs) 0 0; font-size: var(--ds-text-sm-plus); line-height: 1.5; color: var(--ink-soft); }
|
|
77
73
|
.cd-dialog .ov-form { margin: 14px 0 0; }
|
|
78
74
|
.cd-dialog textarea { resize: vertical; line-height: 1.5; }
|
|
79
|
-
.
|
|
75
|
+
.cd-dialog .cd-dialog__err { min-height: 18px; margin: 8px 0 0; font-size: 13px; line-height: 1.45; color: var(--danger); }
|
|
80
76
|
|
|
81
77
|
/* The placeholder rule, shown only for a sentence that has holes in it (task
|
|
82
78
|
1003114 / R03). It is a CONSTRAINT, not a footnote — losing a hole is the one
|
|
@@ -77,10 +77,10 @@
|
|
|
77
77
|
<!-- The flag composer. A dialog rather than an inline field because the reason is
|
|
78
78
|
the point: a one-line input invites "typo", and the queue is only worth
|
|
79
79
|
reading if the reasons are. A native <dialog> (its own focus trap and
|
|
80
|
-
Escape) dressed as the
|
|
81
|
-
<dialog id="cd-dialog" class="
|
|
80
|
+
Escape) dressed as the hall's one dialog (controls.css .hall-dialog, task 1005085). -->
|
|
81
|
+
<dialog id="cd-dialog" class="hall-dialog cd-dialog" aria-labelledby="cd-dialog-h">
|
|
82
82
|
<form method="dialog" id="cd-form">
|
|
83
|
-
<h3 class="
|
|
83
|
+
<h3 class="hall-dialog__title" id="cd-dialog-h">Flag this copy</h3>
|
|
84
84
|
<p class="cd-dialog__target" id="cd-dialog-target"></p>
|
|
85
85
|
<div class="ov-form">
|
|
86
86
|
<div class="ov-field ov-field--wide">
|
|
@@ -103,17 +103,17 @@
|
|
|
103
103
|
<p class="ov-form__why">The artist reads this instead of guessing. A pointer with no judgement costs them a look and tells them nothing.</p>
|
|
104
104
|
</div>
|
|
105
105
|
<p class="cd-dialog__err" id="cd-dialog-err" role="alert"></p>
|
|
106
|
-
<div class="
|
|
107
|
-
<button type="button" class="btn
|
|
108
|
-
<button type="button" class="btn
|
|
106
|
+
<div class="hall-dialog__actions">
|
|
107
|
+
<button type="button" class="btn btn--ghost" id="cd-cancel">Cancel</button>
|
|
108
|
+
<button type="button" class="btn" id="cd-submit">File the flag</button>
|
|
109
109
|
</div>
|
|
110
110
|
</form>
|
|
111
111
|
</dialog>
|
|
112
112
|
|
|
113
113
|
<!-- Closing a flag asks for a note for the same reason opening one does. -->
|
|
114
|
-
<dialog id="cd-close-dialog" class="
|
|
114
|
+
<dialog id="cd-close-dialog" class="hall-dialog cd-dialog" aria-labelledby="cd-close-h">
|
|
115
115
|
<form method="dialog" id="cd-close-form">
|
|
116
|
-
<h3 class="
|
|
116
|
+
<h3 class="hall-dialog__title" id="cd-close-h">Close this flag</h3>
|
|
117
117
|
<p class="cd-dialog__target" id="cd-close-target"></p>
|
|
118
118
|
<div class="ov-form">
|
|
119
119
|
<div class="ov-field ov-field--wide">
|
|
@@ -135,9 +135,9 @@
|
|
|
135
135
|
<p class="ov-form__why">The next person to notice this sentence reads your note instead of re-litigating it.</p>
|
|
136
136
|
</div>
|
|
137
137
|
<p class="cd-dialog__err" id="cd-close-err" role="alert"></p>
|
|
138
|
-
<div class="
|
|
139
|
-
<button type="button" class="btn
|
|
140
|
-
<button type="button" class="btn
|
|
138
|
+
<div class="hall-dialog__actions">
|
|
139
|
+
<button type="button" class="btn btn--ghost" id="cd-close-cancel">Cancel</button>
|
|
140
|
+
<button type="button" class="btn" id="cd-close-submit">Close it</button>
|
|
141
141
|
</div>
|
|
142
142
|
</form>
|
|
143
143
|
</dialog>
|
|
@@ -147,9 +147,9 @@
|
|
|
147
147
|
wrong, a proposal says this is what it should say instead, and one of them
|
|
148
148
|
creates work on the board. The textarea opens PREFILLED with the current
|
|
149
149
|
wording — an artist edits a sentence, they do not retype it from memory. -->
|
|
150
|
-
<dialog id="cd-propose-dialog" class="
|
|
150
|
+
<dialog id="cd-propose-dialog" class="hall-dialog cd-dialog" aria-labelledby="cd-propose-h">
|
|
151
151
|
<form method="dialog" id="cd-propose-form">
|
|
152
|
-
<h3 class="
|
|
152
|
+
<h3 class="hall-dialog__title" id="cd-propose-h">Propose new wording</h3>
|
|
153
153
|
<p class="cd-dialog__target" id="cd-propose-target"></p>
|
|
154
154
|
<div class="ov-form">
|
|
155
155
|
<div class="ov-field ov-field--wide">
|
|
@@ -168,9 +168,9 @@
|
|
|
168
168
|
<p class="ov-form__why">Nothing changes yet. This files a task carrying your wording; whoever claims it lands the diff, and it ships graded like every other change.</p>
|
|
169
169
|
</div>
|
|
170
170
|
<p class="cd-dialog__err" id="cd-propose-err" role="alert"></p>
|
|
171
|
-
<div class="
|
|
172
|
-
<button type="button" class="btn
|
|
173
|
-
<button type="button" class="btn
|
|
171
|
+
<div class="hall-dialog__actions">
|
|
172
|
+
<button type="button" class="btn btn--ghost" id="cd-propose-cancel">Cancel</button>
|
|
173
|
+
<button type="button" class="btn" id="cd-propose-submit">File the change</button>
|
|
174
174
|
</div>
|
|
175
175
|
</form>
|
|
176
176
|
</dialog>
|
|
@@ -181,6 +181,7 @@
|
|
|
181
181
|
<script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
|
|
182
182
|
<script src="/builders/craft-mode.js?v=2026-11-05-goals"></script>
|
|
183
183
|
<script src="/builders/shell.js?v=2026-11-07-brand-mark"></script>
|
|
184
|
+
<script src="/builders/hall-dialog.js"></script>
|
|
184
185
|
<script src="/builders/palette.js" defer></script>
|
|
185
186
|
<script src="/builders/hall-kit.js"></script>
|
|
186
187
|
<script src="/builders/copy-desk.js?v=2026-09-01-redesign"></script>
|
|
@@ -354,6 +354,10 @@
|
|
|
354
354
|
// the composer
|
|
355
355
|
// ---------------------------------------------------------------------------
|
|
356
356
|
|
|
357
|
+
// The three dialogs are the hall's one dialog (hall-dialog.js, task 1005085):
|
|
358
|
+
// the shared Escape, focus trap, scrim click and focus return.
|
|
359
|
+
const dialog = (sel) => window.OTBDialog.bind($(sel));
|
|
360
|
+
|
|
357
361
|
function openComposer(t) {
|
|
358
362
|
target = t;
|
|
359
363
|
$('#cd-dialog-target').innerHTML = t.string_id
|
|
@@ -361,7 +365,7 @@
|
|
|
361
365
|
: `${pill('', 'whole surface')} <span class="ov-key ov-key--faint">${escapeHtml(t.surface)}</span>`;
|
|
362
366
|
$('#cd-reason').value = '';
|
|
363
367
|
$('#cd-dialog-err').textContent = '';
|
|
364
|
-
|
|
368
|
+
dialog('#cd-dialog').open();
|
|
365
369
|
}
|
|
366
370
|
|
|
367
371
|
async function submitFlag() {
|
|
@@ -387,7 +391,7 @@
|
|
|
387
391
|
$('#cd-dialog-err').textContent = d.what_this_means || d.why || err.message || 'Could not file the flag.';
|
|
388
392
|
return;
|
|
389
393
|
}
|
|
390
|
-
|
|
394
|
+
dialog('#cd-dialog').close();
|
|
391
395
|
toast('Flagged. It is in the queue.');
|
|
392
396
|
await loadQueue();
|
|
393
397
|
if ($('#cd-search').value.trim()) await runSearch();
|
|
@@ -423,7 +427,7 @@
|
|
|
423
427
|
+ `Keep all ${n} — move them if the new sentence needs them elsewhere, but do not remove one: `
|
|
424
428
|
+ 'the values come from the code, and dropping one is a code change.';
|
|
425
429
|
}
|
|
426
|
-
|
|
430
|
+
dialog('#cd-propose-dialog').open();
|
|
427
431
|
}
|
|
428
432
|
|
|
429
433
|
async function submitProposal() {
|
|
@@ -463,7 +467,7 @@
|
|
|
463
467
|
err.textContent = d.what_this_means || d.why || e.message || 'Could not file the change.';
|
|
464
468
|
return;
|
|
465
469
|
}
|
|
466
|
-
|
|
470
|
+
dialog('#cd-propose-dialog').close();
|
|
467
471
|
const task = (res.data && res.data.task) || {};
|
|
468
472
|
toast(res.data && res.data.created === false
|
|
469
473
|
? `Already filed — task #${task.id} carries this exact change.`
|
|
@@ -486,7 +490,7 @@
|
|
|
486
490
|
$('#cd-close-note').value = '';
|
|
487
491
|
$('#cd-close-task').value = '';
|
|
488
492
|
$('#cd-close-err').textContent = '';
|
|
489
|
-
|
|
493
|
+
dialog('#cd-close-dialog').open();
|
|
490
494
|
}
|
|
491
495
|
|
|
492
496
|
async function submitClose() {
|
|
@@ -506,7 +510,7 @@
|
|
|
506
510
|
$('#cd-close-err').textContent = d.what_this_means || d.why || err.message || 'Could not close the flag.';
|
|
507
511
|
return;
|
|
508
512
|
}
|
|
509
|
-
|
|
513
|
+
dialog('#cd-close-dialog').close();
|
|
510
514
|
toast('Closed.');
|
|
511
515
|
await loadQueue();
|
|
512
516
|
}
|
|
@@ -560,11 +564,11 @@
|
|
|
560
564
|
if (closeBtn) openCloser(closeBtn.dataset.flag);
|
|
561
565
|
});
|
|
562
566
|
|
|
563
|
-
$('#cd-cancel').addEventListener('click', () =>
|
|
567
|
+
$('#cd-cancel').addEventListener('click', () => dialog('#cd-dialog').close());
|
|
564
568
|
$('#cd-submit').addEventListener('click', submitFlag);
|
|
565
|
-
$('#cd-propose-cancel').addEventListener('click', () =>
|
|
569
|
+
$('#cd-propose-cancel').addEventListener('click', () => dialog('#cd-propose-dialog').close());
|
|
566
570
|
$('#cd-propose-submit').addEventListener('click', submitProposal);
|
|
567
|
-
$('#cd-close-cancel').addEventListener('click', () =>
|
|
571
|
+
$('#cd-close-cancel').addEventListener('click', () => dialog('#cd-close-dialog').close());
|
|
568
572
|
$('#cd-close-submit').addEventListener('click', submitClose);
|
|
569
573
|
|
|
570
574
|
(async () => {
|
|
@@ -82,6 +82,7 @@
|
|
|
82
82
|
<script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
|
|
83
83
|
<script src="/builders/craft-mode.js?v=2026-11-05-goals"></script>
|
|
84
84
|
<script src="/builders/shell.js?v=2026-11-07-brand-mark"></script>
|
|
85
|
+
<script src="/builders/hall-dialog.js"></script>
|
|
85
86
|
<script src="/builders/palette.js" defer></script>
|
|
86
87
|
<script src="/builders/hall-kit.js"></script>
|
|
87
88
|
<!-- the shared ask card the move is drawn with (task 1004829) -->
|