Skip to content

feat(sidebar): show worktree health (ahead/behind, dirty count, last-commit age)#157

Open
liam-russell wants to merge 8 commits into
mainfrom
feat/sidebar-worktree-health
Open

feat(sidebar): show worktree health (ahead/behind, dirty count, last-commit age)#157
liam-russell wants to merge 8 commits into
mainfrom
feat/sidebar-worktree-health

Conversation

@liam-russell

@liam-russell liam-russell commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Type

  • feat — new feature or user-visible capability

Summary

  • Each worktree row in the sidebar now shows ahead/behind counts vs. its upstream (or the repo's default branch), a dirty-file count, and how long ago its last commit was made.

Why

Closes #88 — the sidebar could only show a live-agent badge, so it couldn't answer "which worktree needs my attention?" without opening each one.

Screenshots / recordings

Verified locally via a WebdriverIO screenshot of a worktree row showing all three new badges together: an "↑1" ahead badge, a "1" dirty-file-count badge, and a "🕐 just now" last-commit-age badge, alongside the existing branch name and type label. (Not attaching an image file here since I don't have a way to upload one through this CLI-only session — happy to add one if a maintainer prefers.)

Breaking changes

None

Test plan

  • Added getWorktreeHealth/getWorktreesHealth unit tests in packages/git/src/__tests__/health.test.ts (6 tests, all passing) covering: no-remote fallback, dirty-file counting, real ahead/behind against a configured upstream, and falling back to a base ref when there's no upstream.
  • Added e2e/specs/worktree-health.spec.ts (3 tests, all passing) covering the last-commit-age badge, the ahead badge with a real unpushed commit, and the dirty-count badge.
  • pnpm lint && pnpm typecheck && pnpm test all pass (including the full e2e suite).
  • Manually verified the rendered badges via a local WebdriverIO/Electron screenshot.

…commit age)

Adds getWorktreeHealth/getWorktreesHealth to @sproutgit/git (ahead/behind
vs. upstream or the repo's default branch, dirty-file count, last-commit
timestamp), exposed via a new batched worktree:healthBatch IPC channel and
rendered as compact badges on each worktree-item row in the sidebar.

Closes #88

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 6, 2026 21:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a “worktree health” snapshot pipeline to support new sidebar badges indicating attention-needed status per worktree (ahead/behind vs upstream or default remote branch, dirty-file count, and last-commit recency).

Changes:

  • Introduces WorktreeHealth typing and a new batched IPC endpoint to fetch health for multiple worktrees at once.
  • Implements git-side health computation (getWorktreeHealth / getWorktreesHealth) with concurrency limiting plus unit coverage.
  • Renders new ahead/behind and last-commit-age badges in the worktree sidebar and wires periodic fetching + invalidation into the workspace route; adds e2e coverage for the badges.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/types/src/ipc.ts Adds new IPC route typing for batched worktree health.
packages/types/src/git.ts Defines the WorktreeHealth data contract.
packages/git/src/index.ts Re-exports new health helpers from the git package.
packages/git/src/health.ts Implements health computation + batched concurrency-limited aggregation.
packages/git/src/tests/health.test.ts Unit tests for upstream/baseRef behavior, dirty counting, and batch semantics.
packages/git/package.json Exposes ./health export entry.
e2e/specs/worktree-health.spec.ts E2E coverage for new sidebar badges.
app/src/renderer/workspace/WorktreeSidebar.tsx Renders ahead/behind + last-commit badges and adds test IDs.
app/src/renderer/routes/workspace.tsx Fetches/propagates health data and invalidates it on relevant events.
app/src/renderer/queries.ts Adds useWorktreeHealth query + query key.
app/src/preload/index.ts Adds preload API surface for the new batched IPC call.
app/src/main/ipc/git.ts Registers IPC handler that chooses baseRef and computes batched health.

Comment thread packages/git/src/health.ts Outdated
baseRef?: string | null,
concurrency = DEFAULT_CONCURRENCY
): Promise<Record<string, WorktreeHealth>> {
const result: Record<string, WorktreeHealth> = {};
Comment thread packages/git/src/health.ts Outdated
Comment on lines +87 to +88
const workerCount = Math.min(concurrency, worktreePaths.length);
await Promise.all(Array.from({ length: workerCount }, worker));
Comment thread app/src/renderer/routes/workspace.tsx Outdated
Comment on lines 428 to 434
{changeCount > 0 && (
<span className="shrink-0 rounded-full bg-(--sg-warning)/20 px-1.5 py-0 text-[9px] leading-4 font-semibold text-(--sg-warning)">
<span
className="shrink-0 rounded-full bg-(--sg-warning)/20 px-1.5 py-0 text-[9px] leading-4 font-semibold text-(--sg-warning)"
data-testid="worktree-dirty-count"
>
{changeCount}
</span>
Comment thread packages/types/src/ipc.ts Outdated
'worktree:getMeta': { args: [args: { workspacePath: string; worktreePath: string }]; result: WorktreeMetaRow | null };
'worktree:setMeta': { args: [args: { workspacePath: string; worktreePath: string; branch?: string; sourceRef?: string; rootRepoPath?: string; issueRef?: string | null; issueTitle?: string | null }]; result: void };
'worktree:pruneMetadata': { args: [args: { workspacePath: string; activeWorktreePaths: string[] }]; result: void };
'worktree:healthBatch': { args: [args: { repoPath: string; worktreePaths: string[] }]; result: Record<string, WorktreeHealth> };
…-health

# Conflicts:
#	app/src/renderer/queries.ts
#	app/src/renderer/routes/workspace.tsx
#	app/src/renderer/workspace/WorktreeSidebar.tsx
@liam-russell

Copy link
Copy Markdown
Contributor Author

Addressed all 5 review findings:

  1. Prototype pollution (health.ts): getWorktreesHealth's result map now uses Object.create(null) instead of a plain object literal.
  2. Concurrency clamp (health.ts): getWorktreesHealth now clamps workerCount to at least 1 whenever there's work to do, so concurrency <= 0 no longer silently returns an empty result.
  3. Dead invalidation (now in useWorkspaceFileWatchers.ts after rebasing onto the recent workspace.tsx refactor): removed the no-op qk.worktreeChangeCounts(gitRepoPath) invalidation — useWorktreeChangeCounts actually keys its queries by qk.worktreeStatus(wt.path) per worktree, which is already invalidated on the line above.
  4. Duplicate git status work: rather than switching the sidebar's dirty badge over to health.dirtyCount (which would mean removing the existing per-worktree status polling — a much larger, riskier change to code this PR doesn't otherwise touch), I removed dirtyCount from WorktreeHealth/getWorktreeHealth entirely. The sidebar's dirty-count badge already comes from the pre-existing useWorktreeChangeCounts/worktreeChangeCounts prop, so there's no duplication and no functional loss — getWorktreeHealth now only computes ahead/behind + last-commit-age, which is exactly what wasn't already available.
  5. Partial<Record> for possibly-missing entries: worktree:healthBatch's result type, the preload wrapper, and the useWorktreeHealth/WorktreeSidebar types now all use Partial<Record<string, WorktreeHealth>> to reflect that a worktree can be absent from the map.

Also rebased onto main to pick up the concurrent GitHub PR-status sidebar feature and the workspace.tsx hooks refactor — re-integrated useWorktreeHealth against the new useWorkspaceFileWatchers hook rather than line-merging the two, since the structural refactor made a textual 3-way merge unreliable. All unit tests (packages/git 76/76, app 310/310) and the new/updated e2e specs pass after the rebase.

liam-russell and others added 6 commits July 7, 2026 20:20
The clone in the ahead/behind upstream test doesn't inherit user.name/
user.email from the seed repo, and CI runners have no global git identity
configured — committing there failed with "Please tell me who you are"
on ubuntu-latest and windows-latest (passed locally/on macOS only because
those already had a global identity set).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
These edits (Object.create(null) for the health-batch result map, the
concurrency clamp, dropping the redundant dirtyCount computation, and the
Partial<Record<>> type for possibly-missing health entries) were made
mid-merge but never staged, so the merge commit landed without them —
CI's concurrency=0 test failure surfaced that the clamp fix was actually
still missing from what got pushed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…-health

# Conflicts:
#	app/src/renderer/queries.ts
#	app/src/renderer/workspace/WorktreeSidebar.tsx
…-health

# Conflicts:
#	app/src/renderer/queries.ts
#	packages/types/src/ipc.ts
…-health

# Conflicts:
#	app/src/preload/index.ts
#	packages/git/package.json
#	packages/git/src/index.ts
#	packages/types/src/git.ts
#	packages/types/src/ipc.ts
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.

Show worktree health in the sidebar: ahead/behind, dirty count, last-commit age

2 participants