@bongos/core 1.24.52 → 1.24.54
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 +88 -43
- package/docs/copy-inventory.md +85 -86
- package/docs/copy-registry.json +117 -126
- package/docs/file-map.md +1 -1
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-readings.json +258 -293
- package/modules/discord/webhook.js +28 -6
- package/modules/hall-ui/public/controls.css +96 -0
- package/modules/hall-ui/public/dom-utils.js +64 -3
- package/modules/hall-ui/public/genesis-home.js +1 -1
- package/modules/hall-ui/public/goal-forest-render.js +1 -1
- package/modules/hall-ui/public/goals.css +2 -8
- package/modules/hall-ui/public/idea.js +1 -1
- package/modules/hall-ui/public/record.css +3 -20
- package/modules/hall-ui/public/roster.css +0 -2
- package/modules/hall-ui/public/style.css +14 -26
- package/modules/hall-ui/public/task.js +8 -10
- package/modules/hall-ui/public/watch.css +1 -5
- package/modules/hall-ui/specimens/components.css +26 -0
- package/modules/hall-ui/specimens/components.html +115 -0
- package/modules/hall-ui/specimens/components.js +27 -0
- package/modules/hall-ui/specimens/components.states.json +35 -0
- 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/discord_board_broadcast.mjs +23 -10
- package/tests/docs_openapi_route.mjs +2 -1
- package/tests/goal_forest_page.mjs +2 -1
- package/tests/hall_audit.mjs +2 -1
- package/tests/hall_record_world.mjs +10 -6
- package/tests/hall_roster_world.mjs +1 -1
- package/tests/hall_status_pill.mjs +251 -0
- package/tests/helpers.mjs +2 -0
- package/tests/idea_descent_surface.mjs +2 -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
|
};
|
|
@@ -321,3 +321,99 @@ select option { background: var(--bg-card); color: var(--ink); }
|
|
|
321
321
|
gap: var(--ds-space-xs);
|
|
322
322
|
margin-top: var(--ds-space-md);
|
|
323
323
|
}
|
|
324
|
+
|
|
325
|
+
/* ---------- the status pill (task 1005086) ----------
|
|
326
|
+
THE HALL'S ONE STATUS PILL. DESIGN.md "The hall": a status pill is the chip's
|
|
327
|
+
shape (a 26px full pill, lowercase) on a status -weak fill with the status
|
|
328
|
+
colour as its text. Six tones, from the hall's fixed verification vocabulary
|
|
329
|
+
and the one warm accent:
|
|
330
|
+
--ok done, passed, live --ok on --ok-weak
|
|
331
|
+
--warn waiting, paused, at risk --warn on --warn-weak
|
|
332
|
+
--danger failed, refused, removed --danger on --danger-weak
|
|
333
|
+
--info moving, open, in hand --info on --info-weak
|
|
334
|
+
--neutral not started, closed, quiet (the base, no modifier needed)
|
|
335
|
+
--accent yours, the current thing --accent-ink on --accent-tint
|
|
336
|
+
A task's status picks its tone in ONE place, dom-utils.js STATUS_TONE
|
|
337
|
+
(OTB.statusPill writes the markup), so it reads the same on every page.
|
|
338
|
+
A status is always a word as well as a colour.
|
|
339
|
+
|
|
340
|
+
THE NAMES PAGES ALREADY WRITE join the one rule, as the buttons did in task
|
|
341
|
+
1005084: .rec-pill (the records), .fact-pill (the rows), .gov-pill,
|
|
342
|
+
.gate-pill and .badge (its aliases), with their tone modifiers mapped below.
|
|
343
|
+
Every colour is a status token, a -weak fill mixed into --bg-card, or the
|
|
344
|
+
accent pair; tests/hall_status_pill.mjs measures every tone in both modes. */
|
|
345
|
+
.status-pill, .rec-pill, .fact-pill, .gov-pill, .gate-pill, .badge {
|
|
346
|
+
display: inline-flex;
|
|
347
|
+
align-items: center;
|
|
348
|
+
box-sizing: border-box;
|
|
349
|
+
min-height: 26px;
|
|
350
|
+
padding: 0 var(--ds-space-xs);
|
|
351
|
+
font-family: var(--font-body);
|
|
352
|
+
font-size: var(--ds-text-xs-plus);
|
|
353
|
+
font-weight: 500;
|
|
354
|
+
line-height: 1.2;
|
|
355
|
+
letter-spacing: 0;
|
|
356
|
+
text-transform: lowercase;
|
|
357
|
+
white-space: nowrap;
|
|
358
|
+
color: var(--ink-soft);
|
|
359
|
+
background: color-mix(in srgb, var(--ink) 6%, var(--bg-card));
|
|
360
|
+
border: var(--line) solid transparent;
|
|
361
|
+
border-radius: var(--radius-pill);
|
|
362
|
+
}
|
|
363
|
+
.status-pill--ok, .rec-pill--ok, .fact-pill--ok, .fact-pill--achieved {
|
|
364
|
+
color: var(--ok); background: var(--ok-weak);
|
|
365
|
+
}
|
|
366
|
+
.status-pill--warn, .rec-pill--warn, .fact-pill--warn, .fact-pill--blocked, .badge--sev-high {
|
|
367
|
+
color: var(--warn); background: var(--warn-weak);
|
|
368
|
+
}
|
|
369
|
+
.status-pill--danger, .rec-pill--danger, .fact-pill--danger, .badge--sev-critical {
|
|
370
|
+
color: var(--danger); background: var(--danger-weak);
|
|
371
|
+
}
|
|
372
|
+
.status-pill--info, .rec-pill--info, .fact-pill--info, .fact-pill--open, .badge--sev-medium {
|
|
373
|
+
color: var(--info); background: var(--info-weak);
|
|
374
|
+
}
|
|
375
|
+
.status-pill--neutral, .fact-pill--quiet, .fact-pill--archived, .badge--sev-low, .badge--status {
|
|
376
|
+
color: var(--ink-soft);
|
|
377
|
+
background: color-mix(in srgb, var(--ink) 6%, var(--bg-card));
|
|
378
|
+
}
|
|
379
|
+
.status-pill--accent {
|
|
380
|
+
color: var(--accent-ink); background: var(--accent-tint);
|
|
381
|
+
}
|
|
382
|
+
/* the row size, for a pill inside a dense line (a grade axis, a table cell) */
|
|
383
|
+
.status-pill--sm, .rec-pill--sm { min-height: 22px; padding: 0 9px; font-size: var(--ds-text-2xs-plus); }
|
|
384
|
+
/* a pill that is also a link keeps the pill, and says it is pressable on hover */
|
|
385
|
+
a.status-pill, a.fact-pill { text-decoration: none; }
|
|
386
|
+
a.status-pill:hover, a.fact-pill:hover { text-decoration: underline; text-underline-offset: 3px; }
|
|
387
|
+
|
|
388
|
+
/* ---------- the view toggle (task 1005086) ----------
|
|
389
|
+
ONE segmented control for a VIEW (rows | cards, list | map): not a filter,
|
|
390
|
+
so the pressed side takes the tint the world gives a current thing, never
|
|
391
|
+
the accent fill (a view is not a live door). Moved here from goals.css, its
|
|
392
|
+
first home (task 1003470), so every page that switches a view writes it. */
|
|
393
|
+
.viewtoggle {
|
|
394
|
+
display: inline-flex;
|
|
395
|
+
align-items: center;
|
|
396
|
+
gap: 3px;
|
|
397
|
+
padding: 3px;
|
|
398
|
+
border: var(--line) solid var(--rule-soft);
|
|
399
|
+
border-radius: var(--radius-pill);
|
|
400
|
+
}
|
|
401
|
+
.viewtoggle__btn {
|
|
402
|
+
min-height: 26px;
|
|
403
|
+
min-width: 60px;
|
|
404
|
+
padding: 0 var(--ds-space-sm);
|
|
405
|
+
font: inherit;
|
|
406
|
+
font-size: var(--ds-text-sm);
|
|
407
|
+
text-transform: lowercase;
|
|
408
|
+
color: var(--ink-faint);
|
|
409
|
+
background: none;
|
|
410
|
+
border: 0;
|
|
411
|
+
border-radius: var(--radius-pill);
|
|
412
|
+
cursor: pointer;
|
|
413
|
+
}
|
|
414
|
+
.viewtoggle__btn:hover { color: var(--ink-soft); }
|
|
415
|
+
.viewtoggle__btn[aria-pressed="true"] {
|
|
416
|
+
color: var(--accent-ink);
|
|
417
|
+
background: var(--accent-tint);
|
|
418
|
+
}
|
|
419
|
+
.viewtoggle__btn:focus-visible { border-radius: var(--radius-pill); }
|
|
@@ -93,14 +93,73 @@
|
|
|
93
93
|
// (2200/2600/3200) and whether the prior hide-timer is cleared. A
|
|
94
94
|
// module-level timer backs the clear path; each hall page loads one consuming
|
|
95
95
|
// script, so it is effectively per-page.
|
|
96
|
+
//
|
|
97
|
+
// ONE TOAST, a duration per KIND (task 1005086): a confirmation reads in a
|
|
98
|
+
// glance, an error needs time to be read and acted on. `kind` is info | ok |
|
|
99
|
+
// warn | error; a message that opens like a failure ("Could not …", "Failed …")
|
|
100
|
+
// is an error even when the caller did not say so, so the forty call sites that
|
|
101
|
+
// predate the kinds get the error's longer hold without each being edited. An
|
|
102
|
+
// explicit `duration` still wins. Every message goes through plainMessage(), so
|
|
103
|
+
// a server address (a URL, a host, an IP and port) never reaches the screen.
|
|
104
|
+
const TOAST_MS = { info: 3200, ok: 2600, warn: 5000, error: 6500 };
|
|
105
|
+
const FAILURE_OPENING = /^\s*(could ?n[o']t|cannot|can't|failed|error\b|that did not|did not|not (saved|filed|proposed|sent))/i;
|
|
106
|
+
function toastKind(msg, kind) {
|
|
107
|
+
if (kind && TOAST_MS[kind]) return kind;
|
|
108
|
+
return FAILURE_OPENING.test(String(msg || '')) ? 'error' : 'info';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// plainMessage — a sentence safe to show: URLs, host names with a dot-separated
|
|
112
|
+
// domain, IPv4/IPv6 addresses and host:port pairs are replaced by "the server",
|
|
113
|
+
// so a fetch error ("request to https://10.0.0.5:8443/api failed") reads as a
|
|
114
|
+
// plain message. Collapses the doubled words the replacement can leave.
|
|
115
|
+
function plainMessage(msg) {
|
|
116
|
+
return String(msg ?? '')
|
|
117
|
+
.replace(/\b[a-z][a-z0-9+.-]*:\/\/[^\s"'<>)]*[^\s"'<>).,:;!?]/gi, 'the server')
|
|
118
|
+
.replace(/\[[0-9a-f:.]{2,}\](?::\d{2,5})?/gi, 'the server')
|
|
119
|
+
// an unbracketed IPv6: two or more colons, and a '::' or a hex letter (so a clock time is not one)
|
|
120
|
+
.replace(/(?<![\w:])(?=[0-9a-f:]*(?:::|[a-f]))[0-9a-f]{0,4}(?::[0-9a-f]{0,4}){2,7}(?![\w:])/gi, 'the server')
|
|
121
|
+
.replace(/\b\d{1,3}(?:\.\d{1,3}){3}(?::\d{2,5})?\b/g, 'the server')
|
|
122
|
+
.replace(/\b(?:localhost|[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*\.(?:com|net|org|io|dev|app|cloud|internal|local|lan|localhost|test|example|co))(?::\d{2,5})?(?:\/[^\s"'<>)]*)?/gi, 'the server')
|
|
123
|
+
.replace(/\bthe server(?:\s+the server)+/g, 'the server')
|
|
124
|
+
.replace(/\s{2,}/g, ' ')
|
|
125
|
+
.trim();
|
|
126
|
+
}
|
|
127
|
+
|
|
96
128
|
let _toastTimer = null;
|
|
97
|
-
function toast(msg, { duration
|
|
129
|
+
function toast(msg, { duration, clear = false, kind } = {}) {
|
|
98
130
|
const el = $('#toast');
|
|
99
131
|
if (!el) return;
|
|
100
|
-
|
|
132
|
+
const k = toastKind(msg, kind);
|
|
133
|
+
el.textContent = plainMessage(msg);
|
|
134
|
+
el.dataset.kind = k;
|
|
101
135
|
el.dataset.visible = '1';
|
|
102
136
|
if (clear) clearTimeout(_toastTimer);
|
|
103
|
-
_toastTimer = setTimeout(() => { el.dataset.visible = '0'; }, duration);
|
|
137
|
+
_toastTimer = setTimeout(() => { el.dataset.visible = '0'; }, duration || TOAST_MS[k]);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ONE STATUS → ONE TONE (task 1005086). A task's lifecycle status is coloured
|
|
141
|
+
// here and nowhere else, so it reads the same on the task page, a goal's
|
|
142
|
+
// table, Home's genesis stages and an idea's descent. The tones are the
|
|
143
|
+
// hall's verification vocabulary (DESIGN.md "The hall"): shipped and
|
|
144
|
+
// confirmed are done (ok); ready, active and completed are moving (info);
|
|
145
|
+
// blocked is waiting on something (warn); backlog and abandoned are neither
|
|
146
|
+
// (neutral). `backlog` is deliberately NOT amber: not scheduled is not a
|
|
147
|
+
// problem (task.js's note, kept). A status nobody mapped is neutral.
|
|
148
|
+
const STATUS_TONE = Object.freeze({
|
|
149
|
+
backlog: 'neutral', ready: 'info', active: 'info', blocked: 'warn',
|
|
150
|
+
completed: 'info', confirmed: 'ok', shipped: 'ok', abandoned: 'neutral',
|
|
151
|
+
});
|
|
152
|
+
const STATUS_TONES = Object.freeze(['ok', 'warn', 'danger', 'info', 'neutral', 'accent']);
|
|
153
|
+
function statusTone(status) {
|
|
154
|
+
return STATUS_TONE[String(status ?? '').trim().toLowerCase()] || 'neutral';
|
|
155
|
+
}
|
|
156
|
+
// The pill markup every page writes for a status: `.status-pill` + its tone
|
|
157
|
+
// (controls.css). `label` overrides the shown word (the status stays the key);
|
|
158
|
+
// `tone` names a tone directly for a status outside the task lifecycle.
|
|
159
|
+
function statusPill(status, { label, tone, small = false, extraClass = '' } = {}) {
|
|
160
|
+
const t = STATUS_TONES.includes(tone) ? tone : statusTone(status);
|
|
161
|
+
const cls = `status-pill status-pill--${t}${small ? ' status-pill--sm' : ''}${extraClass ? ` ${extraClass}` : ''}`;
|
|
162
|
+
return `<span class="${cls}">${escapeHtml(label ?? status ?? 'unknown')}</span>`;
|
|
104
163
|
}
|
|
105
164
|
|
|
106
165
|
// Feature-module gate (R60, task 1199 / ADR 0062 §4). serve-internal.js injects
|
|
@@ -472,6 +531,8 @@
|
|
|
472
531
|
|
|
473
532
|
const OTB = {
|
|
474
533
|
escapeHtml, sleep, $, fmtNum, num, fmtInt, fmtCompact, fmtDate, rankLabel, toast, moduleOn, disciplines,
|
|
534
|
+
plainMessage, toastKind, TOAST_MS, // task 1005086 — one toast, a duration per kind, no server address
|
|
535
|
+
STATUS_TONE, STATUS_TONES, statusTone, statusPill, // task 1005086 — one status, one tone, one pill
|
|
475
536
|
// task 1003936 — ONE word for the role axis, all six surfaces
|
|
476
537
|
roleAxisLabel, roleLabel, roleWord, roleOptions, roleIsUnset, ROLE_UNSET,
|
|
477
538
|
fillKindFilter, kindLabel, kindIsUnset, KIND_UNSET, // task 1003453 — one Kind filler, all three surfaces
|
|
@@ -187,7 +187,7 @@
|
|
|
187
187
|
? '<ul class="genesis__tasks">' + s.tasks.map(function (t) {
|
|
188
188
|
return '<li class="genesis__task' + (t.finished ? ' is-finished' : '') + '">'
|
|
189
189
|
+ '<a class="genesis__task-t" href="/task/' + encodeURIComponent(t.id) + '">' + esc(t.title) + '</a>'
|
|
190
|
-
+ '
|
|
190
|
+
+ window.OTB.statusPill(t.status, { small: true, extraClass: 'genesis__task-st' }) + '</li>';
|
|
191
191
|
}).join('') + '</ul>'
|
|
192
192
|
: '<p class="genesis__note">No tasks for this stage yet.</p>';
|
|
193
193
|
return '<section class="genesis__now" aria-labelledby="genesis-now-h">'
|
|
@@ -177,7 +177,7 @@
|
|
|
177
177
|
: '<span class="gf-none">—</span>');
|
|
178
178
|
const rowHtml = (r) => `<tr>
|
|
179
179
|
<td><a class="lb-name" href="${taskHref(r.id)}">#${escapeHtml(r.id)} ${escapeHtml(r.title)}</a></td>
|
|
180
|
-
<td>${
|
|
180
|
+
<td>${window.OTB.statusPill(r.status, { small: true })}</td>
|
|
181
181
|
<td>${escapeHtml(r.priority)}</td>
|
|
182
182
|
<td>${depList(r.waits)}</td>
|
|
183
183
|
<td>${depList(r.needed)}</td>
|
|
@@ -283,14 +283,8 @@
|
|
|
283
283
|
oversight select row), never the accent fill: a view is not a live door. */
|
|
284
284
|
.goals-line { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 10px 20px; margin: 0 0 6px; }
|
|
285
285
|
.goals-count-line { margin: 0; font-family: var(--font-mono); font-size: 12.5px; font-variant-numeric: tabular-nums; color: var(--ink-faint); }
|
|
286
|
-
.viewtoggle
|
|
287
|
-
.
|
|
288
|
-
min-height: 26px; min-width: 60px; padding: 0 14px;
|
|
289
|
-
font: inherit; font-size: 13px; text-transform: lowercase; color: var(--ink-faint);
|
|
290
|
-
background: none; border: 0; border-radius: var(--radius-pill); cursor: pointer;
|
|
291
|
-
}
|
|
292
|
-
.viewtoggle__btn:hover { color: var(--ink-soft); }
|
|
293
|
-
.viewtoggle__btn[aria-pressed="true"] { color: var(--accent-ink); background: var(--accent-tint); }
|
|
286
|
+
/* .viewtoggle itself is the hall's shared view toggle since task 1005086
|
|
287
|
+
(controls.css). */
|
|
294
288
|
|
|
295
289
|
/* ============================================================
|
|
296
290
|
The list — task 1002439's grouped rollup, redesigned in task 1003470 (goal
|
|
@@ -338,7 +338,7 @@
|
|
|
338
338
|
const bits = [];
|
|
339
339
|
if (row.relation) bits.push(escapeHtml(RELATION_WORD[row.relation] || row.relation));
|
|
340
340
|
else if (row.depth === 0) bits.push('became work');
|
|
341
|
-
if (row.status) bits.push(
|
|
341
|
+
if (row.status) bits.push(window.OTB.statusPill(row.status, { small: true }));
|
|
342
342
|
// A version that is not the idea's own is the interesting case, not an
|
|
343
343
|
// anomaly to hide: the descent crosses version boundaries deliberately
|
|
344
344
|
// (ADR 0312) and the read carries version_id so a surface can SAY so.
|
|
@@ -64,26 +64,9 @@
|
|
|
64
64
|
sanctioned exception to the Fifteen Rule (DESIGN.md §Reuse) — a failed grade
|
|
65
65
|
must not be salmon on a page whose ship button is also salmon — and its values
|
|
66
66
|
are measured by tests/hall_tokens.mjs, never hand-tuned. */
|
|
67
|
-
.rec-pill
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
min-height: 26px;
|
|
71
|
-
padding: 0 13px;
|
|
72
|
-
border: var(--line) solid var(--rule);
|
|
73
|
-
border-radius: var(--radius-pill);
|
|
74
|
-
background: none;
|
|
75
|
-
font-family: var(--font-body);
|
|
76
|
-
font-size: 12.5px;
|
|
77
|
-
font-weight: 500;
|
|
78
|
-
letter-spacing: 0;
|
|
79
|
-
text-transform: lowercase;
|
|
80
|
-
color: var(--ink-soft);
|
|
81
|
-
}
|
|
82
|
-
.rec-pill--ok { color: var(--ok); background: var(--ok-weak); border-color: color-mix(in srgb, var(--ok) 42%, transparent); }
|
|
83
|
-
.rec-pill--warn { color: var(--warn); background: var(--warn-weak); border-color: color-mix(in srgb, var(--warn) 42%, transparent); }
|
|
84
|
-
.rec-pill--danger { color: var(--danger); background: var(--danger-weak); border-color: color-mix(in srgb, var(--danger) 42%, transparent); }
|
|
85
|
-
.rec-pill--info { color: var(--info); background: var(--info-weak); border-color: color-mix(in srgb, var(--info) 42%, transparent); }
|
|
86
|
-
.rec-pill--sm { min-height: 22px; padding: 0 9px; font-size: 11.5px; }
|
|
67
|
+
/* .rec-pill is the hall's ONE status pill since task 1005086 (controls.css
|
|
68
|
+
.status-pill, with .rec-pill and its --ok/--warn/--danger/--info/--sm joined
|
|
69
|
+
to it); the tone a lifecycle status takes is dom-utils.js STATUS_TONE. */
|
|
87
70
|
|
|
88
71
|
.rec-title {
|
|
89
72
|
margin: 0 0 18px;
|
|
@@ -102,6 +102,4 @@
|
|
|
102
102
|
others carry a control; the foot line under the table says why. */
|
|
103
103
|
.roster-warn { color: var(--danger); font-weight: 500; }
|
|
104
104
|
.roster-self { color: var(--ink-faint); }
|
|
105
|
-
.badge--sev-medium { color: var(--info); }
|
|
106
|
-
.badge--status { color: var(--ink-faint); }
|
|
107
105
|
.roster-foot { margin: 14px 0 0; font-size: 12.5px; color: var(--ink-faint); max-width: var(--measure); }
|
|
@@ -797,7 +797,8 @@ html[data-theme="dark"] .gplate__light { display: none; }
|
|
|
797
797
|
}
|
|
798
798
|
.gplate--sm { width: 56px; height: 56px; }
|
|
799
799
|
.genesis__grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr); gap: 24px; }
|
|
800
|
-
.genesis__stage-st
|
|
800
|
+
.genesis__stage-st { flex: none; font-size: 11.5px; letter-spacing: 0.04em; color: var(--ink-faint); }
|
|
801
|
+
.genesis__task-st { flex: none; } /* the shared status pill (task 1005086) */
|
|
801
802
|
.genesis__stage.is-now .genesis__stage-st { color: var(--accent-ink); }
|
|
802
803
|
.genesis__stage.is-later { opacity: 0.6; }
|
|
803
804
|
.genesis__now-h { margin: 0 0 4px; font-size: 15px; font-weight: 500; }
|
|
@@ -2102,10 +2103,13 @@ html.pb-editing main { padding-bottom: 180px; }
|
|
|
2102
2103
|
.scroll primary — the default surface for a section
|
|
2103
2104
|
.scroll--quiet secondary — a card INSIDE a card, or supporting detail
|
|
2104
2105
|
.scroll--bare none — layout-only, for stat rows and grids */
|
|
2105
|
-
.
|
|
2106
|
+
/* THE PANEL (DESIGN.md "The hall"): the card ground, the panel corner, no
|
|
2107
|
+
shadow, never nested in another. .panel is its plain name (task 1005086);
|
|
2108
|
+
.scroll is the name every page already writes. */
|
|
2109
|
+
.scroll, .panel {
|
|
2106
2110
|
background: var(--bg-card);
|
|
2107
2111
|
border: 1px solid transparent;
|
|
2108
|
-
border-radius: var(--radius);
|
|
2112
|
+
border-radius: var(--ds-radius-panel);
|
|
2109
2113
|
padding: var(--sp-md);
|
|
2110
2114
|
margin: 0 0 var(--sp-md);
|
|
2111
2115
|
/* anchor jumps must clear the 56px sticky top bar */
|
|
@@ -3751,6 +3755,10 @@ html[data-door="out"] #hall-main { width: 100%; }
|
|
|
3751
3755
|
max-width: min(90vw, 480px);
|
|
3752
3756
|
}
|
|
3753
3757
|
.toast[data-visible="1"] { opacity: 1; visibility: visible; transform: translate(-50%, 0); }
|
|
3758
|
+
/* An error toast (task 1005086: OTB.toast's kind, set on data-kind) is the status
|
|
3759
|
+
red with the card's ink, the pair the danger button measures; it also holds
|
|
3760
|
+
longest (dom-utils.js TOAST_MS), so there is time to read what went wrong. */
|
|
3761
|
+
.toast[data-kind="error"] { background: var(--danger); color: var(--bg-card); }
|
|
3754
3762
|
|
|
3755
3763
|
#copy-toast {
|
|
3756
3764
|
position: fixed;
|
|
@@ -4489,29 +4497,9 @@ html.palette-open { overflow: hidden; }
|
|
|
4489
4497
|
mean editing the JS row builders, and a class name is not what was wrong —
|
|
4490
4498
|
the duplicated, hand-rolled label voice was. Their own state modifiers stay
|
|
4491
4499
|
in their own sheets. */
|
|
4492
|
-
|
|
4493
|
-
.
|
|
4494
|
-
.
|
|
4495
|
-
.badge {
|
|
4496
|
-
display: inline-flex;
|
|
4497
|
-
align-items: center;
|
|
4498
|
-
min-height: 24px;
|
|
4499
|
-
padding: 0 11px;
|
|
4500
|
-
border: var(--line) solid currentColor;
|
|
4501
|
-
border-radius: var(--radius-pill);
|
|
4502
|
-
background: none;
|
|
4503
|
-
font-family: var(--label-face);
|
|
4504
|
-
font-size: 10.5px;
|
|
4505
|
-
letter-spacing: var(--label-space);
|
|
4506
|
-
text-transform: var(--label-case);
|
|
4507
|
-
color: var(--ink-faint);
|
|
4508
|
-
white-space: nowrap;
|
|
4509
|
-
}
|
|
4510
|
-
.fact-pill--open, .fact-pill--info { color: var(--info); }
|
|
4511
|
-
.fact-pill--blocked, .fact-pill--warn { color: var(--warn); }
|
|
4512
|
-
.fact-pill--achieved, .fact-pill--ok { color: var(--ok); }
|
|
4513
|
-
.fact-pill--danger { color: var(--danger); }
|
|
4514
|
-
.fact-pill--archived, .fact-pill--quiet { color: var(--ink-faint); }
|
|
4500
|
+
/* Since task 1005086 they are not only one another but the hall's ONE status
|
|
4501
|
+
pill: controls.css .status-pill, with these names and their tone modifiers
|
|
4502
|
+
joined to it. */
|
|
4515
4503
|
|
|
4516
4504
|
/* A scope, not a state: what the record is filed under. Mono because it is a
|
|
4517
4505
|
machine's word (a module key, a version id), --radius-sm because a pill would
|
|
@@ -254,15 +254,13 @@
|
|
|
254
254
|
// conflating them made every unscheduled task read as a problem. Backlog takes
|
|
255
255
|
// the neutral pill; the lifecycle rail's first beat carries the distinction in
|
|
256
256
|
// words ("not ready to be claimed yet" vs "waiting on another task").
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
function statusPill(status,
|
|
263
|
-
|
|
264
|
-
const tone = PILL_TONE[s];
|
|
265
|
-
return `<span class="rec-pill${tone ? ` rec-pill--${tone}` : ''}${extraClass ? ` ${extraClass}` : ''}">${escapeHtml(s)}</span>`;
|
|
257
|
+
//
|
|
258
|
+
// The map itself moved to dom-utils.js STATUS_TONE (task 1005086), so every page
|
|
259
|
+
// that shows a task's status colours it the same; the reasoning above is why it
|
|
260
|
+
// reads as it does. A word that is not a lifecycle status (the not-found and
|
|
261
|
+
// feed-down records) names its tone directly.
|
|
262
|
+
function statusPill(status, { tone, small } = {}) {
|
|
263
|
+
return window.OTB.statusPill(String(status || 'unknown'), { tone, small });
|
|
266
264
|
}
|
|
267
265
|
|
|
268
266
|
// `removable` is passed ONLY for "Blocked by" (task 1002858), and the asymmetry
|
|
@@ -274,7 +272,7 @@
|
|
|
274
272
|
// task and remove it there.
|
|
275
273
|
function depList(kindWord, items, { removable = false } = {}) {
|
|
276
274
|
const rows = items.map((d) => {
|
|
277
|
-
const status = d.status ? statusPill(d.status,
|
|
275
|
+
const status = d.status ? statusPill(d.status, { small: true }) : '';
|
|
278
276
|
const cut = removable
|
|
279
277
|
? `<button class="rec-dep__cut" type="button" data-cut-dep="${escapeHtml(String(d.id))}" title="Stop waiting on task ${escapeHtml(String(d.id))}" aria-label="Stop waiting on task ${escapeHtml(String(d.id))}">×</button>`
|
|
280
278
|
: '';
|
|
@@ -8,11 +8,7 @@
|
|
|
8
8
|
/* Severity / status pills. Base: the shell's .fact-pill (task 1003307). A
|
|
9
9
|
severity is a state, so it is a hairline in its own colour rather than a
|
|
10
10
|
filled chip — four filled severities in one row was the loudest thing here. */
|
|
11
|
-
|
|
12
|
-
.badge--sev-high { color: var(--warn); }
|
|
13
|
-
.badge--sev-medium { color: var(--info); }
|
|
14
|
-
.badge--sev-low { color: var(--ink-faint); }
|
|
15
|
-
.badge--status { color: var(--ink-faint); }
|
|
11
|
+
/* the severity and status badges take the shared pill's tones (controls.css, task 1005086) */
|
|
16
12
|
|
|
17
13
|
/* Numeric cells in a kit ledger read right-aligned and tabular; a cell that
|
|
18
14
|
warns takes the danger ink. */
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/* modules/hall-ui/specimens/components.css — the specimen's own LAYOUT only
|
|
2
|
+
(task 1005086). Every component on the page is drawn by the hall's shared
|
|
3
|
+
sheets; this lays them out in sections and stops the live toast and the
|
|
4
|
+
dialog from floating, so each sits at rest where it can be looked at. */
|
|
5
|
+
body[data-page="components"] { margin: 0; color: var(--ink); background: var(--bg); font-family: var(--font-body); }
|
|
6
|
+
.spec-head, .spec-main { box-sizing: border-box; max-width: 980px; margin: 0 auto; padding: 0 var(--ds-space-md); }
|
|
7
|
+
.spec-head { padding-top: var(--ds-space-sm); padding-bottom: var(--ds-space-hair); }
|
|
8
|
+
.spec-kicker { display: inline-block; margin: 0 var(--ds-space-xs) 0 0; vertical-align: middle; font-family: var(--label-face); font-size: var(--label-size); font-weight: 400; letter-spacing: var(--label-space); text-transform: var(--label-case); color: var(--ink-faint); }
|
|
9
|
+
.spec-title { margin: 0; font-family: var(--font-display); font-size: var(--ds-text-title); font-weight: 300; }
|
|
10
|
+
.spec-lede { margin: 0 0 var(--ds-space-md); max-width: 60ch; font-size: var(--ds-text-md); color: var(--ink-soft); }
|
|
11
|
+
.spec-sub { margin: var(--ds-space-md) 0 var(--ds-space-xs); font-size: var(--ds-text-sm); font-weight: 500; color: var(--ink-soft); }
|
|
12
|
+
.spec-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ds-space-xs); margin: var(--ds-space-xs) 0; }
|
|
13
|
+
.spec-note { margin: 0; font-size: var(--ds-text-sm); color: var(--ink-soft); }
|
|
14
|
+
.spec-tile { margin: var(--ds-space-sm) 0 0; }
|
|
15
|
+
.spec-field { display: flex; flex-direction: column; gap: var(--ds-space-hair); max-width: 360px; margin-top: var(--ds-space-sm); font-size: var(--ds-text-sm); color: var(--ink-soft); }
|
|
16
|
+
.spec-field input { width: 100%; }
|
|
17
|
+
.spec-toasts { display: flex; flex-direction: column; align-items: flex-start; gap: var(--ds-space-xs); }
|
|
18
|
+
.spec-toast.toast { position: static; transform: none; max-width: 100%; box-sizing: border-box; }
|
|
19
|
+
.spec-toast .spec-note { color: inherit; }
|
|
20
|
+
.spec-dialog { padding: var(--ds-space-sm); border-radius: var(--ds-radius-tile); background: var(--scrim); }
|
|
21
|
+
.spec-dialog .hall-dialog { position: static; width: 100%; max-width: 480px; margin: 0 auto; }
|
|
22
|
+
@media (max-width: 760px) {
|
|
23
|
+
.spec-head, .spec-main { padding: 0 var(--ds-space-xs); }
|
|
24
|
+
.spec-head { padding-top: var(--ds-space-sm); }
|
|
25
|
+
.spec-main .scroll { padding: var(--ds-space-sm); }
|
|
26
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Hall components</title>
|
|
7
|
+
<!-- THE HALL'S COMPONENTS, EACH IN EACH STATE (task 1005086, goal 1000138).
|
|
8
|
+
A specimen, not a hall page: the real server never serves this folder. It
|
|
9
|
+
loads the hall's own sheets and scripts by their module path, so what it
|
|
10
|
+
shows is what every hall page draws, and the ui-design kit renders it in
|
|
11
|
+
both modes at every width:
|
|
12
|
+
node modules/ui-design/kit/render.js --states modules/hall-ui/specimens/components.states.json
|
|
13
|
+
The public design-system reference (task 1005083) is meant to pick this up.
|
|
14
|
+
Nothing here is styled for itself except the specimen's own layout
|
|
15
|
+
(components.css): every component is the shared rule. -->
|
|
16
|
+
<link rel="stylesheet" href="/modules/hall-ui/public/style.css">
|
|
17
|
+
<link rel="stylesheet" href="components.css">
|
|
18
|
+
</head>
|
|
19
|
+
<body data-page="components">
|
|
20
|
+
<header class="spec-head">
|
|
21
|
+
<h1 class="spec-title"><span class="spec-kicker">The hall</span> Components</h1>
|
|
22
|
+
</header>
|
|
23
|
+
<main class="spec-main">
|
|
24
|
+
<p class="spec-lede">Each shared component in each of its tones and states. The page is drawn by the same sheets every hall page loads.</p>
|
|
25
|
+
|
|
26
|
+
<section class="scroll spec-sec" aria-labelledby="sp-pill">
|
|
27
|
+
<h2 class="scroll__h" id="sp-pill">Status pill</h2>
|
|
28
|
+
<p class="scroll__lede">Six tones. A status is always a word as well as a colour.</p>
|
|
29
|
+
<div class="spec-row" data-spec="tones">
|
|
30
|
+
<span class="status-pill status-pill--ok">ok</span>
|
|
31
|
+
<span class="status-pill status-pill--warn">warn</span>
|
|
32
|
+
<span class="status-pill status-pill--danger">danger</span>
|
|
33
|
+
<span class="status-pill status-pill--info">info</span>
|
|
34
|
+
<span class="status-pill status-pill--neutral">neutral</span>
|
|
35
|
+
<span class="status-pill status-pill--accent">accent</span>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="spec-row" data-spec="small">
|
|
38
|
+
<span class="status-pill status-pill--ok status-pill--sm">ok</span>
|
|
39
|
+
<span class="status-pill status-pill--warn status-pill--sm">warn</span>
|
|
40
|
+
<span class="status-pill status-pill--danger status-pill--sm">danger</span>
|
|
41
|
+
<span class="status-pill status-pill--info status-pill--sm">info</span>
|
|
42
|
+
<span class="status-pill status-pill--sm">neutral</span>
|
|
43
|
+
</div>
|
|
44
|
+
<h3 class="spec-sub">A task's status, from the one map (OTB.statusPill)</h3>
|
|
45
|
+
<div class="spec-row" id="spec-task-statuses" data-spec="task-statuses"></div>
|
|
46
|
+
</section>
|
|
47
|
+
|
|
48
|
+
<section class="scroll spec-sec" aria-labelledby="sp-panel">
|
|
49
|
+
<h2 class="scroll__h" id="sp-panel">Panel</h2>
|
|
50
|
+
<p class="scroll__lede">The card ground, the panel corner, no shadow. This section is one.</p>
|
|
51
|
+
<div class="scroll scroll--quiet spec-tile"><p class="spec-note">A quiet tile inside a panel recedes by tone.</p></div>
|
|
52
|
+
</section>
|
|
53
|
+
|
|
54
|
+
<section class="scroll spec-sec" aria-labelledby="sp-empty">
|
|
55
|
+
<h2 class="scroll__h" id="sp-empty">Empty state</h2>
|
|
56
|
+
<div class="empty-state"><p>Nothing is waiting for you.</p><a class="empty-state__action" href="#sp-empty">See the whole queue</a></div>
|
|
57
|
+
</section>
|
|
58
|
+
|
|
59
|
+
<section class="scroll spec-sec" aria-labelledby="sp-loading">
|
|
60
|
+
<h2 class="scroll__h" id="sp-loading">Loading</h2>
|
|
61
|
+
<p class="hall-loading" role="status">Reading the queue…</p>
|
|
62
|
+
<p class="spec-row"><span class="hall-loading hall-loading--inline" role="status">Saving…</span></p>
|
|
63
|
+
</section>
|
|
64
|
+
|
|
65
|
+
<section class="scroll spec-sec" aria-labelledby="sp-toast">
|
|
66
|
+
<h2 class="scroll__h" id="sp-toast">Toast</h2>
|
|
67
|
+
<p class="scroll__lede">One toast; how long it stays depends on its kind, and an error never shows a server address.</p>
|
|
68
|
+
<div class="spec-toasts" id="spec-toasts"></div>
|
|
69
|
+
</section>
|
|
70
|
+
|
|
71
|
+
<section class="scroll spec-sec" aria-labelledby="sp-tabs">
|
|
72
|
+
<h2 class="scroll__h" id="sp-tabs">Tabs and the view toggle</h2>
|
|
73
|
+
<div class="kit-tabs" role="tablist" aria-label="Specimen tabs">
|
|
74
|
+
<button type="button" class="setup__tab is-active" role="tab" aria-selected="true">ready <span class="count-pill">4</span></button>
|
|
75
|
+
<button type="button" class="setup__tab" role="tab" aria-selected="false">active</button>
|
|
76
|
+
<button type="button" class="setup__tab" role="tab" aria-selected="false">done</button>
|
|
77
|
+
</div>
|
|
78
|
+
<div class="viewtoggle" role="group" aria-label="View">
|
|
79
|
+
<button type="button" class="viewtoggle__btn" aria-pressed="true">rows</button>
|
|
80
|
+
<button type="button" class="viewtoggle__btn" aria-pressed="false">cards</button>
|
|
81
|
+
</div>
|
|
82
|
+
</section>
|
|
83
|
+
|
|
84
|
+
<section class="scroll spec-sec" aria-labelledby="sp-controls">
|
|
85
|
+
<h2 class="scroll__h" id="sp-controls">Buttons and the field</h2>
|
|
86
|
+
<div class="spec-row">
|
|
87
|
+
<button type="button" class="btn">claim it</button>
|
|
88
|
+
<button type="button" class="btn btn--ghost">later</button>
|
|
89
|
+
<button type="button" class="btn btn--danger">release</button>
|
|
90
|
+
<button type="button" class="btn" disabled>claimed</button>
|
|
91
|
+
</div>
|
|
92
|
+
<div class="spec-row">
|
|
93
|
+
<button type="button" class="btn btn--sm">claim</button>
|
|
94
|
+
<button type="button" class="btn btn--ghost btn--sm">release</button>
|
|
95
|
+
<button type="button" class="btn btn--text">read the brief</button>
|
|
96
|
+
</div>
|
|
97
|
+
<label class="spec-field">A field <input type="text" value="A sentence someone typed"></label>
|
|
98
|
+
</section>
|
|
99
|
+
|
|
100
|
+
<section class="scroll spec-sec" aria-labelledby="sp-dialog">
|
|
101
|
+
<h2 class="scroll__h" id="sp-dialog">Dialog</h2>
|
|
102
|
+
<div class="spec-dialog">
|
|
103
|
+
<div class="hall-dialog" role="dialog" aria-modal="false" aria-labelledby="sp-dialog-t">
|
|
104
|
+
<div class="hall-dialog__head"><h3 class="hall-dialog__title" id="sp-dialog-t">Release task 1005086?</h3></div>
|
|
105
|
+
<div class="hall-dialog__body"><p>It returns to the queue and no credits are earned.</p></div>
|
|
106
|
+
<div class="hall-dialog__actions"><button type="button" class="btn btn--ghost">cancel</button><button type="button" class="btn btn--danger">release</button></div>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</section>
|
|
110
|
+
|
|
111
|
+
</main>
|
|
112
|
+
<script src="/modules/hall-ui/public/dom-utils.js"></script>
|
|
113
|
+
<script src="components.js"></script>
|
|
114
|
+
</body>
|
|
115
|
+
</html>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// modules/hall-ui/specimens/components.js — fills the parts of the components
|
|
2
|
+
// specimen (task 1005086) that come from the hall's shared JavaScript, so the
|
|
3
|
+
// specimen shows what the code draws rather than a hand copy of it:
|
|
4
|
+
// - every task status through OTB.statusPill (dom-utils.js, the one map);
|
|
5
|
+
// - one toast per kind, each through OTB.plainMessage, at rest on the page
|
|
6
|
+
// (the live toast is fixed to the bottom of the window and leaves).
|
|
7
|
+
(function () {
|
|
8
|
+
'use strict';
|
|
9
|
+
var OTB = window.OTB;
|
|
10
|
+
var statuses = Object.keys(OTB.STATUS_TONE);
|
|
11
|
+
var row = document.getElementById('spec-task-statuses');
|
|
12
|
+
if (row) row.innerHTML = statuses.map(function (s) { return OTB.statusPill(s); }).join(' ');
|
|
13
|
+
|
|
14
|
+
var toasts = document.getElementById('spec-toasts');
|
|
15
|
+
if (toasts) {
|
|
16
|
+
var said = [
|
|
17
|
+
['info', 'Copied the prompt.'],
|
|
18
|
+
['ok', 'Task 1005086 claimed.'],
|
|
19
|
+
['warn', 'The board is paused while the owner reviews it.'],
|
|
20
|
+
['error', 'Could not reach https://10.0.0.5:8443/api/bongos/claims: try again in a moment.'],
|
|
21
|
+
];
|
|
22
|
+
toasts.innerHTML = said.map(function (k) {
|
|
23
|
+
return '<p class="toast spec-toast" data-visible="1" data-kind="' + k[0] + '">'
|
|
24
|
+
+ OTB.escapeHtml(OTB.plainMessage(k[1])) + ' <span class="spec-note">(' + k[0] + ', ' + (OTB.TOAST_MS[k[0]] / 1000) + 's)</span></p>';
|
|
25
|
+
}).join('');
|
|
26
|
+
}
|
|
27
|
+
})();
|