Skip to content

feat(desktop): show ahead/behind status on local workspace#1496

Merged
Kitenite merged 6 commits into
mainfrom
kitenite/show-behind-ahead-in-main-or-local
Feb 14, 2026
Merged

feat(desktop): show ahead/behind status on local workspace#1496
Kitenite merged 6 commits into
mainfrom
kitenite/show-behind-ahead-in-main-or-local

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Feb 14, 2026

Summary

  • Add ahead/behind commit indicators (↓N/↑N) on branch/local workspace items in the sidebar, comparing against the remote tracking branch (origin/{branch})
  • Show diff stats (additions/deletions) for local workspaces alongside the ahead/behind badge
  • Extend gitStatusSchema with optional ahead/behind fields and derive needsRebase from behind > 0

Test plan

  • Open desktop app and verify ↓N/↑N badges appear on the local/main workspace item when commits are ahead or behind remote
  • Verify badges are hidden when ahead and behind are both 0
  • Verify diff stats (file additions/deletions) still show for local workspace
  • Verify worktree workspaces are unaffected (no ahead/behind badge, existing diff stats + close button unchanged)
  • Verify hover card still shows "Behind main by N commits, needs rebase" when applicable
  • Run bun run typecheck and bun run lint:fix — both pass cleanly

Summary by CodeRabbit

  • New Features

    • Shows ahead/behind commit counts for branch workspaces in the sidebar.
    • Adds compact, color-coded ahead/behind indicators next to workspace names.
    • Hover cards now display dynamic "behind by N commits" rebase messages.
    • New workspace icons and a refined collapsed-item view for clearer status and unread indicators.
  • Refactor

    • Sidebar item rendering and drag/reorder behavior streamlined for more consistent visuals and interactions.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 14, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Adds ahead/behind commit counting to workspace git status: new git utility, backend procedure, schema fields, UI components, and sidebar integration to fetch and display ahead/behind counts and derive rebase-needed from behind.

Changes

Cohort / File(s) Summary
Git utility & schema
apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts, packages/local-db/src/schema/zod.ts
Add getAheadBehindCount(repoPath, defaultBranch) which runs git rev-list --left-right --count; extend gitStatusSchema with optional ahead and behind numeric fields.
Backend procedures & init
apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts, apps/desktop/src/lib/trpc/routers/workspaces/procedures/create.ts, apps/desktop/src/lib/trpc/routers/workspaces/utils/workspace-init.ts
Replace prior implicit rebase check with ahead/behind computation; add public getAheadBehind procedure; initialize gitStatus.ahead and gitStatus.behind (set to 0) during worktree creation/initialization; derive needsRebase from behind > 0.
Sidebar UI components & constants
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceAheadBehind.tsx, .../constants.ts
Add WorkspaceAheadBehind component and AHEAD_BEHIND_STALE_TIME constant (60_000 ms) for caching/query stale time.
Workspace list & hover UI integration
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx, .../WorkspaceHoverCard.tsx, .../CollapsedWorkspaceItem.tsx, .../WorkspaceIcon.tsx, .../WorkspaceDiffStats.tsx
Integrate ahead/behind query into branch workspaces and render WorkspaceAheadBehind; refactor collapsed/expanded rendering, DnD reorder wiring, and icon/status components; HoverCard shows dynamic rebase message using behind count.

Sequence Diagram

sequenceDiagram
    actor User
    participant UI as WorkspaceListItem / HoverCard
    participant API as trpc getAheadBehind / git-status proc
    participant Git as getAheadBehindCount
    participant DB as Local DB

    User->>UI: Hover or open workspace list
    UI->>API: request ahead/behind (workspaceId)
    API->>DB: resolve workspace & repo info
    API->>Git: getAheadBehindCount(repoPath, defaultBranch)
    Git->>Git: run `git rev-list --left-right --count origin/<defaultBranch>...HEAD`
    Git-->>API: { ahead: n, behind: m }
    API-->>UI: { ahead: n, behind: m, needsRebase: m > 0 }
    UI->>UI: render WorkspaceAheadBehind and HoverCard message
    UI-->>User: display ahead/behind indicators and rebase status
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 Hopping through branches, counting each stride,

Ahead and behind now sit side by side.
Up-arrow, down-arrow, neat numbers in sight,
My burrow of workspaces now gleams with new light.
Tiny commits counted — a rabbit's delight!

🚥 Pre-merge checks | ✅ 2 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (11 files):

