@lovek4d/kickflip 0.5.3 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTEXT.md +82 -46
- package/SKILL.md +21 -15
- package/bin/doctor.mjs +5 -4
- package/bin/install.mjs +3 -3
- package/bin/kickflip.mjs +12 -12
- package/bin/wait-events.mjs +2 -2
- package/board/agents.mjs +2 -2
- package/board/arch-review.mjs +4 -4
- package/board/bin/board.mjs +179 -92
- package/board/build-script.mjs +2 -1
- package/board/domain.mjs +9 -7
- package/board/finish.mjs +4 -4
- package/board/headless.mjs +9 -5
- package/board/onboarding.mjs +11 -0
- package/board/prototype-base.mjs +1 -0
- package/board/prototype-page.mjs +1 -0
- package/board/public/app-arch.js +1 -1
- package/board/public/app-cards.js +40 -47
- package/board/public/app-header.js +6 -7
- package/board/public/app-lanes.js +19 -45
- package/board/public/app-needs.js +36 -31
- package/board/public/app-snapshot.js +43 -70
- package/board/public/app-threads.js +42 -46
- package/board/public/app.html +9 -4
- package/board/public/app.js +78 -32
- package/board/public/hub-kit.js +62 -2
- package/board/public/hub.css +2 -0
- package/board/public/new-task.js +8 -6
- package/board/public/progress-review.html +46 -0
- package/board/public/progress-review.js +158 -0
- package/board/public/prototype-kit.js +109 -4
- package/board/public/prototype-switcher.js +130 -31
- package/board/public/prototype.html +57 -3
- package/board/public/router.js +7 -6
- package/board/public/shell.js +65 -24
- package/board/public/sidebar.css +6 -0
- package/board/public/sidebar.js +19 -69
- package/board/public/style.css +116 -73
- package/board/public/ui-cards.js +78 -57
- package/board/public/ui-enums.js +2 -2
- package/board/public/ui-header.js +23 -16
- package/board/public/ui-hub.js +19 -5
- package/board/public/ui-links.js +7 -7
- package/board/public/ui-order.js +1 -1
- package/board/public/ui-page-header.js +19 -7
- package/board/public/ui-patch.js +7 -33
- package/board/public/ui-progress-bar.js +28 -0
- package/board/public/ui-questions.js +25 -17
- package/board/public/ui-sidebar.js +31 -23
- package/board/public/ui-snapshot.js +44 -53
- package/board/public/ui-threads.js +57 -28
- package/board/public/ui.js +94 -18
- package/board/public/view-hub-settings.js +12 -58
- package/board/public/view-hub.js +5 -2
- package/board/public/view-notes.js +27 -7
- package/board/public/view-questions.js +90 -68
- package/board/public/view-settings.js +133 -15
- package/board/render-check.mjs +10 -0
- package/board/review.mjs +1 -1
- package/board/server.mjs +20 -19
- package/board/stop.mjs +1 -0
- package/board/store.mjs +29 -18
- package/board/sync.mjs +2 -0
- package/board/tickets.mjs +3 -3
- package/board/transcript.mjs +2 -2
- package/board/worktree.mjs +22 -21
- package/hooks-plugin/hooks/board-data-gate.mjs +9 -6
- package/hooks-plugin/hooks/usage-gate.mjs +20 -3
- package/hub/board-repo.mjs +1 -1
- package/hub/boards.mjs +4 -4
- package/hub/front-door.mjs +2 -2
- package/hub/github.mjs +3 -3
- package/hub/hub.mjs +11 -11
- package/hub/incoming.mjs +2 -2
- package/hub/owner-checks.mjs +1 -1
- package/hub/pr-report.mjs +2 -2
- package/hub/review-send.mjs +1 -1
- package/hub/sessions.mjs +2 -2
- package/hub/share.mjs +1 -1
- package/hub/slack.mjs +2 -2
- package/hub/try.mjs +6 -6
- package/hub/upgrade.mjs +1 -1
- package/hub/usage.mjs +3 -3
- package/hub/views.mjs +1 -1
- package/package.json +1 -1
- package/shared/card-prs.mjs +1 -1
- package/shared/machine-gate.mjs +2 -0
- package/shared/prototype-frame.js +1 -0
- package/shared/tailscale.mjs +1 -1
- package/shared/usage-gate.mjs +1 -1
- package/board/public/ui-phase-bars.js +0 -17
package/board/public/sidebar.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// The sidebar's behaviour on a page with <aside id="sidebar" class="sidebar"> and <div id="sidebarScrim"
|
|
2
|
-
// class="sidebar-scrim">, after ui-
|
|
2
|
+
// class="sidebar-scrim">, after ui-progress-bar.js, ui-page-header.js and ui-sidebar.js (docs/adr/2026-10-29-1639-a-sidebar-of-every-board-replaces-the-hub-home.md).
|
|
3
3
|
// Sidebar.mount({here, data}) paints `data` or the last copy this device stored; the app shell hands it each new copy
|
|
4
4
|
// through setData from its one stream (shell.js). The page header's logo (a.hub) toggles the sidebar at every
|
|
5
5
|
// width, with what waits on the other sites in its accessible label. Below 900px the sidebar is a drawer: the logo or a swipe in from the
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
// logo does, checkDevice reading this device's push subscription again, and keydown and pointerdown taking a framed
|
|
18
18
|
// page's events (view-frame.js), as the document's own
|
|
19
19
|
// A starting board's row (docs/adr/2026-11-04-1914-a-new-board-shows-its-start-steps-and-clone-progress.md): its seconds
|
|
20
|
-
// tick in place each second, and
|
|
21
|
-
//
|
|
20
|
+
// tick in place each second, and its running bar beats on every pulse's one clock (ui-progress-bar.js onePulse). When
|
|
21
|
+
// the board joins, its row fades in, and a repaint during the fade carries it on from where it was.
|
|
22
22
|
(function () {
|
|
23
23
|
const DATA_KEY = 'hub:sidebar';
|
|
24
24
|
// app.html reads it before the first paint, so a collapsed rail never shows for a moment.
|
|
@@ -27,8 +27,7 @@
|
|
|
27
27
|
// A phone has no keys to press, so it never shows the shortcuts sheet.
|
|
28
28
|
const DESKTOP = '(hover: hover) and (pointer: fine)';
|
|
29
29
|
const TWICE_MS = 1000;
|
|
30
|
-
//
|
|
31
|
-
const PULSE_MS = 1400;
|
|
30
|
+
// sidebar.css sb-join's length.
|
|
32
31
|
const JOIN_MS = 2500;
|
|
33
32
|
// paint() sets the bar's duration from this and its delay from switching.started.
|
|
34
33
|
const IDLE_MS = 3000;
|
|
@@ -58,9 +57,8 @@
|
|
|
58
57
|
if (!data) { try { data = JSON.parse(read(DATA_KEY) || 'null'); } catch { data = null; } }
|
|
59
58
|
let device = null;
|
|
60
59
|
let update = null;
|
|
61
|
-
//
|
|
62
|
-
let
|
|
63
|
-
const upgradeShown = () => (restart ? { state: restart.error ? 'updated' : 'restarting', ...restart } : data?.upgrade);
|
|
60
|
+
// The hub's version when this page booted; the row names the version only once it differs.
|
|
61
|
+
let bootVersion = data?.version ?? null;
|
|
64
62
|
let updateHTML = '';
|
|
65
63
|
const rail = matchMedia(RAIL);
|
|
66
64
|
const html = document.documentElement;
|
|
@@ -73,7 +71,7 @@
|
|
|
73
71
|
const logo = [...document.querySelectorAll('.top a.hub')].find(pageLogo);
|
|
74
72
|
if (!logo) return;
|
|
75
73
|
const n = othersWaiting(data, here);
|
|
76
|
-
logo.setAttribute('aria-label', `Boards${update ? ', new version ready' :
|
|
74
|
+
logo.setAttribute('aria-label', `Boards${update ? ', new version ready' : ''}${n ? `, ${n} waiting on other boards` : ''}`);
|
|
77
75
|
}
|
|
78
76
|
// switching: {opened, started, timer} while switch mode shows, opened when it opened the drawer, started on performance.now().
|
|
79
77
|
let switching = null;
|
|
@@ -91,7 +89,6 @@
|
|
|
91
89
|
}
|
|
92
90
|
function paintStarts() {
|
|
93
91
|
const now = Date.now();
|
|
94
|
-
for (const bar of aside.querySelectorAll('.sb-starting .phase-bars i.now')) bar.style.animationDelay = `-${now % PULSE_MS}ms`;
|
|
95
92
|
for (const [route, at] of joined) {
|
|
96
93
|
const row = now - at < JOIN_MS && aside.querySelector(`a.sb-row[href="/${encodeURIComponent(route)}/"]`);
|
|
97
94
|
if (!row) { joined.delete(route); continue; }
|
|
@@ -104,7 +101,10 @@
|
|
|
104
101
|
}
|
|
105
102
|
// A repaint keeps the drawer's scroll, so a new copy doesn't move the list under a finger.
|
|
106
103
|
function paint() {
|
|
107
|
-
html.classList.toggle('sb-update', Boolean(update
|
|
104
|
+
html.classList.toggle('sb-update', Boolean(update));
|
|
105
|
+
const dot = globalThis.BoardUI?.hubDot?.(data);
|
|
106
|
+
html.classList.toggle('sb-hub-busy', dot === 'busy');
|
|
107
|
+
html.classList.toggle('sb-hub-down', dot === 'down');
|
|
108
108
|
if (!data) return;
|
|
109
109
|
const scroll = aside.querySelector('.sb-scroll');
|
|
110
110
|
const top = painted ? scroll?.scrollTop : undefined;
|
|
@@ -116,11 +116,14 @@
|
|
|
116
116
|
bar.style.animationDelay = `${switching.started - performance.now()}ms`;
|
|
117
117
|
bar.style.animationDuration = `${IDLE_MS}ms`;
|
|
118
118
|
}
|
|
119
|
+
// A shared person's sidebar has no update row (ui-sidebar.js).
|
|
119
120
|
const fresh = aside.querySelector('.sb-update');
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
if (fresh) {
|
|
122
|
+
if (held) fresh.replaceWith(held);
|
|
123
|
+
const row = updateRow(update, data.version && data.version !== bootVersion ? data.version : null);
|
|
124
|
+
if (!held || row !== updateHTML) (held || fresh).innerHTML = row;
|
|
125
|
+
updateHTML = row;
|
|
126
|
+
}
|
|
124
127
|
const list = aside.querySelector('.sb-scroll');
|
|
125
128
|
if (list) list.scrollTop = top ?? list.scrollHeight;
|
|
126
129
|
paintStarts();
|
|
@@ -131,6 +134,7 @@
|
|
|
131
134
|
if (!next) return;
|
|
132
135
|
const starting = new Set(startsRunning().map((s) => s.route));
|
|
133
136
|
for (const s of next.sites || []) if (starting.has(s.route)) joined.set(s.route, Date.now());
|
|
137
|
+
bootVersion ??= next.version ?? null;
|
|
134
138
|
data = next;
|
|
135
139
|
write(DATA_KEY, JSON.stringify(data));
|
|
136
140
|
paint();
|
|
@@ -195,64 +199,10 @@
|
|
|
195
199
|
if (!refreshApp.disabled && update !== 'reloading') { setUpdate('reloading'); reload(); }
|
|
196
200
|
return;
|
|
197
201
|
}
|
|
198
|
-
const restartApp = e.target.closest?.('[data-app-restart]');
|
|
199
|
-
if (restartApp) { if (!restartApp.disabled) restartHub(); return; }
|
|
200
|
-
const upgradeApp = e.target.closest?.('[data-app-upgrade]');
|
|
201
|
-
if (upgradeApp) { if (!upgradeApp.disabled) upgrade(upgradeApp); return; }
|
|
202
202
|
if (e.target.closest?.('[data-new-board]')) { newBoard(); return; }
|
|
203
203
|
const failed = e.target.closest?.('[data-failed-start]');
|
|
204
204
|
if (failed) openFailed(failed.dataset.failedStart);
|
|
205
205
|
});
|
|
206
|
-
// Restart asks the hub to restart (POST /_hub/restart-hub), then polls until it answers with other code than it ran (its
|
|
207
|
-
// x-hub-code) and reloads the page (docs/adr/2026-11-05-2200-the-hub-pulls-a-new-version-itself-and-offers-restart.md).
|
|
208
|
-
// Past RESTART_TRIES polls the row goes back to Restart with how to restart by hand.
|
|
209
|
-
const RESTART_TRIES = 120;
|
|
210
|
-
async function restartHub() {
|
|
211
|
-
if (restart) return;
|
|
212
|
-
const version = data.upgrade.version;
|
|
213
|
-
restart = { version };
|
|
214
|
-
paint();
|
|
215
|
-
let before, command;
|
|
216
|
-
try {
|
|
217
|
-
const response = await fetch('/_hub/restart-hub', { method: 'POST', headers: { accept: 'application/json', 'content-type': 'application/json' }, body: '{}' });
|
|
218
|
-
const body = await response.json().catch(() => ({}));
|
|
219
|
-
if (!response.ok) throw new Error(body.error || 'The hub didn’t restart.');
|
|
220
|
-
before = response.headers.get('x-hub-code');
|
|
221
|
-
command = body.command;
|
|
222
|
-
} catch (e) {
|
|
223
|
-
restart = { version, error: e.message || 'The hub didn’t answer.' };
|
|
224
|
-
paint();
|
|
225
|
-
restart = null;
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
for (let i = 0; i < RESTART_TRIES; i++) {
|
|
229
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
230
|
-
try {
|
|
231
|
-
const response = await fetch('/_hub/api/hub', { cache: 'no-store', headers: { accept: 'application/json' } });
|
|
232
|
-
const code = response.headers.get('x-hub-code');
|
|
233
|
-
if (response.ok && code && code !== before) { reload(); return; }
|
|
234
|
-
} catch { /* the hub is down while it restarts */ }
|
|
235
|
-
}
|
|
236
|
-
restart = { version, error: `The hub hasn’t come back. On the host, run ${command}` };
|
|
237
|
-
paint();
|
|
238
|
-
restart = null;
|
|
239
|
-
}
|
|
240
|
-
// Update pulls again after a failed pull (docs/adr/2026-11-05-2200-the-hub-pulls-a-new-version-itself-and-offers-restart.md). The row
|
|
241
|
-
// waits (ui.js waitOn) until the hub answers with what it did, then while that pull runs (ui-sidebar.js
|
|
242
|
-
// updateRow), and a refusal shows as a failed update.
|
|
243
|
-
function upgrade(button) {
|
|
244
|
-
BoardUI.waitOn(button, (async () => {
|
|
245
|
-
let next;
|
|
246
|
-
try {
|
|
247
|
-
const response = await fetch('/_hub/upgrade', { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' });
|
|
248
|
-
const body = await response.json().catch(() => ({}));
|
|
249
|
-
next = response.ok ? body.upgrade : { ...data.upgrade, state: 'failed', error: body.error || 'The hub didn’t answer.' };
|
|
250
|
-
} catch {
|
|
251
|
-
next = { ...data.upgrade, state: 'failed', error: 'The hub didn’t answer.' };
|
|
252
|
-
}
|
|
253
|
-
setData({ ...data, upgrade: next });
|
|
254
|
-
})());
|
|
255
|
-
}
|
|
256
206
|
// A failed start's sheet (docs/adr/2026-11-02-2006-a-failed-start-retries-from-its-sheet.md). Dismiss deletes its Starting
|
|
257
207
|
// record; Retry starts it again, and its row reads starting… until the hub's next copy says otherwise.
|
|
258
208
|
function openFailed(route) {
|
package/board/public/style.css
CHANGED
|
@@ -82,8 +82,13 @@ button.chip { border: 0; }
|
|
|
82
82
|
.status.review { border-color: var(--color-info); background: conic-gradient(var(--color-info) 0 75%, transparent 75% 100%); }
|
|
83
83
|
.sub.agent { color: var(--color-notice); }
|
|
84
84
|
.sub.next { color: var(--color-notice); }
|
|
85
|
-
.card-hold
|
|
85
|
+
.card-hold { color: var(--color-quiet); }
|
|
86
|
+
.card-blocked, .card-action { color: var(--color-notice); }
|
|
86
87
|
.card-ready { color: var(--color-success); }
|
|
88
|
+
.card-staging { color: var(--color-notice); }
|
|
89
|
+
/* The footer status of a stale card, and of one the orchestrator stalled (ui-cards.js timeLine). */
|
|
90
|
+
.card-stale { color: var(--muted); }
|
|
91
|
+
.card-stalled { color: var(--color-danger); }
|
|
87
92
|
.sub.next.idle { color: var(--muted); }
|
|
88
93
|
.sub.refused { color: var(--color-danger); }
|
|
89
94
|
.toast-stack { position: fixed; left: 50%; bottom: calc(var(--app-bottom, 0px) + 12px + env(safe-area-inset-bottom));
|
|
@@ -166,7 +171,10 @@ button.link { background: none; border: 0; color: var(--accent); padding: 8px 12
|
|
|
166
171
|
.wb-counts { position: relative; border: 0; background: none; padding: 4px 0; font: inherit; cursor: help; color: var(--muted); font-weight: 400; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
167
172
|
.wb-tip { display: none; position: absolute; right: 0; top: 100%; z-index: 6; margin-top: 2px; padding: 8px 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--panel); box-shadow: 0 6px 18px rgba(0,0,0,.18); color: var(--text); font-weight: 400; text-align: left; }
|
|
168
173
|
.wb-counts:hover .wb-tip, .wb-counts.open .wb-tip { display: block; }
|
|
169
|
-
.wb-counts .wv-cur { font-weight: 700;
|
|
174
|
+
.wb-counts .wv-cur { font-weight: 700; }
|
|
175
|
+
.wb-counts .wv-review { color: var(--color-info); }
|
|
176
|
+
.wb-counts .wv-progress { color: var(--color-notice); }
|
|
177
|
+
.wb-counts .wv-todo, .wb-counts .wv-done { color: var(--color-quiet); }
|
|
170
178
|
.wb-lane + .wb-lane, .wb-lane + .wb-loose { border-top: 1px solid var(--line); }
|
|
171
179
|
.wb-loosehead:hover { background: var(--band); }
|
|
172
180
|
/* A wave card's step number, inside its id chip, and its ⋯ menu. */
|
|
@@ -186,12 +194,12 @@ button.link { background: none; border: 0; color: var(--accent); padding: 8px 12
|
|
|
186
194
|
.refresh #refresh { padding: 4px; margin-left: -4px; flex: none; }
|
|
187
195
|
.refresh #status { font-size: 12px; min-width: 0; }
|
|
188
196
|
.refresh .late { color: var(--color-notice); font-weight: 600; }
|
|
189
|
-
|
|
190
|
-
.refresh .
|
|
197
|
+
/* The header's time keeps its text; a stopped or offline board turns it and the refresh arrow red, paused usage orange. */
|
|
198
|
+
.refresh .down, .refresh:has(.down) #refresh { color: var(--color-danger); }
|
|
199
|
+
.refresh .hold, .refresh:has(.hold) #refresh { color: var(--color-notice); }
|
|
191
200
|
.lane-github { margin-left: auto; font-size: 12px; font-weight: 400; color: var(--muted); text-align: right; }
|
|
192
201
|
.lane-github .late { color: var(--color-notice); }
|
|
193
202
|
.msg.other { background: transparent; border: 1px solid var(--line); }
|
|
194
|
-
.status.stale { border-color: var(--color-danger); background: conic-gradient(var(--color-danger) 0 75%, transparent 75% 100%); }
|
|
195
203
|
.msg .reply, .msg .small, .rep, .sent { overflow-wrap: anywhere; }
|
|
196
204
|
code { overflow-wrap: anywhere; }
|
|
197
205
|
/* iOS Safari zooms the page when a field under 16px takes focus, which shifts the page width. */
|
|
@@ -328,14 +336,17 @@ main { padding-bottom: calc(110px + env(safe-area-inset-bottom)); }
|
|
|
328
336
|
}
|
|
329
337
|
.working-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--color-notice); margin-right: 5px; vertical-align: 1px; }
|
|
330
338
|
@media (prefers-reduced-motion: no-preference) { .working-dot { animation: pulse 1.4s ease-in-out infinite; } }
|
|
331
|
-
|
|
332
|
-
|
|
339
|
+
/* A thread box's row: What's left? at the left, the eye, fire and attach at the right, then Send, or a pair below
|
|
340
|
+
(docs/adr/2026-11-05-1918-done-shows-only-in-in-review-beside-reply.md). */
|
|
341
|
+
form[data-reply] .row .quick { margin: 0; }
|
|
342
|
+
form[data-reply] .icons { display: inline-flex; flex: none; margin-left: auto; }
|
|
343
|
+
.dz.bare > textarea { padding-bottom: 12px; }
|
|
333
344
|
/* A composer's send buttons (ui.js sendButton), each with its key as a small gray hint where there is a keyboard and
|
|
334
345
|
room. A pair takes a row of two halves; a Todo card's reply box wraps its pair under the row. */
|
|
335
346
|
.send-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin-top: 10px; }
|
|
336
347
|
.send-actions button, [data-sends] button { display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
|
|
337
348
|
.send-actions button { min-height: 44px; }
|
|
338
|
-
form[data-reply] .row { flex-wrap: wrap; }
|
|
349
|
+
form[data-reply] .row { flex-wrap: wrap; gap: 6px; }
|
|
339
350
|
form[data-reply] .row > .send-actions { flex: 0 0 100%; margin-top: 4px; }
|
|
340
351
|
.key-hint { display: none; font-size: 11px; font-weight: 400; color: var(--muted); white-space: nowrap; }
|
|
341
352
|
button.primary .key-hint { color: color-mix(in srgb, var(--on-accent) 62%, transparent); }
|
|
@@ -355,29 +366,18 @@ button.attach:disabled { opacity: .35; }
|
|
|
355
366
|
pixel off its siblings (docs/adr/2026-11-03-1519-a-building-cards-phase-bars-end-its-time-row.md). */
|
|
356
367
|
.card-time .phase-bars { display: inline-flex; gap: 2px; vertical-align: middle; position: relative; top: -1px; }
|
|
357
368
|
.phase-bars i { width: 9px; height: 4px; border-radius: 2px; background: var(--line); }
|
|
358
|
-
/* A strip dims
|
|
359
|
-
(docs/adr/2026-11-03-2024-every-in-progress-card-shows-its-phase-bars.md). app-cards.js
|
|
369
|
+
/* A strip dims while the card waits on something other than a builder; ui-progress-bar.js then writes its running bar
|
|
370
|
+
as done, so it stops blinking (docs/adr/2026-11-03-2024-every-in-progress-card-shows-its-phase-bars.md). app-cards.js
|
|
371
|
+
sweeps bars in --bar-fill. */
|
|
360
372
|
.phase-bars { --bar-fill: var(--color-notice); }
|
|
361
373
|
.phase-bars.dim { --bar-fill: color-mix(in srgb, var(--muted) 55%, transparent); }
|
|
362
374
|
.phase-bars i.done, .phase-bars i.now { background: var(--bar-fill); }
|
|
363
375
|
/* A bar part way, to --p: a starting board's clone (ui-sidebar.js). */
|
|
364
376
|
.phase-bars i.fill { background: linear-gradient(to right, var(--bar-fill) var(--p), var(--line) var(--p)); }
|
|
365
|
-
|
|
366
|
-
@
|
|
367
|
-
/*
|
|
368
|
-
|
|
369
|
-
shorthand stays out so .sub's 12px wins. It sets no width: a button is its own block formatting context, so a full-width one
|
|
370
|
-
drops below the bump icons' float instead of sitting beside it as the div's line box does (board/layout.test.mjs). */
|
|
371
|
-
button.sub { display: block; margin-inline: 0; margin-bottom: 0; padding: 0; min-height: 0; border: 0; border-radius: 0; background: none;
|
|
372
|
-
font-family: inherit; font-weight: inherit; letter-spacing: inherit; line-height: inherit; text-align: left; appearance: none; -webkit-appearance: none; }
|
|
373
|
-
button.sub.agent.stalled { color: var(--color-danger); }
|
|
374
|
-
/* A tap on it calms the line to the in-progress orange, its dot sending a ripple, until the card's next status line (app-cards.js nudge). */
|
|
375
|
-
button.sub.agent.stalled.nudging { color: var(--color-notice); cursor: default; }
|
|
376
|
-
.nudging .working-dot { position: relative; }
|
|
377
|
-
.nudging .working-dot::after { content: ''; position: absolute; inset: -1px; border-radius: 50%; border: 1.5px solid currentColor; opacity: 0; }
|
|
378
|
-
@keyframes nudge-ping { from { transform: scale(1); opacity: .9; } to { transform: scale(2.6); opacity: 0; } }
|
|
379
|
-
@media (prefers-reduced-motion: no-preference) { .nudging .working-dot::after { animation: nudge-ping 1.1s ease-out infinite; } }
|
|
380
|
-
.stalled .working-dot { background: currentColor; }
|
|
377
|
+
/* The pulse dims the bar's own fill, so it pulses in its lane's colour. */
|
|
378
|
+
@keyframes pulse-fill { 50% { background-color: color-mix(in srgb, var(--bar-fill) 35%, transparent); } }
|
|
379
|
+
/* Every pulse beats on one clock (ui-progress-bar.js onePulse), so a card's running bar and its status dot blink together. */
|
|
380
|
+
@media (prefers-reduced-motion: no-preference) { .phase-bars i.now { animation: pulse-fill 1.4s ease-in-out infinite; } }
|
|
381
381
|
.brand { display: grid; grid-template-columns: auto auto minmax(0, 1fr); column-gap: 10px; row-gap: 2px; }
|
|
382
382
|
.brand .hub { grid-row: 1 / span 2; align-self: center; }
|
|
383
383
|
/* The session's working line, "thinking", is the owner's blue with one font, size, weight and pulse on the card and in the thread. */
|
|
@@ -390,26 +390,36 @@ button.sub.agent.stalled.nudging { color: var(--color-notice); cursor: default;
|
|
|
390
390
|
.msg.agent { padding: 0 10px; margin: 4px 0; font-size: 12px; font-weight: 600; overflow-wrap: anywhere; }
|
|
391
391
|
.msg.agent.started { color: var(--color-notice); } .msg.agent.finished { color: var(--color-success); }
|
|
392
392
|
.msg.agent .muted { font-weight: 400; }
|
|
393
|
-
/* A planner's line, dot included, is the owner's blue
|
|
394
|
-
(docs/adr/2026-11-03-1551-planners-and-implementers-are-two-kinds-of-agent.md). */
|
|
395
|
-
.
|
|
393
|
+
/* A planner's line in the thread, dot included, is the owner's blue; its finish stays green
|
|
394
|
+
(docs/adr/2026-11-03-1551-planners-and-implementers-are-two-kinds-of-agent.md). On the card it takes the lane's colour below. */
|
|
395
|
+
.msg.builder-status.planner, .msg.agent.started.planner { color: var(--accent); }
|
|
396
396
|
.planner .working-dot { background: currentColor; }
|
|
397
|
-
/*
|
|
398
|
-
|
|
399
|
-
(docs/adr/2026-11-
|
|
397
|
+
/* A run's status line, dot included, takes its card's lane colour, a planner's too: grey in Todo, the phase-bar orange in
|
|
398
|
+
In Progress, blue in In Review; a Todo card's planner dashes are its grey, the running one pulsing
|
|
399
|
+
(docs/adr/2026-11-06-0145-every-run-takes-its-lanes-colour.md). */
|
|
400
|
+
.sub.agent.in-todo { color: var(--muted); }
|
|
400
401
|
.sub.agent.in-progress { color: var(--color-notice); }
|
|
401
|
-
.sub.agent.in-
|
|
402
|
-
.sub.agent.in-progress .working-dot, .sub.agent.in-
|
|
402
|
+
.sub.agent.in-review { color: var(--color-info); }
|
|
403
|
+
.sub.agent.in-todo .working-dot, .sub.agent.in-progress .working-dot, .sub.agent.in-review .working-dot { background: currentColor; }
|
|
403
404
|
/* docs/adr/2026-11-05-0410-a-card-status-word-comes-from-a-signal-the-board-checks.md: a quiet builder and a request no planner
|
|
404
405
|
has picked up are grey, after the lane colours so they win. */
|
|
405
406
|
.sub.agent.quiet, .sub.asked { color: var(--muted); }
|
|
406
|
-
.
|
|
407
|
-
|
|
408
|
-
|
|
407
|
+
.sub.agent.waiting-gray { color: var(--muted); }
|
|
408
|
+
/* A stale build's status line is grey, its dot still (ui-cards.js builderStatus). */
|
|
409
|
+
.sub.agent.stale { color: var(--muted); }
|
|
410
|
+
.sub.agent.stale .working-dot { animation: none; }
|
|
411
|
+
/* The one class every paused status takes, gray: the card and thread line, the header, the hub banner. */
|
|
412
|
+
.paused { color: var(--muted); font-weight: 600; }
|
|
413
|
+
.phase-bars.in-todo { --bar-fill: var(--muted); }
|
|
409
414
|
.msg.calls { padding: 0 10px; margin: 4px 0; font-size: 12px; overflow-wrap: anywhere; }
|
|
410
415
|
.msg.calls summary { cursor: pointer; color: var(--muted); list-style: none; }
|
|
411
416
|
.msg.calls summary::-webkit-details-marker { display: none; }
|
|
412
417
|
.msg.calls[open] summary .mark-icon { transform: rotate(90deg); }
|
|
418
|
+
.msg.calls.status-fold summary { color: var(--color-notice); overflow-wrap: anywhere; }
|
|
419
|
+
.msg.calls.status-fold.planner summary { color: var(--accent); }
|
|
420
|
+
/* Only a run's finished line is green: the fold's summary mirrors its last line, green once the run has finished, else the
|
|
421
|
+
started and status lines' orange or a planner's blue (docs/adr/2026-11-07-1600-only-a-runs-finished-line-is-green.md). */
|
|
422
|
+
.msg.calls.status-fold summary.finished { color: var(--color-success); }
|
|
413
423
|
.msg.calls ol { margin: 4px 0; padding-left: 23px; }
|
|
414
424
|
.agent-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: currentColor; margin-right: 5px; vertical-align: 1px; }
|
|
415
425
|
/* Every thread status line's leading mark, icon or dot, sits centred in one 18px box, so the text after it always starts
|
|
@@ -426,7 +436,6 @@ button.sub.agent.stalled.nudging { color: var(--color-notice); cursor: default;
|
|
|
426
436
|
.msg .reply { display: flex; flex-wrap: wrap; align-items: center; column-gap: 12px; row-gap: 2px; }
|
|
427
437
|
.msg-foot .foot-status { display: inline-flex; align-items: center; }
|
|
428
438
|
/* The thread's eye and fire are toggles: pressed reads as chosen, and the hint under the box says what Send posts. */
|
|
429
|
-
form[data-reply] .tools { display: inline-flex; flex: none; }
|
|
430
439
|
button.attach[aria-pressed="true"] { color: var(--accent); background: var(--chip); }
|
|
431
440
|
/* INT-30: card line order */
|
|
432
441
|
.line6 { margin-top: 4px; }
|
|
@@ -444,17 +453,25 @@ button.attach[aria-pressed="true"] { color: var(--accent); background: var(--chi
|
|
|
444
453
|
.lane[open] > .list { border-radius: 0 0 10px 10px; }
|
|
445
454
|
.lane > summary .count { color: var(--muted); font-weight: 400; }
|
|
446
455
|
/* Each lane's sort icon and its menu, grey (docs/adr/2026-11-01-1817-colour-says-whos-waiting.md) */
|
|
447
|
-
/*
|
|
448
|
-
.view
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
.shared-view #compose,
|
|
453
|
-
.shared-
|
|
456
|
+
/* A board shared with someone else (docs/adr/2026-11-05-2215-a-board-is-shared-view-only-with-a-tailscale-login.md):
|
|
457
|
+
shell.js marks the page .shared-view, which hides Board settings and the architecture review, and, unless the person
|
|
458
|
+
has Full on this board (.shared-full; docs/adr/2026-11-05-1912-a-shared-person-is-view-or-full.md), the +, every
|
|
459
|
+
composer and a card's buttons, keeping its links (Visit, Notes, Open questions). Merge shows disabled under Full. */
|
|
460
|
+
.shared-view :is(#settingsLink, #archBulb) { display: none !important; }
|
|
461
|
+
.shared-view:not(.shared-full) :is(#compose, [data-composer], .actions button, button[data-confirm]) { display: none !important; }
|
|
462
|
+
.shared-full button[data-confirm="merge"] { opacity: .45; pointer-events: none; }
|
|
454
463
|
.view-only-strip { margin: 8px 0; padding: 8px 12px; border-radius: 10px; background: var(--accent-soft); font-size: 13px; }
|
|
455
464
|
.view-only-strip b { color: var(--accent); }
|
|
456
|
-
|
|
457
|
-
|
|
465
|
+
/* A board out of reach (docs/adr/2026-11-06-0135-a-board-out-of-reach-keeps-its-copy-or-shows-a-landing.md): it reads as before,
|
|
466
|
+
undimmed, and only the buttons that send, ui.js SENDS, are held, the labelled ones saying why; app-header.js stops their taps. */
|
|
467
|
+
.offline :is(form button[type="submit"], .actions button, button[data-confirm], button[data-deck-pick], button[data-review-answer], button[data-prompt-send], button[data-prompt-choice], button[data-quick], button[data-card-bump], button[data-bump], button[data-withdraw], button[data-band-move], button[data-grill], button[data-proto], button[data-visual], button[data-plan], button[data-main-pull], button[data-arch-run], button[data-try], button[data-reopen]) { opacity: .55; cursor: not-allowed; }
|
|
468
|
+
/* With no copy to show, the landing takes the board's place: the kickflip mark and two lines, as hub/front-door.mjs's page has them. */
|
|
469
|
+
.down-landing { min-height: 60vh; display: grid; place-content: center; justify-items: center; gap: 10px; padding: 32px 24px; text-align: center; }
|
|
470
|
+
.down-landing-mark { display: block; margin-bottom: 8px; width: 70px; height: 60px; object-fit: contain; }
|
|
471
|
+
.down-landing-line { margin: 0; font-size: 20px; font-weight: 600; line-height: 1.3; text-wrap: balance; }
|
|
472
|
+
.down-landing-hint { margin: 0; font-size: 14px; color: var(--muted); }
|
|
473
|
+
.view-board.down main > :not(#downLanding), .view-board.down :is(#needsPane, #compose) { display: none; }
|
|
474
|
+
.view-settings.down .settings-body > :not(.down-landing) { display: none; }
|
|
458
475
|
/* INT-28: answer banner */
|
|
459
476
|
.answer-banner { display: flex; align-items: center; gap: 4px; background: var(--accent-soft); border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
|
|
460
477
|
border-radius: 10px; padding: 2px 4px 2px 0; margin-bottom: 12px; }
|
|
@@ -495,16 +512,26 @@ button.attach[aria-pressed="true"] { color: var(--accent); background: var(--chi
|
|
|
495
512
|
}
|
|
496
513
|
@keyframes refresh-spin { to { transform: rotate(360deg); } }
|
|
497
514
|
/* INT-55: the header's right group (the time then ↻, settings) sits one gap apart; each control paints disabled until app-header.js wires it */
|
|
498
|
-
.top .nav { gap:
|
|
515
|
+
.top .nav { gap: 2px; }
|
|
499
516
|
.top .nav .refresh { flex-direction: row-reverse; gap: 2px; }
|
|
500
517
|
.top .nav #refresh, .top .nav a.icon { display: inline-flex; align-items: center; justify-content: center; margin: 0; padding: 4px; border: 0; line-height: 1; }
|
|
501
518
|
.top .nav #status { line-height: 1.1; white-space: nowrap; }
|
|
502
519
|
.top .nav #refresh:disabled, .top .nav a.icon[aria-disabled="true"] { opacity: .35; pointer-events: none; }
|
|
503
|
-
/*
|
|
504
|
-
|
|
505
|
-
.
|
|
506
|
-
.
|
|
507
|
-
.
|
|
520
|
+
/* The foot of the board (docs/adr/2026-11-07-1300-board-and-open-questions-are-two-pages.md): the bulb and the gear, centred under the lanes.
|
|
521
|
+
[the bulb runs an architecture review of the board's repo](docs/adr/2026-11-02-1813-the-bulb-runs-an-architecture-review-of-the-boards-repo.md): it glows orange while an architecture review runs */
|
|
522
|
+
.board-foot { display: flex; justify-content: center; gap: 8px; margin: 20px 0 0; }
|
|
523
|
+
.board-foot .arch-bulb, .board-foot a.icon { display: inline-flex; align-items: center; justify-content: center; min-width: 44px; min-height: 44px; margin: 0; padding: 4px; border: 0; line-height: 1; color: var(--muted); }
|
|
524
|
+
.board-foot :is(.arch-bulb, a.icon):hover { color: var(--text); }
|
|
525
|
+
.board-foot .arch-bulb[hidden] { display: none; }
|
|
526
|
+
.board-foot .arch-bulb.running { color: var(--color-notice); }
|
|
527
|
+
.board-foot a.icon[aria-disabled="true"] { opacity: .35; pointer-events: none; }
|
|
528
|
+
/* The pages switch below 900px: the board header's ? with the open-question count on its corner, and Waiting on you's check back to the board. */
|
|
529
|
+
.top .pages-switch { position: relative; display: inline-flex; align-items: center; justify-content: center; flex: none; width: auto; height: auto; margin: 0; padding: 4px; border: 0; border-radius: 0; line-height: 1; color: var(--muted); }
|
|
530
|
+
.top .pages-switch:hover { color: var(--text); }
|
|
531
|
+
.top .pages-switch:active { background: none; }
|
|
532
|
+
.pages-switch .q-badge { position: absolute; top: 0; right: -2px; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 999px; background: var(--color-notice); color: #1c1407;
|
|
533
|
+
font-size: 11px; font-weight: 700; line-height: 16px; text-align: center; box-shadow: 0 0 0 1.5px var(--bg); }
|
|
534
|
+
.pages-switch .q-badge[hidden] { display: none; }
|
|
508
535
|
.arch-bulb.running svg { filter: drop-shadow(0 0 4px currentColor); }
|
|
509
536
|
@media (prefers-reduced-motion: no-preference) { .arch-bulb.running svg { animation: arch-glow 1.4s ease-in-out infinite; } }
|
|
510
537
|
@keyframes arch-glow { 50% { filter: drop-shadow(0 0 1px currentColor); opacity: .55; } }
|
|
@@ -514,8 +541,6 @@ button.attach[aria-pressed="true"] { color: var(--accent); background: var(--chi
|
|
|
514
541
|
.arch-last .muted { font-size: 12.5px; }
|
|
515
542
|
.arch-body .arch-waits { color: var(--color-notice); }
|
|
516
543
|
.arch-body .arch-run { width: 100%; min-height: 44px; font-size: 15px; }
|
|
517
|
-
/* INT-64: offline, the header's time and refresh turn red */
|
|
518
|
-
.view-board.offline .refresh #refresh, .refresh .link-state.offline { color: var(--color-danger); }
|
|
519
544
|
.back { display: inline-flex; align-items: center; justify-content: center; flex: none; width: 32px; height: 32px; color: var(--accent); text-decoration: none; }
|
|
520
545
|
/* The lead slot, logo or back, is one 44px tap box on the page's left edge, so every page's header lines up the same
|
|
521
546
|
(docs/adr/2026-10-29-1639-a-questions-takes-show-inline.md). The brand keeps that height when the rail hides the logo, so the
|
|
@@ -526,15 +551,10 @@ button.attach[aria-pressed="true"] { color: var(--accent); background: var(--chi
|
|
|
526
551
|
.top .hub + .back { margin-left: 0; }
|
|
527
552
|
.top .back:active { background: var(--accent-soft); }
|
|
528
553
|
.top .brand :is(h1, h2) { font-size: 17px; font-weight: 600; line-height: 22px; }
|
|
529
|
-
/*
|
|
530
|
-
|
|
531
|
-
a blue tint that fades (app-snapshot.js, whose SIGNS holds each animation's length). */
|
|
554
|
+
/* Every change, a line, a moved card or an updated card, open body included, takes the one blue tint that fades
|
|
555
|
+
(app-snapshot.js highlight, whose TINT_MS is its length). */
|
|
532
556
|
main > * { transition: opacity .25s ease; }
|
|
533
|
-
.
|
|
534
|
-
.view-board[data-board="snapshot"] :is(.actions button, button[data-plan], button[data-bump], button[data-confirm="bump"], button[data-edit], button[data-withdraw], button[data-check-github], form[data-reply] button[type=submit], form[data-reply] button[data-quick], form[data-reply] button[data-proto], form[data-reply] button[data-confirm], .prompt button) { opacity: .45; pointer-events: none; }
|
|
535
|
-
.flash { animation: snapshot-flash 1.6s ease-out var(--sign-delay, 0s) 1; border-radius: 6px; }
|
|
536
|
-
@keyframes snapshot-flash { from { box-shadow: inset 0 0 0 2px var(--color-info); } to { box-shadow: inset 0 0 0 2px transparent; } }
|
|
537
|
-
.item.tint { animation: card-tint 2.4s ease-out var(--sign-delay, 0s) 1; border-radius: 6px; }
|
|
557
|
+
.tint { animation: card-tint 2.4s ease-out var(--sign-delay, 0s) 1; border-radius: 6px; }
|
|
538
558
|
@keyframes card-tint { 0%, 25% { background: var(--accent-soft); } 100% { background: transparent; } }
|
|
539
559
|
.msg .quote { margin: 4px 0 6px; padding: 2px 0 2px 8px; border-left: 2px solid var(--line); color: var(--muted); white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
540
560
|
/* [one board registry for every hub feature](docs/adr/2026-10-29-1639-one-board-registry-for-every-hub-feature.md): the GitHub mark opens the Hub links sheet, with a red dot while a link is broken; a new board's setup checklist */
|
|
@@ -565,6 +585,9 @@ main > * { transition: opacity .25s ease; }
|
|
|
565
585
|
.view-settings .add input { flex: 1; min-width: 0; }
|
|
566
586
|
.view-settings .add.pick { flex-wrap: wrap; }
|
|
567
587
|
.view-settings .share-now { color: var(--ok); font-weight: 600; }
|
|
588
|
+
.view-settings .share-level { display: flex; gap: 4px; margin-top: 6px; }
|
|
589
|
+
.view-settings .share-level button { min-height: 32px; padding: 4px 12px; font-size: 13px; }
|
|
590
|
+
.view-settings .share-level button[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); font-weight: 600; }
|
|
568
591
|
.view-settings .share-now::before { content: ""; display: inline-block; width: 7px; height: 7px; margin-right: 5px; border-radius: 50%; background: currentColor; vertical-align: 1px; }
|
|
569
592
|
.view-settings .share-reach { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 10px 0 4px; }
|
|
570
593
|
.view-settings .share-reach p { flex: 1 1 220px; margin: 0; }
|
|
@@ -579,6 +602,7 @@ main > * { transition: opacity .25s ease; }
|
|
|
579
602
|
.view-settings label.who .sub { display: block; margin-top: 2px; }
|
|
580
603
|
.view-settings input.toggle { flex: none; width: 1.4rem; height: 1.4rem; margin: 0; accent-color: var(--accent); }
|
|
581
604
|
.view-settings input.count { flex: none; width: 4.5em; text-align: center; }
|
|
605
|
+
.view-settings label.who select { flex: none; max-width: 45%; min-height: 36px; }
|
|
582
606
|
/* A settings switch waiting on the server (ui.js waitOn) keeps its face, disabled, with a spinner beside it
|
|
583
607
|
(docs/adr/2026-11-03-1921-board-settings-grant-a-boards-session-permissions.md) */
|
|
584
608
|
.view-settings .toggle-slot { flex: none; display: inline-flex; align-items: center; gap: 8px; }
|
|
@@ -599,6 +623,14 @@ main > * { transition: opacity .25s ease; }
|
|
|
599
623
|
.view-settings .close-board-warning:has(li) { border: 1px solid var(--color-danger); padding: 0 12px; border-radius: 10px; margin-bottom: 14px; }
|
|
600
624
|
.view-questions .q { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 12px; margin: 12px 0; }
|
|
601
625
|
.view-questions .q.urgent { border-color: var(--color-notice); }
|
|
626
|
+
/* docs/adr/2026-11-05-2331-a-sent-answer-is-resolved-in-place-until-the-page-reloads.md
|
|
627
|
+
A requested question takes an accent tint, with its border drawn inside the card as well, so the card keeps its size.
|
|
628
|
+
A resolved question has no border or tint of its own; its Resolved badge marks it
|
|
629
|
+
(docs/adr/2026-11-06-1200-a-resolved-question-drops-its-green-border-and-tint.md). */
|
|
630
|
+
.view-questions .q.pr-busy { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 9%, var(--panel)); box-shadow: inset 0 0 0 1px var(--accent); }
|
|
631
|
+
.view-questions .q.pr-busy .opt { opacity: .55; cursor: default; }
|
|
632
|
+
.view-questions .q.pr-busy .opt:has(input:checked) { opacity: 1; }
|
|
633
|
+
.view-questions .q.resolved .sent-line { margin: 6px 0; font-size: 13px; color: var(--color-success); }
|
|
602
634
|
.view-questions .q h3 { font-size: 15px; margin: 6px 0 4px; line-height: 1.3; }
|
|
603
635
|
.view-questions .ctx { color: var(--muted); font-size: 13px; margin-bottom: 8px; }
|
|
604
636
|
.view-questions .opt { display: grid; grid-template-columns: 22px 1fr; gap: 6px; padding: 8px; border: 1px solid var(--line);
|
|
@@ -620,20 +652,17 @@ main > * { transition: opacity .25s ease; }
|
|
|
620
652
|
.view-questions .topics button { min-width: 0; border: 1px solid var(--line); border-radius: 999px; padding: 4px 11px; font-size: 13px;
|
|
621
653
|
background: var(--panel); color: var(--text); white-space: normal; text-align: left; overflow-wrap: anywhere; }
|
|
622
654
|
.view-questions .topics button[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); }
|
|
655
|
+
.view-questions .topics button.progress { border-color: var(--line); background: color-mix(in srgb, var(--color-notice) 12%, var(--panel)); }
|
|
656
|
+
.view-questions .topics button.progress[aria-pressed="true"] { border-color: var(--accent); background: color-mix(in srgb, var(--color-notice) 12%, var(--accent-soft)); }
|
|
623
657
|
@keyframes pill-arrive { from { opacity: 0; transform: translateX(-12px); box-shadow: 0 0 0 3px var(--accent); } to { opacity: 1; transform: none; box-shadow: 0 0 0 0 transparent; } }
|
|
624
658
|
@media (prefers-reduced-motion: no-preference) { .view-questions .topics button.arrived { animation: pill-arrive .6s ease-out; } }
|
|
625
659
|
.view-questions .topics .n { color: var(--muted); margin-left: 4px; }
|
|
626
660
|
.view-questions .topichead { margin: 2px 0 6px; }
|
|
627
661
|
.view-questions details.done > summary { cursor: pointer; color: var(--muted); font-size: 13px; margin: 16px 0 4px; }
|
|
628
|
-
.view-questions details.done .trow { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
|
|
629
|
-
.view-questions details.done button.ghost { font-size: 14px; text-align: left; padding: 6px 2px; }
|
|
630
|
-
.view-questions details.done .trow button { min-width: 0; }
|
|
631
|
-
.view-questions details.done .trow span { flex: none; }
|
|
632
662
|
.view-questions details.resolved-group > summary { list-style: none; min-height: 44px; display: flex; align-items: center; gap: 8px; font-size: 14px; margin: 12px 0; }
|
|
633
663
|
.view-questions details.resolved-group > summary::-webkit-details-marker { display: none; }
|
|
634
664
|
.view-questions details.resolved-group > summary .caret { transition: transform .15s; }
|
|
635
665
|
.view-questions details.resolved-group[open] > summary .caret { transform: rotate(90deg); }
|
|
636
|
-
.view-questions details.resolved-group > summary .names { font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
|
637
666
|
.view-questions .q.resolved button.chip { cursor: pointer; font: inherit; font-size: 12px; min-height: 28px; }
|
|
638
667
|
.view-questions .q.resolved button.chip:disabled { cursor: default; }
|
|
639
668
|
.view-questions .q.resolved .resolved-line { margin: 8px 0; }
|
|
@@ -647,10 +676,12 @@ main > * { transition: opacity .25s ease; }
|
|
|
647
676
|
.view-questions .bar .send-hint { margin-left: auto; }
|
|
648
677
|
/* The + rides above the sticky Send bar rather than over its Send. */
|
|
649
678
|
.view-questions .bar #qstatus { min-width: 0; }
|
|
650
|
-
/* Typing with the keyboard up: one compact row docked on the keyboard, and the + out of the way (view-questions.js dock). */
|
|
651
|
-
.view-questions.kb-up .bar { position: fixed; left: 0; right: 0; bottom: var(--kb, 0px); z-index: 7; padding: 6px 16px; box-shadow: 0 -6px 14px rgba(0,0,0,.12); }
|
|
652
|
-
.view-questions.kb-up .bar .send-hint { display: none; }
|
|
653
679
|
.view-questions .bar #qstatus.sent { color: var(--color-success); font-weight: 600; }
|
|
680
|
+
/* With no open question, the moon and its line sit at the centre of what the page shows between its top and the nav,
|
|
681
|
+
or of what the desktop pane has left under its Needs you lines. */
|
|
682
|
+
.view-questions .q-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; color: var(--muted); text-align: center;
|
|
683
|
+
min-height: calc(var(--vh, 100dvh) - var(--app-bottom, 0px) - max(16px, env(safe-area-inset-top))); }
|
|
684
|
+
.view-questions .q-empty p { margin: 0; font-size: 15px; }
|
|
654
685
|
.view-questions .solo { display: flex; justify-content: space-between; align-items: center; gap: 8px; margin: 4px 0; }
|
|
655
686
|
/* docs/adr/2026-11-02-1332-a-prototype-round-ends-with-a-one-take-confirm.md */
|
|
656
687
|
.view-questions .topichead { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; }
|
|
@@ -721,7 +752,7 @@ main > * { transition: opacity .25s ease; }
|
|
|
721
752
|
position: fixed; top: auto; bottom: 0; left: 0; right: 0; z-index: 7;
|
|
722
753
|
border-bottom: 0; border-top: 1px solid var(--line); padding-top: 4px; padding-bottom: max(4px, env(safe-area-inset-bottom));
|
|
723
754
|
}
|
|
724
|
-
.top .nav #refresh, .top .nav a.icon, .top .
|
|
755
|
+
.top .nav #refresh, .top .nav a.icon, .top .pages-switch { min-width: 44px; min-height: 44px; }
|
|
725
756
|
body > main, body.has-sidebar > :not(.sidebar) > main, body.has-sidebar > :not(.sidebar) > .split > main { padding-top: max(16px, env(safe-area-inset-top)); padding-bottom: calc(96px + var(--app-bottom, 0px) + env(safe-area-inset-bottom)); }
|
|
726
757
|
.view-frame:not(.own-header) { padding-bottom: var(--app-bottom, 0px); }
|
|
727
758
|
.sheet { bottom: max(0px, calc(var(--app-bottom, 0px) - var(--kb, 0px)));
|
|
@@ -730,6 +761,9 @@ main > * { transition: opacity .25s ease; }
|
|
|
730
761
|
.sheet.keyboard-sheet { --sheet-limit: max(80px, calc(var(--vh, 100dvh) + var(--kb, 0px) - max(0px, calc(var(--app-bottom, 0px) - var(--kb, 0px))) - max(12px, env(safe-area-inset-top)))); }
|
|
731
762
|
}
|
|
732
763
|
|
|
764
|
+
/* Typing, a text field focused (shell.js sets :root.typing): the floating + is disabled and the page stays as it is. */
|
|
765
|
+
:root.typing .fab { opacity: .35; pointer-events: none; }
|
|
766
|
+
|
|
733
767
|
.settings-modal { position: fixed; inset: max(12px, env(safe-area-inset-top)) 0 calc(12px + max(var(--kb, 0px), var(--app-bottom, 0px))); margin: auto; width: min(680px, calc(100% - 24px));
|
|
734
768
|
height: fit-content; max-width: none; max-height: max(120px, calc(var(--vh, 100dvh) - max(0px, calc(var(--app-bottom, 0px) - var(--kb, 0px))) - 24px - env(safe-area-inset-top)));
|
|
735
769
|
padding: 0; border: 1px solid var(--line); border-radius: 18px; background: var(--panel); color: var(--text);
|
|
@@ -903,15 +937,24 @@ button[aria-busy="true"]:is([data-card-bump], [data-bump]) svg { fill: currentCo
|
|
|
903
937
|
.deck-more { display: flex; align-items: center; justify-content: space-between; min-height: 46px; padding: 10px 14px; border-top: 1px solid color-mix(in srgb, var(--color-notice) 28%, var(--line));
|
|
904
938
|
background: color-mix(in srgb, var(--color-notice) 14%, var(--panel)); color: var(--text); font-weight: 600; font-size: 14px; text-decoration: none; }
|
|
905
939
|
.deck-more::after { content: '›'; color: var(--accent); font-size: 18px; }
|
|
940
|
+
.deck-more:hover { text-decoration: none; }
|
|
906
941
|
@media (min-width: 900px) {
|
|
907
942
|
.split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: start; }
|
|
908
943
|
.split > main { max-width: none; margin: 0; }
|
|
909
944
|
.needs-deck { display: none; }
|
|
945
|
+
/* Waiting on you is the board's pane here, so neither page shows the switch to the other. */
|
|
946
|
+
.top .pages-switch { display: none; }
|
|
947
|
+
.top .nav a.pages-switch { display: none; }
|
|
910
948
|
/* The board's + sits at the bottom right of the left pane: the pane ends at the middle of the space right of the sidebar. */
|
|
911
949
|
.view-board .fab { right: calc(50% - var(--sidebar-w, 0px) / 2 + var(--fab-edge)); }
|
|
912
950
|
body.has-sidebar { --sidebar-w: 272px; }
|
|
913
951
|
html.sb-collapsed body.has-sidebar { --sidebar-w: 0px; }
|
|
914
|
-
.needs-pane { display: block; position: sticky; top: 56px; height: calc(100vh - 56px); overflow-y: auto; overscroll-behavior: contain; padding: 12px 16px
|
|
952
|
+
.needs-pane { display: block; position: sticky; top: 56px; height: calc(100vh - 56px); overflow-y: auto; overscroll-behavior: contain; padding: 12px 16px 0; border-left: 1px solid var(--line); }
|
|
915
953
|
.needs-pane .needs-questions main { padding: 0; max-width: none; }
|
|
954
|
+
.needs-pane:has(.q-empty), .needs-pane .needs-questions:has(.q-empty), .needs-pane .needs-questions:has(.q-empty) main, .needs-pane #page:has(.q-empty) { display: flex; flex-direction: column; }
|
|
955
|
+
.needs-pane .needs-questions:has(.q-empty), .needs-pane .needs-questions:has(.q-empty) main, .needs-pane #page:has(.q-empty), .needs-pane .q-empty { flex: 1; min-height: 0; }
|
|
916
956
|
.needs-pane .waiting { margin: 0 0 12px; }
|
|
917
957
|
}
|
|
958
|
+
/* The Send bar is the last thing on its page: only the phone nav under it is left clear, so its background runs to the edge. */
|
|
959
|
+
body .view-questions main { padding-bottom: var(--app-bottom, 0px); }
|
|
960
|
+
.msg.calls.status-fold summary .fold-log { margin-left: 6px; color: var(--accent); text-decoration: underline; }
|