Agent chat: PR chips read (number plus a readable title), +N lists the rest, own line under the reply
Leo flagged the row of PR chips at the end of an agent message in the cmux NEXT transcript. It read #29007 cm… #28969 cm… +53: each title was cut to two letters, +53 was a bare count with no way to see what it held, and the row was glued onto the reply's last prose line. His exact words didn't reach this lane (the coordinator's brief was truncated). This PR fixes the three defects in his screenshot and keeps the chips.
Builds on: #26082 (PR pills share the message action row: #N repo pills, up to two, +N popover). It is merged and nothing else in flight touches these chips. Prior art acknowledged for PullRequestDock.tsx / MessageActions.tsx: #26082, #25207, #25208, #26619, #27252, #24715, #24662 (merged) and #28837, #28674, #28559, #28058, #26819, #26779, #26720, #26374, #29169 (open). None of the open ones change the chip contents, the overflow or the placement.
What changes
- A chip reads. Each chip is
#Nplus the PR's title, or#Nalone, never a stub. A new planner (summary/pullRequestChipPlan.ts) works from measured widths. It tries titled chips first (up to two), then numbers only, then one number. A title is drawn only when at least 12 characters of it fit (or all of it, if shorter). The title comes from the header's live state, the chat's--title, or GitHub's page title. The chip's existing hover card still gives the whole title, the repository and the state. +Nlists the rest. It is still a button and still opens the instant portaled popover, so the transcript never moves. The popover now lists every PR the row left out, each#Nwith its title (or the repository when cmux has no title). The list scrolls inside a capped height instead of running off the screen, as it did before (see the before stills). Focus stays on+Nwhen the list opens, so no chip's hover card covers it. Tab moves into the chips and Escape returns to+N.- Placement. Where the message has an action row, the chips stay beside it (unchanged). On a reply without one (streaming or footerless), the chips used to share a line with the final word. They now get their own line, right-aligned under the last words, up to 70% of the width. Final code, tables, math, rules and diagrams keep their bottom-edge tail (
BlockTail, unchanged). On a received message's line, the dock is capped atmin(320px, 45vw), the same cap as the inline tail. - Nothing is removed: the chips, the hover card, the overflow, the action-row placement, the fallback above the composer and the legacy comparison modes all remain.
Gallery (agent-pane.pr-dock): three variants for 55 PRs, picked readable-titles
| Light | Dark | |
|---|---|---|
readable-titles (picked): number + readable title, own right-aligned line | ![]() | ![]() |
numbers-only: numbers alone, titles on hover and in the list | ![]() | ![]() |
inline-last-line: readable chips kept on the reply's last line | ![]() | ![]() |
Why readable-titles: it is the only variant where both chips tell you which PR they are without hovering. numbers-only is tidy, but with 55 PRs a number alone means nothing. inline-last-line cramps the titles back toward stubs and still glues the row to the prose, which is the third defect.
Before / after (2x, Linux Chromium gallery, synthetic 55-PR fixture)
Before is the base renderer with this PR's fixture (commit 89f2a9e, run 41196311350). After is this head (run 41212927635).
| Before | After | |
|---|---|---|
| Light | ![]() | ![]() |
| Dark | ![]() | ![]() |
Light, +N open | ![]() | ![]() |
Dark, +N open | ![]() | ![]() |
Viewed the after images: yes, all of them. Before, the row reads #1254 atlas-web +54 stuck to "pick them up.", and the open list is 54 identical atlas-web rows running off the bottom of the pane. After, the row is #1254 Retry uploads on transient fa… #1253 Keep the upload progress o… +53 on its own line, right-aligned, and the open list shows each PR's title and scrolls within the pane.
Tests
cmux-ci test on fleet Linux at e6a34d6 (run 41212927635): 107 pass, 0 fail. Typecheck, vp fmt --check, vp lint and check:design-tokens are clean.
- New
pr-chip-plan.test.ts(truncation): two titled chips when they fit; one titled chip before two stubbed ones; numbers alone when no readable title fits; a title is never cut below its readable minimum (a sweep over widths); short titles count at their own width; untitled PRs; numbers-only and one-chip modes; at least one chip always. - New in
pr-dock-inline-layout.test.ts(real Chromium, light and dark, 760/420/320 px): with 55 PRs, every drawn chip is#Nwith a whole title or at least 12 characters of one. The row sits on its own line below the last words, right-aligned to the reply.+Nreads+(55 − drawn)and opens a list of exactly the rest, each titled. Drawn and listed together cover all 55. The list stays inside the viewport and scrolls. Escape closes it. A chip's hover card gives the whole title. These six layout tests are red on base (run 41193251052: "on its own right-aligned line", 6 fail) and green here. - Existing suites unchanged and passing:
agent-pane-pr-dock,pr-hover-card,transcript-pr-revision,agent-pane-bottom-stack,agent-pane-dock-gutters,popover-instant-open*, and the rest ofpr-dock-inline-layout(action-row, fallback, tail variants, overflow never moves the transcript).pr-dock-stale-statenow expects the number alone in JSDOM, which has no layout, so no title fits.
Performance
Nothing new runs per streamed token. The PR list is still memoized on the producer's revision key (transcript-pr-revision.test.tsx: "200 reply/thought tokens without new PR findings do not recompute the dock's PR list" passes). The planner runs only in the existing ResizeObserver callback (the one observer, disconnected on cleanup, unchanged). It measures at most two hidden titles, and setPlan bails out when the plan is unchanged.
| Metric | Before | After | Delta | Harness |
|---|---|---|---|---|
| Dock PR-list recomputes over 200 reply tokens with no new PR | 0 | 0 | 0 | transcript-pr-revision.test.tsx, run 41212927635 |
| ResizeObservers per dock | 1 | 1 | 0 | code read |
Risk
Low to medium, webview only. The row's planner now measures titles, so the case to watch is a narrow pane with long titles: the tests cover it at 320 px. The visible behavior change is the placement of chips on a reply with no action row (own line instead of after the last word); the old treatment remains as the inline-last-line gallery comparison.
Spec: leo-workspace design/chat-transcript.md §"Pull request chips" plus a Rules line, in teamleaderleo/leo-workspace#1924.
Changelog
Fixed: Pull request chips at the end of an agent message now show the PR number with a readable title (or the number alone), and +N opens a list of the rest.
Agent session: agent session
🤖 Generated with Claude Code











