Skip to content

Show a running build in Chat's run strip and Chat, say that Build plan approves, and keep the runs card within Chat - #883

Merged
lavaman131 merged 5 commits into
mainfrom
build-visibility
Nov 13, 2026
Merged

lavaman131 merged 5 commits into
mainfrom
build-visibility

Conversation

@lavaman131

@lavaman131 lavaman131 commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator

Make a Build visible and approvable where people work

What the maintainer saw

On a hosted deployment:

  1. No way to approve. After chopin rewrote a document's tasks, it said in Chat that the new version "is a draft, so it still needs approval". Nothing anywhere offered an approval. Pressing Build went ahead.
  2. Chat showed nothing of a Build. During a 60-minute build, Chat had only "asked chopin to break the plan into tasks" and chopin's task message. The run strip under Chat listed only the Planner's workflow runs, never the build, its task or its pull request.
  3. "The scroll box gets clipped under the workflow widget." Scrolled up, Chat's transcript ended on a hard line against the runs card, so the card looked like it covered messages.

Causes

  1. Approval. The code is clear that a person's press is the approval: queueBuild approves the exact draft it starts, and a built document's rebuild works from the document's own edits, not from the task graph. Only the Planner didn't know. Its tool said "Only people may approve, lock or start implementation" and nothing more, so it told people a draft waited for an approval that doesn't exist.
  2. Chat. Nothing about a build reached Chat. The strip reads only Planner workflow runs, and no system line was ever posted for a build.
  3. Scroll edge. Two things.
    • The card had no height limit. With several runs unfolded it grew to its full height: in a 600px window it measured 906px, squeezed the transcript to 12px above the window's top and pushed the composer below its bottom, on desktop and phone alike. Only a single run's "every stage" lane had a limit.
    • A hard cut. With few runs, the transcript does end at the card's top edge, with its newest line and scrollbar above it, in main Chat and on a phone. But a line scrolled past that edge was cut off flat against the card, while the top edge fades.

What changed

1. Build plan is the approval, and the Planner says so (agent/tools.ts, agent/planner.ts, build-view.tsx)

  • edit_implementation_graph's description and the Planner's instructions say the Build plan press approves the saved draft and starts it, so it never tells people tasks still need approval.
  • A saved draft's result carries a note for the Planner to read before it says what happens next:
    • A document not yet built: Build plan builds exactly this draft.
    • A built document: Build carries the document's edits onto its pull requests; this draft is not what it runs.
  • Build's "Not built yet" card says "Build plan approves these tasks and builds them."

2. A running build heads Chat's run strip (build-model.ts buildRun, chat/run-card.tsx, chat.tsx, room-workspace.tsx)

  • The row: one line, read from the same snapshot as Build: a pulsing dot, "Build" (or "Building changes" for a rebuild), "1 of 2 tasks", and its age.
  • Unfolded by default: the task being built, the agent's latest activity, the pull requests opened so far as #12 links, and Open Build.
  • Beside workflows: a running workflow folds itself, unless it waits on people.
  • Always shown: it appears whether or not the Planner runs here, and leaves the strip when the build ends.

3. Chat says what the build did (tasks/build-notices.ts, tasks/notifications.ts, main.ts, chat/transcript.tsx)

4. The transcript's lower edge fades (chat/overlay-scrollbar.css)

  • The fade: its last 16px fade into the pane, as the top edge already does.
  • At the bottom: resting on the newest line, the fade sits on the transcript's own bottom space, under no text. The spec checks there are at least 16px.
  • Over, not in: it is drawn over the scroller. A first version used a scroll-state query inside the scroller, which restyled the whole transcript (1,773 elements) once it left the bottom; this version restyles 12, the same as main.

5. The runs card takes at most half of Chat (chat/run-card.css, chat.tsx, threads/thread-panel.tsx)

  • However many runs are unfolded, the card takes at most half of Chat's height, in main Chat and in a thread, and scrolls in its own lane beyond that, with its scrolling contained. The transcript keeps its place and the composer stays in view.

apps/web/DESIGN.md records the strip row, Chat's build lines, the edge and the card's limit.

Evidence

Synthetic, from the browser spec's fake connector, paused on its first task (desktop 1280 wide, and at a phone's width).

Before (main) After
A build running, desktop
A build running, phone width
The build finished

Chopin's app view is light only, so there are no dark shots.

Contrast (computed colours read from the rendered page, WCAG ratios):

Element Ratio
Build row: "Build" and the task 9.58:1
Build row: status, activity, Open Build 6.50:1
Build row: age and chevron 4.97:1
Build row: pulse dot (non-text) 5.79:1
Chat build line: text on the ground 5.89:1
Chat build line: #12 link 6.86:1
Chat build line: wrench (non-text) 4.51:1

Tests

New

  • implementation-launcher.e2e.ts: "a running build heads Chat's run strip with its task, and Chat says when it opens each pull request and when it finishes". It pairs the fake connector and pauses it on the first task, then checks:

    It fails on main, which shows no row.

  • chat-run-strip.e2e.ts (harness project):

    • Five runs of six stages, all unfolded, in a 1280 × 600 window and a 390 × 664 phone. It checks that the card is at most half of Chat and scrolls; that the transcript keeps its top and some height; that the card ends above the composer, and the composer inside Chat and the window; that the last run's stages are reached by scrolling the card; and that the newest message is clear of it. It fails without the new rule (906px against a limit of 289px).
    • A long Chat and four runs, on desktop and phone. At the transcript's end, the scroll area, newest message and scrollbar end above the card, with 16px clear for the fade, as runs unfold, all show and fold again. This passes on main for the geometry; the 16px check is new.
  • Unit tests:

    • build-notices.test.ts: opened once per pull request, finished, stopped, didn't start, rebuild.
    • build-model.test.ts: buildRun.
    • run-card.test.tsx: the row's markup and folding.
    • transcript.test.tsx: pull request links in system lines.

Checks run

  • bun run types and bun run ci pass.

  • Rebased onto 8cb7e623f, which includes the full-height row shade and the image viewer; the only conflicts were in DESIGN.md's Chat row paragraph, where both changes are kept. The build row is a .run-row > .run-head, so it gets the row-shade change's hairline and shade with no change of its own. Earlier, on 8634eefaf, which includes build-pr-lifecycle: The only conflict was two new describe blocks appended to build-model.test.ts; both are kept. The Chat lines read open pull requests from live.pullRequests, which now lists only open ones, and a rebuild's updates from its commits, so a closed pull request is never named as updated.

  • bun test apps/web/src: 1,666 pass. bun test apps/server/src/tasks apps/server/src/agent apps/server/src/plan apps/server/src/chat: 1,873 pass.

  • bun run build:

    Size Budget
    Initial JavaScript, raw 259,958 B 260,500 B
    Initial JavaScript, gzip 82,481 B 83,450 B
    Initial stylesheet, raw 184,420 B 185,000 B
    Initial stylesheet, gzip 32,675 B 33,000 B

    Initial JavaScript is the same as main. The card's 74 B limit lands in the initial stylesheet with run-card.css.

  • Browser specs, --workers=2, after rebasing onto 8634eefaf: implementation-launcher, chat-run-strip, run-card and stopped-build-editing passed 33, with 1 skipped (the opt-in live ACP case). Before that rebase, chat-hover-contrast and chat-phone also passed.

  • Before the rebase: build-restart, chat-row-edges, chat-long-transcript, message-toolbar-layout and pane-work also passed, except one test, chat-row-edges "scrolling a long Chat moves only its thumb". It fails on main the same way on this machine: 160 forced layouts from Playwright's own injected script. With that line logged rather than asserted, its other limits hold: 15 dirty objects and 12 restyles, as on main.

  • bun run design:behavior: 104 passed and 2 failed under load: connecting-indicator and chat-media (narrow), where the design-audit page didn't load in time. Run alone, connecting-indicator passed, and chat-media passed 14 of 14.

Judgement calls

  • Build plan stays the approval; there's no Approve button. The code already treats the press as the approval, and AGENTS.md says "A person's press is the approval". Adding a separate approval would change the product. So the fix is the Planner's words, plus one line on Build's card.
  • On a built document, a Planner draft is explained, not refused. Refusing edit_implementation_graph there would be cleaner, but could break a flow I can't see. The Planner is told the draft isn't what Build runs.
  • The strip shows only a running build. Once a build ends, its Chat line carries the result; keeping an ended row as well would say it twice.
  • The thread panel's strip shows no build. A build belongs to the document, not to a thread.
  • No progress bar in the row. Build already shows one. A new one would add CSS to the nearly full initial stylesheet.
  • A notice that would be posted twice keeps its first text. If a build's end were recorded before its run's outcome, the ID keeps the first line and the conflict is only logged. In the flows tested, the outcome is recorded first.
  • A notice lost in a crash isn't recovered. A crash between persisting a change and posting its line loses that one line. The next process sees the state as already known.

A build ran for an hour on a hosted deployment while Chat said nothing of
it: only that someone asked chopin to break the plan into tasks. Chat now
has one quiet line for each pull request a build opens ("Build opened #12
for “Scaffold the crate”.") and one when it ends ("Build finished: 2
tasks, #12 and #13.", or why it stopped or didn't start; a rebuild says
which pull requests it updated). The `#` is a link to the pull request,
drawn by the system row only for a GitHub pull request link whose label is
its own number, and a build's line has a wrench in place of the system
icon.

The lines are worked out from the stored implementation state each time it
is announced (tasks/build-notices.ts), after the change was persisted. Each
has an ID made from the build and the pull request, so a repeat is one
line; a document opened long after its builds seeds what it has seen and
says nothing of them. They are posted through a new backend callback under
the document lock, on the open room or the document opened for it, as the
research notices are.

Assistant-model: Claude Opus 5.5
Assistant-workflow: inline (subagent)
Assistant-verification: bun test apps/server/src/tasks/build-notices.test.ts (4 pass)
Assistant-verification: bun test apps/server/src/agent apps/server/src/tasks (572 pass)
Assistant-verification: bun test apps/web/src/chat/transcript.test.tsx (19 pass)
Assistant-verification: bun run types
Assistant-verification: bun run ci
User-preference: "make a Build visible and approvable where people work"
Co-authored-by: Alex Lavaee <lavaman131@github.com>
…equests

During a build Chat's run strip listed only the Planner's workflow runs, so
a build that took an hour was visible only in Build. The strip now starts
with the document's running build, read from the same snapshot as Build:
one line with a pulsing dot, "Build" (or "Building changes" for a rebuild),
"1 of 2 tasks" and its age, unfolded into the task being built, its agent's
latest activity, the pull requests it has opened as #12 links, and Open
Build. Beside it a running workflow folds itself unless it waits on people.
It shows whether or not the Planner runs here, and leaves the strip the
moment the build ends; Chat's line about how it ended stays.

The browser spec pairs the fake connector, pauses its agent on its first
task, checks the row on a desktop window and at a phone's width, opens
Build from it, then lets the agent finish and checks Chat's lines and that
the strip lets the build go. It fails on main, where the strip never shows.

