Public: anyone can read this; the crew can change it.
Design: a run's summary line and cancel actions use §5's text and bar, and run-summary/run-actions leave §5d #4032
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/3960-design-a-run-s-summary-line-and-cancel-a → main 5 commits fork 2 threads · 2 unresolved
4 files
changed +36
−7
docs/DESIGN.md +1 −1
| @@ -396,7 +396,7 @@ gone, so this list only shrinks. | ||
| 396 | 396 | - **`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` |
| 397 | 397 | - **`style/content.ts`, prose (markdown)** — table cell alignment, built by `markdown.ts` as `ta-${align}`: `.ta-c` `.ta-r` |
| 398 | 398 | - **`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` |
| 399 | - **`style/content.ts`, apps, runs, and the crew** — run pages, their logs and checks (a pull's look is §5's **look**): `.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` | |
| 399 | - **`style/content.ts`, apps, runs, and the crew** — run pages, their logs and checks (a pull's look is §5's **look**): `.ln-forge` `.ln-pulse` `.ln-say` `.ln-tool` `.ln-you` `.log-tail-row` `.pinned-failures` `.pinned-slot` `.run-jump` `.run-live` `.run-steer` `.tests-bar` `.tests-words` `.txt` `.who` | |
| 400 | 400 | - **`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` |
| 401 | 401 | - **`pages/board.ts`** — the board itself is §5's **board**; the spend tables `spend.ts` draws wear §5's `.table` and `.stack`, no class of their own |
| 402 | 402 | - **`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 +3 −3
| @@ -196,7 +196,7 @@ export async function runView( | ||
| 196 | 196 | >`, |
| 197 | 197 | )} |
| 198 | 198 | </div> |
| 199 | <p class="run-summary" id="run-summary">${run.summary}</p> | |
| 199 | <p class="mt" id="run-summary">${run.summary}</p> | |
| 200 | 200 | ${when( |
| 201 | 201 | carried.length > 0, |
| 202 | 202 | () => |
| @@ -235,7 +235,7 @@ export async function runView( | ||
| 235 | 235 | ${when( |
| 236 | 236 | writer && unfinished && run.kind === "agent" && issue === undefined, |
| 237 | 237 | () => |
| 238 | html`<div class="run-actions" id="run-cancel"> | |
| 238 | html`<div class="bar mt" id="run-cancel"> | |
| 239 | 239 | <form method="post" action="${base}/runs/${run.id}/cancel"> |
| 240 | 240 | ${btn("Stop", { size: "sm", variant: "outline", icon: "square" })} |
| 241 | 241 | </form> |
| @@ -244,7 +244,7 @@ export async function runView( | ||
| 244 | 244 | ${when( |
| 245 | 245 | writer && unfinished && run.kind !== "agent", |
| 246 | 246 | () => |
| 247 | html`<div class="run-actions" id="run-cancel"> | |
| 247 | html`<div class="bar mt" id="run-cancel"> | |
| 248 | 248 | <form method="post" action="${base}/runs/${run.id}/cancel"> |
| 249 | 249 | ${btn("Cancel run", { |
| 250 | 250 | size: "sm", |
src/web/style/content.ts −3
| @@ -837,9 +837,6 @@ form.inline { display: inline; margin-left: auto; } | ||
| 837 | 837 | .run-steer textarea { min-height: 38px; max-height: 160px; resize: none; } |
| 838 | 838 | .run-steer .bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } |
| 839 | 839 | .run-steer .bar .muted { flex: 1 1 200px; } |
| 840 | .run-summary { margin: 10px 0 0; text-wrap: pretty; } | |
| 841 | .run-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 12px 0; } | |
| 842 | .run-actions form { display: inline-flex; } | |
| 843 | 840 | /* A tail beside a running row (studio/app.ts): the same log, a peek at it |
| 844 | 841 | — fixed height so nothing around it shifts as lines arrive, and it |
| 845 | 842 | scrolls itself rather than the page. */ |
test/studio-runs.test.ts +32
| @@ -521,6 +521,38 @@ describe("working: a run's own page in the studio", () => { | ||
| 521 | 521 | // The transcript is on the page, and there is no second reading of it. |
| 522 | 522 | expect(page).toContain('id="run-log"'); |
| 523 | 523 | expect(page).not.toContain("?mode=dev"); |
| 524 | // The summary is a plain paragraph, not a page-part class (#3960). | |
| 525 | expect(page).toMatch(/<p class="mt" id="run-summary">/); | |
| 526 | expect(page).not.toContain('class="run-summary"'); | |
| 527 | expect(page).not.toContain("run-actions"); | |
This absence check cannot fail for the cancel blocks, because this run never renders them. Add the cancel-block cases: an unfinished agent run with no issue, and a non-agent run, each asserting |
||
| 528 | }); | |
| 529 | ||
| 530 | test("the cancel form sits in a .bar.mt row for an issue-less agent run and a non-agent run", async () => { | |
| 531 | const agent = createRun(app.db, { | |
| 532 | repoId: repo.id, | |
| 533 | kind: "agent", | |
| 534 | actorId: scoutId, | |
| 535 | reason: "assigned", | |
| 536 | }); | |
| 537 | claimRun(app.db, agent.id); | |
| 538 | const check = createRun(app.db, { | |
| 539 | repoId: repo.id, | |
| 540 | kind: "check", | |
| 541 | reason: "push", | |
| 542 | }); | |
| 543 | for (const [run, label] of [ | |
| 544 | [agent, "Stop"], | |
| 545 | [check, "Cancel run"], | |
| 546 | ] as const) { | |
| 547 | const page = await ( | |
| 548 | await get(`/alice/engine/runs/${run.id}`, writerCookie) | |
| 549 | ).text(); | |
| 550 | expect(page).toMatch( | |
| 551 | new RegExp( | |
| 552 | `<div class="bar mt" id="run-cancel">\\s*<form method="post" action="/alice/engine/runs/${run.id}/cancel">[\\s\\S]*?${label}[\\s\\S]*?</form>`, | |
| 553 | ), | |
| 554 | ); | |
| 555 | } | |
| 524 | 556 | }); |
| 525 | 557 | |
| 526 | 558 | test("a running run's page reads its log from the one /events stream and opens no other", async () => { |
new above
offline
Bottom margin of the old .run-actions (12px) is gone; if the block after this feels tight on a 360px phone, add class mb here and on the Cancel run block.