fix(jetbrains): show a changes badge for uncommitted worktree work - #13636
Merged
Conversation
An Agent Manager worktree row measured its changes with WorktreeStatsDto, which counts only what is committed against the base branch. A worktree whose agent has not committed yet therefore reported zero files and the badge hid, which reads as "this worktree changed nothing". That is also why the badge looked like it depended on a pull request: nothing gates it on one, but a worktree with no pull request is exactly the worktree whose work is still uncommitted. Fall back to the uncommitted counts when nothing is committed, routing the click to the Local comparison so it opens the files the badge just counted. Keep the committed number when there is one: it is what a pull request would show. The row detail popup, the only place that breaks the two sets apart, no longer requires a pull request to open either. Backend stats keep their meaning, so the "Files changed" semantics and the BASE diff editor are untouched.
Contributor
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Files Reviewed (10 files)
Reviewed by grok-4.6 · Input: 89.2K · Output: 14.3K · Cached: 580.4K Review guidance: REVIEW.md from base branch |
marius-kilocode
approved these changes
Sep 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue
No existing issue — reported directly ("for a worktree with no PRs I don't see a changes badge").
Context
An Agent Manager worktree row measured its changes with
WorktreeStatsDto, which counts only what is committed against the base branch (git diff <merge-base> HEAD, untracked skipped). A worktree whose agent has not committed yet therefore reported zero files and the badge hid — which reads as "this worktree changed nothing", the opposite of what the row is being asked.That is also why the badge looked like it depended on a pull request. Nothing gates it on one; the correlation is indirect. A worktree with no pull request is exactly the worktree whose work is still uncommitted. The uncommitted counts already existed (
WorktreeDirtyDto, polled every 30 s beside the stats) but never reached the row, and the one surface that did show them — the row hover popup — returned early when there was no pull request, so such a row had no changes information anywhere.VS Code does not have this gap: it measures merge-base against the working tree plus untracked files, so the same worktree shows a badge there. This closes that divergence.
Implementation
The backend keeps its meaning.
WorktreeStatsDtois deliberately the PR-shaped "Files changed" number and is covered by backend tests (stats is unchanged by uncommitted edits), andGitComparison.Mode.Basealso feeds the BASE diff editor, whose contents are asserted to exclude working-tree drift. Changing it would have flipped all of that, so the fix is entirely frontend.ChangesPanel— inMode.COMPACTthere is only one group, so when nothing is committed but there is uncommitted work, that group renders the uncommitted counts and switches to the uncommitted tooltip. The counts it drops in the other case stay out of the retainedState, so an unrelated dirty poll still cannot repaint the row (covered by an existing no-repaint test, plus a new one).ActiveListMetrics— gains the uncommitted triple plusonLocal, and owns the routing policy once aslocal/action. Both the model-side region map (activeListRegions, which is whatActiveListView.fireactually invokes) and the rendered hit cell readaction, so click, cursor, tooltip, and hit region cannot disagree about which comparison the badge is describing.AgentManagerPanel—WorktreeRowcarriesdirty, including inequals/hashCodeso equality still gates row rebuilds;metricsreports when either set is non-empty;onDirtynow callssync()(its old comment said rows showed nothing dirty, which is no longer true); andrequest()opens the hover popup for any row with a pull request, committed changes, uncommitted changes, or commit counters, instead of requiring a pull request. It still returns null for a busy row and for a row with nothing to say, so the popup does not follow the pointer down a list of untouched worktrees as an empty balloon.Worth a look: the badge silently changes what it measures between the two states. The tooltip is what carries that (
worktree.dirty.tooltip.openvsworktree.stats.tooltip.open); there is no visual distinction, becauseDiffStatBadge.fill— which is how the popup distinguishes the two groups — is a constructor val and making it mutable felt like more than this fix needs. Happy to add it if reviewers disagree.Also deliberately left alone:
BranchDockhas a genuinely PR-dependent gap of its own (no PR plusbusyorGIT_MISSINGhides the summary even with real changes). Different surface, separate change.Screenshots / Video
Not captured — no sandbox IDE screenshot was taken for this change. The visible states are asserted in code instead: the fallback renders
["3 files", "-1", "+2"]in the real rendered row tree, and the popup's counter row renders["4 files", "-2", "+6", "1", "2"]with no PR chrome. If a screenshot is wanted before merge, say so and I will run a sandbox IDE and attach before/after.How to Test
Manual/local verification
./gradlew typecheckand./gradlew testfrompackages/kilo-jetbrains/— green (884 tests). Executed by the agent.bun run script/check-md-table-padding.tsandbun run script/check-opencode-annotations.ts --worktree— green; no shared upstream files touched. Executed by the agent.git diff --numstat $(git merge-base origin/main HEAD) HEADreports 0 files (what the row badge used) whilegit diff --numstat $(git merge-base origin/main HEAD)reports 1 (what VS Code uses). Executed by the agent.New/changed automated coverage, all exercising the real Swing tree and the real list hit-testing rather than mocks:
AgentManagerPanelTest— re-scopedworktree rows prefer base files and fall back to uncommitted ones(committed wins, uncommitted stands in, ahead/behind alone and a clean worktree still show nothing, hit regions match);the changes badge opens whichever comparison it is showing;the uncommitted badge says so and reaches its own comparison through the list(renders the counts, hand cursor, uncommitted tooltip, real click opens thelocaltab). The busy-row test still asserts no metrics and no hit region.ChangesPanelTest— compact fallback counts, tooltip, and precedence; compact ignores uncommitted counts it is not showing (0 repaints, 0 invalidations over 100 updates).WorktreeRowPopupBodyTest— a worktree with no pull request still breaks its changes out, with no state pill and no title.Reviewer test steps
Blocked checks and substitute verification
AgentManagerPanelTestandWorktreeRowPopupBodyTest, which read the actual visibleJBLabeltext from the real renderer output.Checklist
Get in Touch