⚔️ apps/desktop/src/lib/trpc/routers/workspaces/procedures/create.ts (content)
⚔️ apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts (content)
⚔️ apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts (content)
⚔️ apps/desktop/src/lib/trpc/routers/workspaces/utils/workspace-init.ts (content)
⚔️ apps/desktop/src/main/index.ts (content)
⚔️ apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx (content)
⚔️ apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/WorkspaceHoverCard/WorkspaceHoverCard.tsx (content)
⚔️ apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/constants.ts (content)
⚔️ apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/ChangesContent.tsx (content)
⚔️ apps/desktop/src/renderer/screens/main/hooks/index.ts (content)
⚔️ packages/local-db/src/schema/zod.ts (content)

These conflicts must be resolved before merging into main.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(desktop): show ahead/behind status on local workspace' directly and concisely summarizes the main change: adding ahead/behind commit indicators to workspace items in the sidebar UI.
Description check ✅ Passed The PR description provides a clear Summary section, detailed Test plan with checkboxes, and covers all key changes. However, it lacks a formal Description section and Related Issues section as specified in the template.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch kitenite/show-behind-ahead-in-main-or-local

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts`:
- Around line 85-102: The getAheadBehind procedure calls getAheadBehindCount
with repoPath: project.mainRepoPath and defaultBranch: workspace.branch but
getAheadBehindCount currently compares origin/{branch}...HEAD which is only
correct when the main repo's HEAD is on workspace.branch; change the call and/or
getAheadBehindCount signature to accept an explicit localBranch ref (e.g.,
localBranch: workspace.branch) and have getAheadBehindCount compare
origin/{branch}...refs/heads/{localBranch} (or otherwise use the explicit branch
ref instead of implicit HEAD) so the ahead/behind numbers reflect the specified
workspace.branch; keep refreshGitStatus behavior as-is since worktrees rely on
HEAD.

In `@apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts`:
- Around line 1093-1116: Remove the dead function checkNeedsRebase from the file
(it’s no longer referenced now that getAheadBehindCount drives needsRebase via
behind > 0); locate the checkNeedsRebase declaration and delete its entire
definition and any associated unused imports or helper constants only used by it
so there’s no leftover unused symbols (leave getAheadBehindCount intact).
🧹 Nitpick comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx (1)

530-543: Minor UX note: diff stats for branch workspaces appear only after hover, while ahead/behind shows immediately.

The localChanges query (Line 137) is gated by hasHovered, so WorkspaceDiffStats for branch workspaces won't render until the user has hovered at least once. This can cause a subtle layout shift when the stats badge appears. If this is intentional (keeping the sidebar compact by default), that's fine — just calling it out in case it's an oversight.

Comment on lines +85 to +102
getAheadBehind: publicProcedure
.input(z.object({ workspaceId: z.string() }))
.query(async ({ input }) => {
const workspace = getWorkspace(input.workspaceId);
if (!workspace) {
return { ahead: 0, behind: 0 };
}

const project = getProject(workspace.projectId);
if (!project) {
return { ahead: 0, behind: 0 };
}

return getAheadBehindCount({
repoPath: project.mainRepoPath,
defaultBranch: workspace.branch,
});
}),
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.

⚠️ Potential issue | 🟡 Minor

Verify that getAheadBehind semantics match the intended use for branch workspaces.

The procedure compares origin/{workspace.branch}...HEAD in the main repo. This correctly shows how the local branch relates to its remote tracking branch — but only when the main repo's HEAD is on workspace.branch. If the user switches branches externally (e.g., via terminal), HEAD will refer to a different branch and the numbers will be misleading.

Consider using an explicit branch ref instead of implicit HEAD to make this resilient:

Proposed fix

In getAheadBehindCount (or a variant), replace HEAD with the explicit branch name:

 const output = await git.raw([
   "rev-list",
   "--left-right",
   "--count",
-  `origin/${defaultBranch}...HEAD`,
+  `origin/${defaultBranch}...${defaultBranch}`,
 ]);

This would require either a separate parameter or passing the local branch name explicitly. The tradeoff is that refreshGitStatus (for worktrees) correctly uses HEAD since the worktree's HEAD is always on its branch.

🤖 Prompt for AI Agents
In `@apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts` around
lines 85 - 102, The getAheadBehind procedure calls getAheadBehindCount with
repoPath: project.mainRepoPath and defaultBranch: workspace.branch but
getAheadBehindCount currently compares origin/{branch}...HEAD which is only
correct when the main repo's HEAD is on workspace.branch; change the call and/or
getAheadBehindCount signature to accept an explicit localBranch ref (e.g.,
localBranch: workspace.branch) and have getAheadBehindCount compare
origin/{branch}...refs/heads/{localBranch} (or otherwise use the explicit branch
ref instead of implicit HEAD) so the ahead/behind numbers reflect the specified
workspace.branch; keep refreshGitStatus behavior as-is since worktrees rely on
HEAD.

Comment on lines +1093 to +1116
export async function getAheadBehindCount({
repoPath,
defaultBranch,
}: {
repoPath: string;
defaultBranch: string;
}): Promise<{ ahead: number; behind: number }> {
const git = simpleGit(repoPath);
try {
const output = await git.raw([
"rev-list",
"--left-right",
"--count",
`origin/${defaultBranch}...HEAD`,
]);
const [behindStr, aheadStr] = output.trim().split(/\s+/);
return {
ahead: Number.parseInt(aheadStr || "0", 10),
behind: Number.parseInt(behindStr || "0", 10),
};
} catch {
return { ahead: 0, behind: 0 };
}
}
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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Search for all references to checkNeedsRebase in the codebase
rg -n 'checkNeedsRebase' --type=ts --type=tsx

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

# Check the git.ts file for `any` type usage and other potential issues
head -100 apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts

Repository: superset-sh/superset

Length of output: 2626


🏁 Script executed:

# Check if the file uses proper aliases as defined in tsconfig
grep -n "import.*from" apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts | head -20

Repository: superset-sh/superset

Length of output: 612


🏁 Script executed:

# Search for all references to checkNeedsRebase without file type restriction
rg -n 'checkNeedsRebase' apps/desktop/src/lib/trpc/routers/workspaces/utils/

Repository: superset-sh/superset

Length of output: 166


🏁 Script executed:

# Read lines 1070-1120 to see checkNeedsRebase and the new function in context
sed -n '1070,1120p' apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts

Repository: superset-sh/superset

Length of output: 1144


🏁 Script executed:

# Search for any type usage in git.ts
rg -n ': any' apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts

Repository: superset-sh/superset

Length of output: 46


🏁 Script executed:

# Check if checkNeedsRebase is exported from git.ts and imported elsewhere
rg -n 'export.*checkNeedsRebase|import.*checkNeedsRebase' apps/desktop/

Repository: superset-sh/superset

Length of output: 166


🏁 Script executed:

# Search entire repository for checkNeedsRebase imports/usage (not just definition)
rg -n 'checkNeedsRebase' --type ts

Repository: superset-sh/superset

Length of output: 166


🏁 Script executed:

# Specifically check git-status.ts mentioned in the review
find apps/desktop -name "git-status.ts" -o -name "*git-status*" | head -5

Repository: superset-sh/superset

Length of output: 133


🏁 Script executed:

# Look for any references to checkNeedsRebase in the whole repo
rg 'checkNeedsRebase' -g '!git.ts'

Repository: superset-sh/superset

Length of output: 46


🏁 Script executed:

# Check git-status.ts to see how getAheadBehindCount is used
cat -n apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts | head -50

Repository: superset-sh/superset

Length of output: 1817


🏁 Script executed:

# Also search git-status.ts for any references to checkNeedsRebase or needsRebase
rg -n 'needsRebase|checkNeedsRebase' apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts

Repository: superset-sh/superset

Length of output: 96


Remove the checkNeedsRebase function as dead code.

The getAheadBehindCount function correctly parses --left-right --count output, mapping the first column (left/remote) to behind and the second column (right/HEAD) to ahead. Silent error fallback to {0, 0} is reasonable for a non-critical status indicator.

The existing checkNeedsRebase function (line 1080) is no longer used—git-status.ts now derives needsRebase from behind > 0 using the new getAheadBehindCount. Remove this function to avoid confusion.

🤖 Prompt for AI Agents
In `@apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts` around lines 1093
- 1116, Remove the dead function checkNeedsRebase from the file (it’s no longer
referenced now that getAheadBehindCount drives needsRebase via behind > 0);
locate the checkNeedsRebase declaration and delete its entire definition and any
associated unused imports or helper constants only used by it so there’s no
leftover unused symbols (leave getAheadBehindCount intact).

Extract WorkspaceIcon and CollapsedWorkspaceItem to reduce
the main component from ~700 to ~470 lines. Deduplicate
icon rendering, consolidate context menu items, and simplify
drag-and-drop handler logic.
@Kitenite Kitenite merged commit c3a1b5d into main Feb 14, 2026
5 of 6 checks passed
@Kitenite Kitenite deleted the kitenite/show-behind-ahead-in-main-or-local branch February 14, 2026 10:13
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 14, 2026

🚀 Preview Deployment

🔗 Preview Links

Service Status Link
Neon Database (Neon) View Branch
Fly.io Electric (Fly.io) View App
Fly.io Streams (Fly.io) View App
Vercel API (Vercel) Open Preview
Vercel Web (Vercel) Open Preview
Vercel Marketing (Vercel) Open Preview
Vercel Admin (Vercel) Open Preview
Vercel Docs (Vercel) Open Preview

Preview updates automatically with new commits

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.

1 participant