Assistant-model: Claude Opus 5.5
Assistant-workflow: inline (subagent)
Assistant-verification: bun test apps/web/src/build-model.test.ts (60 pass)
Assistant-verification: bun test apps/web/src/chat/run-card.test.tsx (22 pass)
Assistant-verification: bun run types
Assistant-verification: bun run ci
Assistant-verification: bun run e2e e2e/implementation-launcher.e2e.ts e2e/chat-run-strip.e2e.ts e2e/run-card.e2e.ts e2e/build-restart.e2e.ts e2e/stopped-build-editing.e2e.ts e2e/chat-row-edges.e2e.ts e2e/chat-hover-contrast.e2e.ts e2e/chat-long-transcript.e2e.ts e2e/chat-phone.e2e.ts e2e/message-toolbar-layout.e2e.ts e2e/pane-work.e2e.ts --workers=2 (77 passed, 1 skipped, 1 failed: chat-row-edges "scrolling a long Chat moves only its thumb", 160 forced layouts from Playwright's own script, the same on main)
User-preference: "make a Build visible and approvable where people work"
Co-authored-by: Alex Lavaee <lavaman131@github.com>
…sks wait for one

After chopin rewrote a document's tasks it told people the new version "is
a draft, so it still needs approval", but nothing anywhere offered an
approval, and pressing Build went ahead. The code is clear that a person's
press is the approval: queueBuild approves the exact draft it starts, and a
built document's rebuild works from the document's own edits, not from the
task graph, with no review. The Planner was only told "Only people may
approve, lock or start implementation".

edit_implementation_graph's description and the Planner's instructions now
say that the Build plan press approves the saved draft and starts it, and a
saved draft's result carries a note for the Planner to read before it says
what happens next: on a document not yet built, Build plan builds exactly
this draft; on a built one, Build carries the document's edits onto its
pull requests and the draft is not what it runs. Build's card under "Not
built yet" says "Build plan approves these tasks and builds them."

Assistant-model: Claude Opus 5.5
Assistant-workflow: inline (subagent)
Assistant-verification: bun test apps/server/src/agent apps/server/src/tasks (572 pass)
Assistant-verification: bun run types
Assistant-verification: bun run ci
User-preference: "make a Build visible and approvable where people work"
Co-authored-by: Alex Lavaee <lavaman131@github.com>
…s last line clear of it

The maintainer saw Chat's transcript run under the workflow runs card. The
transcript does end at the card's top edge, but a line scrolled past it
ended on a hard cut against the card, which reads as the card covering it.
Its last 16px now fade into the pane, as its top edge already fades under
the header. The fade is drawn over the scroller, never inside it, so
scrolling restyles nothing (a scroll-state query on the scroller restyled
the whole transcript once it left the bottom); resting on the newest line
it lies on the transcript's own bottom space, under no text.

A browser spec seeds a long Chat and four runs and checks, on a desktop
window and a phone, that at the transcript's end its scroll area, newest
message and scrollbar end above the card, that the newest message clears
the fade, and that this holds as runs unfold, as every run shows, and as
they fold again. DESIGN.md records the build in the strip, Chat's build
lines and the edge.

Assistant-model: Claude Opus 5.5
Assistant-workflow: inline (subagent)
Assistant-verification: bun run types
Assistant-verification: bun run ci
Assistant-verification: bun run e2e e2e/chat-row-edges.e2e.ts e2e/chat-run-strip.e2e.ts e2e/chat-hover-contrast.e2e.ts --workers=2 (10 passed with the forced-layout line of "moves only its thumb" logged instead of asserted: 160 forced layouts, as on main; dirty objects 15 and restyles 12, as on main)
User-preference: "nit, but the scroll box gets clipped under the workflow widget"
Co-authored-by: Alex Lavaee <lavaman131@github.com>
…it never pushes the composer away

With several workflow runs unfolded the card under Chat's transcript grew
to its full height: in a 600px window it measured 906px, squeezed the
transcript to 12px above the window's top and pushed the composer below
its bottom, on a desktop window and on a phone alike. Only a single run's
"every stage" lane had a bound. The card's wrapper, in main Chat and in a
thread, now takes at most half of Chat's height and scrolls beyond that,
with its scrolling contained.

The browser spec unfolds five runs of six stages in a 1280 × 600 window
and a 390 × 664 phone and checks that the card is at most half of Chat and
scrolls, that the transcript keeps its place and height, that the card
ends above the composer and the composer inside Chat and the window, that
the last run's stages are reached by scrolling the card, and that the
newest message stays clear of it. Without the rule it fails (906 against
289).

Assistant-model: Claude Opus 5.5
Assistant-workflow: inline (subagent)
Assistant-verification: bun run e2e e2e/chat-run-strip.e2e.ts --project=harness --workers=2 (2 passed; the new test fails without the rule)
User-preference: "nit, but the scroll box gets clipped under the workflow widget"
Co-authored-by: Alex Lavaee <lavaman131@github.com>
@lavaman131
lavaman131 merged commit 4b1e6c6 into main Nov 13, 2026
46 of 48 checks passed
@lavaman131
lavaman131 deleted the build-visibility branch November 13, 2026 00:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant