Public: anyone can read this; the crew can change it.

Design: the held board (boards-held) is built from the design system, and its held-* classes leave §5d #3219

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

mason/forge:mason/3199-design-the-held-board-boards-held-is-bui → main 1 commit fork

  • Merged. mason/3199-design-the-held-board-boards-held-is-bui was merged into main as a5cf048.

Review

Sign in to review this change.
4 files changed +46 −58
docs/DESIGN.md +1 −1
@@ -393,7 +393,7 @@ gone, so this list only shrinks.
393393 - **`style/content.ts`, the editor** — the editor's parts beyond the §5 row: `.editor-mirror` `.editor-path` `.editor-preview` `.editor-stack` `.editor-tab` `.mirrored` `.ml`
394394 - **`style/content.ts`, search results** — search results, the inbox's ask cards, the client list and the claim link: `.ask-acts` `.ask-card` `.ask-group` `.ask-kind` `.ask-meta` `.ask-more-acts` `.ask-pick` `.ask-pick-hit` `.ask-short` `.ask-waiting` `.claim-link-box` `.forges-sent` `.live-toggle` `.needs-you` `.no-hits`
395395 - **`pages/board.ts`** — the spend tables `spend.ts` draws, first on the board (the board itself is §5's **board**): `.spend-table` `.spend-totals`
396 - **`pages/flow.ts`** — /flow's pipeline bars, one `held-${kind}` per reason a stage holds work: `.flow-bar` `.flow-out` `.held-bar` `.held-done_on_main` `.held-key` `.held-open_pull` `.held-parked_twice` `.held-planner` `.held-red_fix` `.held-run_cap` `.held-running` `.held-setting` `.held-stalled` `.held-startable`
396 - **`pages/flow-panel.ts`** — the pipeline's outflow sparklines, on /flow and /boards: `.flow-bar` `.flow-out`
397397 - **`pages/map.ts`** — the map of what waits for what: `.map-ask` `.map-breath` `.map-canvas` `.map-card` `.map-counts` `.map-dot` `.map-edge` `.map-edge-crit` `.map-edge-done` `.map-empty` `.map-lane` `.map-lane-caret` `.map-lane-crew` `.map-lane-head` `.map-lane-name` `.map-lane-word` `.map-lanes` `.map-list` `.map-main` `.map-meta` `.map-node` `.map-pillars` `.map-rule` `.map-svg` `.map-title` `.map-tool` `.map-tools` `.map-who` `.map-who-i` `.map-who-n` `.map-word` `.map-wrap`
398398 - **`pages/milestones.ts`** — milestones and one limb opened: `.ms-actions` `.ms-bar` `.ms-count` `.ms-figures` `.ms-head` `.ms-limb` `.ms-limbs` `.ms-more` `.ms-move` `.ms-name` `.ms-planner` `.ms-rows` `.ms-section` `.ms-state` `.ms-top` `.ms-trim` `.ms-weight`
399399 - **`studio/feed_style.ts`** — the home feed's cards, its composer and the apps rail: `.composer` `.composer-bar` `.fcard-form` `.fcard-reason` `.fcard-reply` `.fcard-text` `.fcard-unblock` `.fcard-who` `.fcard-why` `.feed` `.feed-head` `.kpi` `.kpi-note` `.kpi-spark` `.kpi-value` `.kpis` `.home-app` `.home-apps` `.home-apps-verbs` `.home-pick` `.home-rail` `.home-tabs`
src/web/pages/boards-held.ts +37 −51
@@ -1,6 +1,6 @@
11 /**
2 * Why ready issues wait, per autonomous repository: its ready list as one bar
3 * with the count each filter holds (`flow/held.ts`). The flow panel
2 * Why ready issues wait, per autonomous repository: its ready list as one
3 * table row with the count each filter holds (`flow/held.ts`). The flow panel
44 * (`flow-panel.ts`), on `/boards` and `/flow`, draws it from here. A private
55 * repository is in the rows for the owner only.
66 */
@@ -8,48 +8,21 @@
88 import { HOLDS } from "../../crew/held.ts";
99 import type { HeldRow } from "../../flow/held.ts";
1010 import { html, type Html } from "../html.ts";
11 import { card } from "../ui.ts";
11 import { card, meter } from "../ui.ts";
1212
13 export const heldCss = /* css */ `
14 .held-bar { width: 100%; height: 12px; display: block; }
15 .held-key { display: inline-flex; align-items: center; gap: 4px; margin-right: 12px; font-variant-numeric: tabular-nums; }
16 .held-key svg { width: 10px; height: 10px; }
17 .held-startable { fill: var(--success); }
18 .held-running { fill: var(--accent); }
19 .held-open_pull { fill: var(--merged); }
20 .held-planner { fill: var(--primary); }
21 .held-done_on_main { fill: var(--add-fg); }
22 .held-stalled { fill: var(--attn); }
23 .held-run_cap { fill: var(--danger); }
24 .held-parked_twice { fill: var(--del-fg); }
25 .held-red_fix { fill: var(--muted-fg); }
26 .held-setting { fill: var(--border-strong); }
27 `;
28
29 /** One segment per kind, as wide as its share: a picture only; the counts beside it are the words. */
30 function heldBar(row: HeldRow): Html {
31 const parts: [string, number][] = [
32 ["startable", row.startable],
33 ...HOLDS.map((h): [string, number] => [h, row.counts[h]]),
34 ];
35 const total = parts.reduce((n, [, c]) => n + c, 0);
36 let x = 0;
37 const rects = parts
38 .filter(([, c]) => c > 0)
39 .map(([kind, c]) => {
40 const w = (c / total) * 100;
41 const rect = html`<rect class="held-${kind}" x="${x.toFixed(2)}" y="0" width="${w.toFixed(2)}" height="12" />`;
42 x += w;
43 return rect;
44 });
45 return html`<svg class="held-bar" viewBox="0 0 100 12" preserveAspectRatio="none" aria-hidden="true" focusable="false">${rects}</svg>`;
13 function heldCount(kind: string, n: number): Html {
14 return html`<span data-held="${kind}"><span class="mono">${kind}=${String(n)}</span></span>`;
4615 }
4716
48 function heldKey(kind: string, n: number): Html {
49 return html`<span class="held-key small" data-held="${kind}">
50 <svg viewBox="0 0 10 10" aria-hidden="true" focusable="false"><rect class="held-${kind}" width="10" height="10" /></svg>
51 <span class="mono">${kind}=${String(n)}</span>
52 </span>`;
17 /** The startable share of the ready list; `attn` when none of it can start. */
18 function startableMeter(row: HeldRow): Html {
19 const total = row.startable + HOLDS.reduce((n, h) => n + row.counts[h], 0);
20 if (total === 0) return html`<span class="muted">—</span>`;
21 return meter(
22 (row.startable / total) * 100,
23 `${row.startable} of ${total} ready can start`,
24 row.startable === 0 ? "attn" : undefined,
25 );
5326 }
5427
5528 /** Why ready issues cannot start, per autonomous repository (#2244). */
@@ -65,16 +38,29 @@ export function heldCard(rows: readonly HeldRow[]): Html {
6538 </p>
6639 ${rows.length === 0
6740 ? html`<p class="muted">No repository runs on its own.</p>`
68 : rows.map(
69 (r) => html`<div data-held-repo="${r.repo}">
70 <p><a href="/${r.repo}/map">${r.repo}</a></p>
71 ${heldBar(r)}
72 <p>
73 ${heldKey("startable", r.startable)}
74 ${HOLDS.filter((h) => r.counts[h] > 0).map((h) => heldKey(h, r.counts[h]))}
75 </p>
76 </div>`,
77 )}`,
41 : html`<div class="table-scroll">
42 <table class="table" aria-label="Ready, and why it waits">
43 <thead>
44 <tr>
45 <th scope="col">Repository</th>
46 <th scope="col">Can start</th>
47 <th scope="col">Held by</th>
48 </tr>
49 </thead>
50 <tbody>
51 ${rows.map(
52 (r) =>
53 html`<tr data-held-repo="${r.repo}">
54 <th scope="row"><a href="/${r.repo}/map">${r.repo}</a></th>
55 <td>${heldCount("startable", r.startable)} ${startableMeter(r)}</td>
56 <td>
57 ${HOLDS.filter((h) => r.counts[h] > 0).map((h) => html`${heldCount(h, r.counts[h])} `)}
58 </td>
59 </tr>`,
60 )}
61 </tbody>
62 </table>
63 </div>`}`,
7864 { id: "held" },
7965 );
8066 }
src/web/pages/flow-panel.ts +2 −2
@@ -34,10 +34,10 @@ import type { QueueRow } from "../../flow/queue.ts";
3434 import { readHeld } from "../../flow/held.ts";
3535 import type { App } from "../../app.ts";
3636 import { html, type Html } from "../html.ts";
37 import { heldCard, heldCss } from "./boards-held.ts";
37 import { heldCard } from "./boards-held.ts";
3838 import { badge, card } from "../ui.ts";
3939
40 export const flowPanelCss = heldCss + /* css */ `
40 export const flowPanelCss = /* css */ `
4141 .flow-out { display: inline-flex; align-items: center; gap: 8px; font-variant-numeric: tabular-nums; }
4242 .flow-out svg { width: 72px; height: 16px; }
4343 .flow-bar { fill: var(--muted-fg); }
test/crew-held.test.ts +6 −4
@@ -398,14 +398,16 @@ describe("the tick says why each ready issue cannot start", () => {
398398 expect(res.status).toBe(200);
399399 const page = await res.text();
400400 expect(page).toContain('id="held"');
401 const row = page.slice(page.indexOf('data-held-repo="alice/open-door"'));
402 expect(row).toContain('class="held-bar"');
401 const at = page.indexOf('data-held-repo="alice/open-door"');
402 const row = page.slice(at, page.indexOf("</tr>", at));
403 expect(row).toContain('<meter');
404 expect(row).toContain('aria-label="0 of 3 ready can start"');
405 expect(row).toContain('data-tone="attn"');
403406 expect(row).toContain('<span class="mono">startable=0</span>');
404407 expect(row).toContain('<span class="mono">stalled=1</span>');
405408 expect(row).toContain('<span class="mono">done_on_main=1</span>');
406409 expect(row).toContain('data-held="done_on_main"');
407 expect(row).toContain('<rect class="held-done_on_main"');
408 expect(page).toContain(".held-done_on_main { fill:");
410 expect(page).not.toContain("held-done_on_main");
409411 expect(row).toContain('<span class="mono">run_cap=1</span>');
410412 expect(page).toContain('data-held-repo="alice/shut-door"');
411413

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs