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

Design: /flow's outflow sparklines use the design system, and flow-bar/flow-out leave §5d #3823

Merged opened this

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

Merged

scout/forge:scout/3614-design-flow-s-outflow-sparklines-use-the → main 1 commit fork

  • Merged. scout/3614-design-flow-s-outflow-sparklines-use-the was merged into main as 2142012.

Review

Sign in to review this change.
4 files changed +8 −15
docs/DESIGN.md −1
@@ -394,7 +394,6 @@ gone, so this list only shrinks.
394394 - **`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`
395395 - **`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`
396396 - **`pages/board.ts`** — the spend tables `spend.ts` draws, first on the board (the board itself is §5's **board**): `.spend-table` `.spend-totals`
397 - **`pages/flow-panel.ts`** — the pipeline's outflow sparklines, on /flow and /boards: `.flow-bar` `.flow-out`
398397 - **`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-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`
399398 - **`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`
400399
src/web/pages/boards-view.ts +1 −2
@@ -19,7 +19,7 @@ import { notFoundPage } from "../chrome.ts";
1919 import { html, raw, when, type Html } from "../html.ts";
2020 import { fragment, page } from "../layout.ts";
2121 import { btn, card, empty } from "../ui.ts";
22 import { flowPanel, flowPanelCss } from "./flow-panel.ts";
22 import { flowPanel } from "./flow-panel.ts";
2323 import { isPinned, listPins } from "../../boards/pins.ts";
2424 import { cellsOf, chartBox, paramsOf, queryFields, readParams, runFor } from "./boards.ts";
2525
@@ -105,7 +105,6 @@ export async function boardsList(ctx: Ctx): Promise<Response> {
105105 body: html`<div class="page-head"><h1>Boards</h1>
106106 ${btn("Explore the data", { href: "/boards/data", variant: "ghost" })}</div>
107107 ${card(make)} ${body} ${flowPanel(ctx.app, { kind: "owner" }, ctx.url, "/boards")}`,
108 styleCss: flowPanelCss,
109108 });
110109 }
111110
src/web/pages/flow-panel.ts +3 −10
@@ -37,13 +37,6 @@ import { html, type Html } from "../html.ts";
3737 import { heldCard } from "./boards-held.ts";
3838 import { badge, card } from "../ui.ts";
3939
40 export const flowPanelCss = /* css */ `
41 .flow-out { display: inline-flex; align-items: center; gap: 8px; font-variant-numeric: tabular-nums; }
42 .flow-out svg { width: 72px; height: 16px; }
43 .flow-bar { fill: var(--muted-fg); }
44 .flow-bar[data-empty] { fill: var(--muted); }
45 `;
46
4740 /** What `?out=` and `?hour=` ask to list, checked: an unknown stage or hour lists nothing. */
4841 function asked(url: URL): { key: PipelineKey; hour: number | null } | null {
4942 const key = stageOf(url.searchParams.get("out") ?? "");
@@ -69,15 +62,15 @@ function sparkline(t: StageThroughput): Html {
6962 const bars = t.hours.map((n, hour) => {
7063 const x = (HOURS - 1 - hour) * 3;
7164 const h = n === 0 ? 1 : Math.max(2, Math.round((n / max) * 16));
72 return html`<rect class="flow-bar" ${n === 0 ? html`data-empty` : null} x="${String(x)}" y="${String(16 - h)}" width="2" height="${String(h)}" />`;
65 return html`<rect ${n === 0 ? html`fill-opacity="0.3"` : null} x="${String(x)}" y="${String(16 - h)}" width="2" height="${String(h)}" />`;
7366 });
74 return html`<svg viewBox="0 0 72 16" aria-hidden="true" focusable="false">${bars}</svg>`;
67 return html`<svg class="muted" viewBox="0 0 72 16" width="72" height="16" fill="currentColor" aria-hidden="true" focusable="false">${bars}</svg>`;
7568 }
7669
7770 function outCell(base: string, t: StageThroughput): Html {
7871 const day = t.items.length;
7972 const passed = t.items.filter((i) => i.outcome === "passed").length;
80 return html`<span class="flow-out" data-out="${OUTFLOW[t.key].out}">
73 return html`<span class="bar num" data-out="${OUTFLOW[t.key].out}">
8174 <a class="mono" href="${outHref(base, t.key, 0)}" data-hour="0">${String(t.hours[0] ?? 0)}</a>
8275 ${sparkline(t)}
8376 <a class="muted small" href="${outHref(base, t.key, null)}" data-day="${String(day)}">${String(day)} in 24 h</a>
test/flow-throughput.test.ts +4 −2
@@ -190,13 +190,15 @@ describe("/flow: actual throughput per stage", () => {
190190 expect(page).toMatch(/data-out="landed">\s*<a class="mono" href="\/boards\?out=landed&amp;hour=0#out" data-hour="0">2<\/a>/);
191191 expect(page).toContain('data-day="6">6 in 24 h');
192192 expect(page).toContain("2 passed · 1 failed");
193 expect((page.match(/class="flow-bar"/g) ?? []).length).toBe(PIPELINE.length * 24);
193 expect(page).not.toContain("flow-bar");
194 expect(page).not.toContain("flow-out");
195 expect((page.match(/<rect /g) ?? []).length).toBeGreaterThanOrEqual(PIPELINE.length * 24);
194196 });
195197
196198 test("each sparkline is a picture only: hidden from assistive tech, no links or tab stops of its own", async () => {
197199 const page = await (await get("/boards")).text();
198200 const svgs = page.match(/<svg[\s\S]*?<\/svg>/g) ?? [];
199 const sparks = svgs.filter((s) => s.includes('class="flow-bar"'));
201 const sparks = svgs.filter((s) => s.includes('viewBox="0 0 72 16"'));
200202 expect(sparks).toHaveLength(PIPELINE.length);
201203 for (const svg of sparks) {
202204 expect(svg).toContain('aria-hidden="true"');

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs