Skip to content

fix(jetbrains): show a changes badge for uncommitted worktree work - #13636

Merged
kirillk merged 1 commit into
mainfrom
rapid-nebula
Sep 1, 2026
Merged

fix(jetbrains): show a changes badge for uncommitted worktree work#13636
kirillk merged 1 commit into
mainfrom
rapid-nebula

Conversation

@kirillk

@kirillk kirillk commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

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. WorktreeStatsDto is deliberately the PR-shaped "Files changed" number and is covered by backend tests (stats is unchanged by uncommitted edits), and GitComparison.Mode.Base also 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 — in Mode.COMPACT there 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 retained State, 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 plus onLocal, and owns the routing policy once as local / action. Both the model-side region map (activeListRegions, which is what ActiveListView.fire actually invokes) and the rendered hit cell read action, so click, cursor, tooltip, and hit region cannot disagree about which comparison the badge is describing.
  • AgentManagerPanelWorktreeRow carries dirty, including in equals/hashCode so equality still gates row rebuilds; metrics reports when either set is non-empty; onDirty now calls sync() (its old comment said rows showed nothing dirty, which is no longer true); and request() 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.open vs worktree.stats.tooltip.open); there is no visual distinction, because DiffStatBadge.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: BranchDock has a genuinely PR-dependent gap of its own (no PR plus busy or GIT_MISSING hides 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 typecheck and ./gradlew test from packages/kilo-jetbrains/ — green (884 tests). Executed by the agent.
  • Root guards bun run script/check-md-table-padding.ts and bun run script/check-opencode-annotations.ts --worktree — green; no shared upstream files touched. Executed by the agent.
  • Confirmed the diagnosis against real git in a worktree with one uncommitted edit: git diff --numstat $(git merge-base origin/main HEAD) HEAD reports 0 files (what the row badge used) while git 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-scoped worktree 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 the local tab). 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

  1. Open a JetBrains sandbox IDE with Agent Manager on a repo with at least one worktree.
  2. In a worktree with no pull request, edit a tracked file and add an untracked one; do not commit.
  3. Within one 30 s stats poll the row shows a changes badge. Hover it: the tooltip reads "Uncommitted changes. Click to compare with HEAD."
  4. Click the badge — the Local comparison tab opens, listing those files.
  5. Hover the row body: the detail popup now opens without a pull request, showing the uncommitted group and any ahead/behind counters.
  6. Commit the work. The badge flips to the committed-vs-base number, the tooltip back to "Click to open diff", and clicking opens the Branch comparison.
  7. On a clean worktree, confirm the row still shows no badge and the popup does not open.

Blocked checks and substitute verification

  • No sandbox IDE screenshot was captured, as noted above. Substitute verification is the rendered-tree label assertions in AgentManagerPanelTest and WorktreeRowPopupBodyTest, which read the actual visible JBLabel text from the real renderer output.

Checklist

  • Issue linked above, or exception explained
  • Tests/verification described
  • Screenshots/video included for visual changes, or marked N/A — not captured; see above
  • Changeset considered for user-facing changes
  • I personally reviewed the diff and can explain the changes, including any AI-assisted work.

Get in Touch

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.
@kilo-code-bot

kilo-code-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (10 files)
  • .changeset/jetbrains-uncommitted-changes-badge.md
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/agentManager/AgentManagerPanel.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/ChangesPanel.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/list/ActiveListActions.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/list/ActiveListModel.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/list/ActiveListRenderer.kt
  • packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle.properties
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/agentManager/AgentManagerPanelTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/agentManager/worktree/WorktreeRowPopupBodyTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/ui/ChangesPanelTest.kt

Reviewed by grok-4.6 · Input: 89.2K · Output: 14.3K · Cached: 580.4K

Review guidance: REVIEW.md from base branch main

@kirillk
kirillk enabled auto-merge September 1, 2026 12:23
@kirillk
kirillk merged commit a946e19 into main Sep 1, 2026
24 checks passed
@kirillk
kirillk deleted the rapid-nebula branch September 1, 2026 12:23
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.

2 participants