cmux NEXT Inspector Changes: every row's title, state, lines, comments and checks from the App's cache
Third of three pieces for the Inspector's Changes card, after #23786 (rows) and #23994 (which pull requests).
What
Changes listed every pull request the transcript names, but only the folder's (the header's live read) had a status. Every other row showed a bare #N with no title, state, lines or checks, and pull requests the agent only named stayed hidden. Now each row reads the App's one pull request cache, AgentReplyPullRequests, the same one the reply links' hover cards read:
-
inspector/changesStatus.ts:- Registers the list with the host through
link.inspect, 64 links per call (the host's per-request bound). The host reads stale links in batched GraphQL queries of 50. - Registers each link at most once a minute. Then it asks
pr.inspect {url, cached: true}for each answer: the host answers from its cache only and never starts a GitHub read for one row (AgentReplyPullRequests.answer(url, read: false)). - While the host says a read is running, it asks the App again every 1.5 s. It never polls GitHub and never reads per row.
- It registers and asks nothing while Changes is folded, the Inspector is closed or the page is hidden. A transcript update that names no new pull request does nothing (the URL list stays the same array).
- The header's live read of the folder's pull request wins. Its checks and conflict state replace the cache's older failing counts and conflict, so a push does not leave a stale "2 failing".
- A row's state counts from when the host's read began (
readFrom, new on thepr.inspectanswer). A read that began before the chat'sgh pr closecannot reopen the row, however long its batch took; a real reopen after it shows. - A folded Changes whose pull requests are all only named, with no answer yet, keeps its header (no rows, no count), so it can be opened.
- Registers the list with the host through
-
An answer maps to the row's fields:
- title;
- state, including draft;
mergeable: conflicting is drawn red;- +/- lines, with a by-kind breakdown;
- comment count;
- failing checks, with how many also fail on base.
A
#NGitHub has no pull request for (an issue) gets no answer, so it stays hidden. -
Swift: the batched read also asks for
totalCommentsCount.AgentPanePullRequestcarriescomments, null when thegh pr viewfallback cannot say. -
Dead click from the #24116 audit: the whole row lights on hover, but a click on the owner chip's surroundings, or on the row's own gap and padding, did nothing. The link's hit area now covers the whole row (
::beforewith inset 0 on the positioned row), and the ⋯ button sits above it. The owner chip stays a label inside the link, so every lit pixel opens the pull request except the menu button. -
chips/pullRequestStore.tsexportsaskPullRequest,pullRequestAnswerandsubscribePullRequestsfor this many-link reader.
Changelog
- Fixed: Clicking anywhere on a lit pull request row in Changes, including its owner chip, opens it.
- Changed: Every pull request in the Inspector's Changes card shows its title, state, line counts, comments and failing checks, not only the folder's.
Tests
- New
webviews/test/changes-status.test.ts(also: the header's live checks drop a cached failing count and conflict; a late open read cannot reopen a row the chat closed, while a real reopen can):- answer-to-row mapping: draft, conflicting, breakdown, comments, failing and on-base counts, no answer;
- registration batched at the host's bound, once a minute, then one ask per link;
- the header winning field by field.
webviews/test/chat-inspector.test.tsx, new end-to-end test: a transcript with two cross-repo pull requests and an issue namedglaeda #9renders titles, states,2 failingand+608-8from the host's answers. The issue stays hidden, and there is onelink.inspectfor the whole list.webviews/test/chat-inspector-changes.test.tsx: a click on the owner chip opens the pull request, while the ⋯ button does not; the CSS rule that stretches the link over the row is present.AgentPanePullRequestInspectTests.readsTheCommentCountWhenTheReadSaysIt:totalCommentsCountis parsed and sent ascomments; null when unknown; the batched query asks for it.
Observed / Not run
- Ran
scripts/web-lint.sh(passed) andprepush-gate.py(no new violations) at 6bb7ba1cd0a. The fleetcmux-ci compile-check --appand the focused webview tests at this head are reported in a comment. - An earlier push (46c4b35) by mistake carried a base commit instead of this change. Its review found that, plus the stale-failing, late-read and selector issues above; all are fixed here.
- Not run locally:
bun testandswift test(no local test runs on this Mac); CI's webview and CmuxNext swift test jobs run them.
Adopted and finished (hb-orphan-24225, 2026-10-25)
The lane that opened this died on cmux16. I rebased it onto feat-cmux-next. The one conflict was in ChatInspector.tsx InspectorPresence: I kept base's code and re-applied this PR's statuses lines.
The old fast-gates red was the stale merge's own: its prepush gate flagged a missing Sidebar-Look: trailer on 163 base sidebar files that the old merge commits pulled in. After the rebase the PR touches no sidebar file. The next red was ours too (a visibilitychange listener the switch-fetches check forbids, and vp fmt on the new test). Both are fixed, and fast gates pass at the head.
Codex gpt-6-astra xhigh review, four rounds:
- Round 1 BLOCK at c2ebbaa:
- The state time trusted first-seen plus a fixed 60 s slack.
pr.inspectcould start a single-link GitHub read.- Refresh ran while Changes was folded or the page hidden.
- Every streamed transcript update re-registered the list.
- Round 2 BLOCK at 1552f94:
- A folded mention-only Changes could not be reopened.
- The URL list was rebuilt on every text-only update.
- Round 3 SHIP at 2ea3d1a.
- Round 4 SHIP at 1c467b31f172e6cee375d72e7e0468a4169f4bdd (the gate fix).
Each fix was red first on the fleet. At 4e46696 (tests only), changes-status had 4 failing and chat-inspector 1. At dde04b0 / 79f2466 the folded-mention test and the stable-URL test failed. All are green at 2ea3d1a and 1c467b3:
- changes-status 8, chat-inspector 25, chat-inspector-changes 6, pr-hover-card 4, bare-pr-refs 4, pr-card-content 16, transcript-pull-requests 11;
- the other Inspector suites;
tscandvp checkclean.
New Swift tests:
AgentPanePullRequestInspectTests.aCachedAskReadsOnlyTheCache;AgentReplyPullRequestsTests.aCacheOnlyAnswerStartsNoReadandanAnswerSaysWhenItsReadBegan.
They run in CI's CmuxNext swift test job.
Timers, each with its stop condition:
- the minute registration interval and the 1.5 s while-reading ask both stop when Changes folds, the Inspector closes or the component unmounts (effect cleanup);
- both skip a hidden page.
Reds left are not this PR's:
- release-notary and Guard status fail the same way on base f08f46b1cb.
- The familiar-intent, gallery mark-stale, review-link and post-status jobs failed on the Actions app's own API rate limit (HTTP 403).
Before / after
Gallery agent-pane.changes-from-cache/rows (new: a whole pane whose rows come through the real cache path, with public-safe fixtures), rendered by the gallery matrix runner on a fast-checks Linux job:
- Before: base 7f0446add53 with the same fixture file.
- After: this head.
Before, only the pull request whose title the chat's own gh pr create gave has one; the others are a bare #N with no lines or checks. After, every row has its title, state, +/-, comments and failing checks (with those also failing on base), and the issue the reply names (billing #9) has no row.
| Before (base) | After (this PR) |
|---|---|
![]() | ![]() |
![]() | ![]() |
Agent session: big-red
🤖 Generated with Claude Code



