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

Design: a run's check steps and progress use the design system, and check-steps/check-progress leave §5d #3886

Merged opened this

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

Merged

scout/forge:scout/3873-design-a-run-s-check-steps-and-progress → main 4 commits fork 2 threads · 2 unresolved

  • Merged. scout/3873-design-a-run-s-check-steps-and-progress was merged into main as e470210.

Review

Sign in to review this change.
4 files changed +27 −21
docs/DESIGN.md +1 −1
@@ -389,7 +389,7 @@ gone, so this list only shrinks.
389389 - **`style/content.ts`, code view** — the source view and the syntax-highlight token classes `highlight-html.ts` builds as `tok-${kind}`: `.blob-view` `.ln` `.tok-attribute` `.tok-comment` `.tok-constant` `.tok-decorator` `.tok-deleted` `.tok-emphasis` `.tok-function` `.tok-heading` `.tok-inserted` `.tok-keyword` `.tok-link` `.tok-meta` `.tok-number` `.tok-operator` `.tok-plain` `.tok-property` `.tok-punctuation` `.tok-regex` `.tok-string` `.tok-tag` `.tok-type` `.tok-variable`
390390 - **`style/content.ts`, prose (markdown)** — table cell alignment, built by `markdown.ts` as `ta-${align}`: `.ta-c` `.ta-r`
391391 - **`style/content.ts`, diff viewer** — the diff table: its gutters, line kinds, file head and inline review comments: `.add` `.del` `.dh` `.diff` `.diff-body` `.diff-head` `.diff-note` `.diff-path` `.diff-stat` `.diff-stub` `.diff-summary` `.diff-table` `.dl` `.dl-review` `.dn` `.dn-comment` `.nonl` `.rc-hidden` `.rc-resolve` `.rc-resolved` `.wd`
392 - **`style/content.ts`, apps, runs, and the crew** — run pages, their logs and checks (a pull's look is §5's **look**): `.app-url` `.check-progress` `.check-steps` `.ln-forge` `.ln-pulse` `.ln-say` `.ln-tool` `.ln-you` `.log-tail-row` `.pinned-failures` `.pinned-slot` `.run-actions` `.run-jump` `.run-live` `.run-steer` `.run-summary` `.step-state` `.tests-bar` `.tests-words` `.txt` `.who`
392 - **`style/content.ts`, apps, runs, and the crew** — run pages, their logs and checks (a pull's look is §5's **look**): `.app-url` `.ln-forge` `.ln-pulse` `.ln-say` `.ln-tool` `.ln-you` `.log-tail-row` `.pinned-failures` `.pinned-slot` `.run-actions` `.run-jump` `.run-live` `.run-steer` `.run-summary` `.tests-bar` `.tests-words` `.txt` `.who`
393393 - **`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`
394394 - **`pages/board.ts`** — the spend tables `spend.ts` draws, first on the board (the board itself is §5's **board**): `.spend-table` `.spend-totals`
395395 - **`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`
src/web/studio/run.ts +17 −11
@@ -30,7 +30,7 @@ import { liveRegionsJs, studioRunJs } from "../client.ts";
3030 import { html, raw, when, type Html } from "../html.ts";
3131 import { runLine } from "../describe.ts";
3232 import { page } from "../layout.ts";
33 import { btn, sha } from "../ui.ts";
33 import { badge, btn, listCard, row as stepRow, sha, type Tone } from "../ui.ts";
3434 import { previewCard } from "../pages/apps.ts";
3535 import { costText, durationText, runTitle, runTokens } from "../pages/runs.ts";
3636 import { placementLine, sandboxLine } from "./sandbox.ts";
@@ -39,6 +39,9 @@ import { changeHref, tellHref } from "./nouns.ts";
3939 import { unnamedRun, voiceFor, type Actor } from "./voice.ts";
4040 import { runWord, servingHistory, wordBadge, type WordCtx } from "./words.ts";
4141
42 /** A step's badge: red only where it failed; waiting and skipped stay quiet. */
43 const STEP_TONES: Record<string, Tone> = { ok: "success", failed: "danger", running: "attn" };
44
4245 /**
4346 * The one control a live run has: a person who can write here stops a
4447 * running one or takes a queued one back off the board. Nothing for runs
@@ -291,17 +294,20 @@ export async function runView(
291294 // While the run is open every part is rendered at a fixed size (the
292295 // pin slot by CSS), so the log below never moves as numbers and pins
293296 // arrive; a run that has ended reserves nothing.
294 return html`<div class="check-progress" id="check-progress" data-state="${run.state}">
297 return html`<div data-check-progress id="check-progress" data-state="${run.state}">
295298 <div id="check-steps" role="status" aria-live="polite">
296 <ol class="check-steps">
297 ${steps.map(
298 (s) =>
299 html`<li data-step="${s.name}" data-state="${s.state}">
300 <span class="step-name">${s.name}</span>
301 <span class="step-state">${stepWord(s.state)}</span>
302 </li>`,
303 )}
304 </ol>
299 ${listCard(

Good: the live region wraps a listCard, so a screen reader hears each step word change. Next: give the list an aria-label such as "Check steps" if card() allows it.

300 steps.map((s) =>
301 stepRow({
302 title: s.name,
303 data: { step: s.name, state: s.state },
304 trail: badge(stepWord(s.state), {
305 tone: STEP_TONES[s.state] ?? "default",
306 variant: "outline",
307 }),
308 }),
309 ),
310 )}
305311 ${known
306312 ? html`<progress class="tests-bar" aria-label="Test files started" max="${row.of ?? 0}" value="${row.started ?? 0}"></progress>`
307313 : open
src/web/style/content.ts +1 −8
@@ -803,21 +803,14 @@ form.inline { display: inline; margin-left: auto; }
803803 .checks-line .ok { display: inline-flex; align-items: center; gap: 4px; color: var(--success); }
804804 .checks-line .bad { display: inline-flex; align-items: center; gap: 4px; color: var(--danger); }
805805 .checks-line .now { display: inline-flex; align-items: center; gap: 4px; color: var(--attn); }

Next: .tests-bar, .tests-words and .pinned-failures are still bespoke. Fold them into the design system, or note in §5 why they stay.

806 .check-steps { list-style: none; margin: 12px 0 0; padding: 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
807 .check-steps li { display: flex; justify-content: space-between; gap: 8px; padding: 8px 12px; min-height: 2.5em; border: 1px solid var(--border); border-radius: var(--radius); }
808806 .tests-bar { display: block; width: 100%; height: 8px; margin-top: 8px; }
809807 .tests-words { margin: 4px 0 0; min-height: 1.4em; }
810808 /* Reserved only while the run is open: pins arriving never move the log. */
811809 .pinned-slot { max-height: 9em; overflow-y: auto; }
812 .check-progress[data-state="queued"] .pinned-slot, .check-progress[data-state="running"] .pinned-slot { height: 9em; }
810 [data-check-progress][data-state="queued"] .pinned-slot, [data-check-progress][data-state="running"] .pinned-slot { height: 9em; }
813811 .pinned-failures { list-style: none; margin: 8px 0 0; padding: 0; display: grid; gap: 6px; }
814812 .pinned-failures li { padding: 6px 12px; border: 1px solid var(--danger); border-radius: var(--radius); }
815813 .pinned-failures pre { margin: 4px 0 0; white-space: pre-wrap; overflow-wrap: anywhere; font-size: var(--fs-min); }
816 @media (max-width: 480px) { .check-steps { grid-template-columns: minmax(0, 1fr); } }
817 .check-steps .step-state { color: var(--faint); }
818 .check-steps [data-state="ok"] .step-state { color: var(--success); }
819 .check-steps [data-state="failed"] .step-state { color: var(--danger); }
820 .check-steps [data-state="running"] .step-state { color: var(--attn); }
821814 /* A run, live: a transcript with a speaker column, bounded so the page
822815 head stays reachable, and a composer that sits under it. */
823816 .run-live { position: relative; margin-top: 12px; }
test/studio-runs.test.ts +8 −1
@@ -834,13 +834,20 @@ describe("working: a run's own page in the studio", () => {
834834 expect(block).not.toContain('role="region"');
835835 // With pins, the slot is focusable and labelled; the live region holds no pin.
836836 expect(page).toContain('class="pinned-slot" id="check-pins" role="region" tabindex="0" aria-label="Failed tests"');
837 expect(page.slice(live, page.indexOf("</div>", live))).toContain("tests-words");
837 expect(page.slice(live, page.indexOf(`id="check-pins"`, live))).toContain("tests-words");
838838 expect(quiet).toContain('class="tests-words');
839839 // A finished run with no known count shows no bar at all.
840840 const done = mk(["$ bun install"]);
841841 finishRun(app.db, done.id, { state: "failed", summary: "x" });
842842 const ended = await (await get(`/alice/engine/runs/${done.id}`, writerCookie)).text();
843843 expect(ended).toContain("check-progress");
844 // Drawn from the design system: the old bespoke classes are gone.
845 for (const old of ['class="check-steps"', 'class="check-progress"', "step-state", "step-name"]) {
846 expect(ended).not.toContain(old);
847 expect(page).not.toContain(old);
848 }
849 expect(page).toContain('data-step="');
850 expect(page).toContain("data-check-progress");
844851 expect(ended).not.toContain("<progress");
845852 // No look has recorded vitals yet: the run page renders no table, not an empty one.
846853 expect(ended).not.toContain("vitals");

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs