Skip to content

fix(desktop): sync workspace branch names and hover card branch display#1198

Merged
saddlepaddle merged 5 commits into
superset-sh:mainfrom
ascrazy:codex/branch-sync
Feb 4, 2026
Merged

fix(desktop): sync workspace branch names and hover card branch display#1198
saddlepaddle merged 5 commits into
superset-sh:mainfrom
ascrazy:codex/branch-sync

Conversation

@ascrazy
Copy link
Copy Markdown
Contributor

@ascrazy ascrazy commented Feb 4, 2026

Description

Syncs workspace branch names from git status to avoid stale branch labels after external git switch, and fixes hover card to show the actual branch (separate from worktree name).

Related Issues

Type of Change

  • Bug fix
  • [-] New feature
  • [-] Documentation
  • [-] Refactor
  • [-] Other (please describe):

Testing

  • cd apps/desktop && SKIP_ENV_VALIDATION=1 bun run dev
  • Add a workspace and create a worktree
  • Open a worktree and a terminal tab inside it
  • git switch -c test-new-branch in the terminal tab
  • Hover on the worktree in the sidebar
  • Branch name on the worktree item in sidebar should update
  • Worktree hover card should show correct branch name

Screenshots (if applicable)

Before After
ezgif-4747c1f8969e6b6f ScreenRecording2026-02-04at21 10 20-ezgif com-video-to-gif-converter

Additional Notes

  • Hover card now uses branchName from getWorktreeInfo; worktreeName remains the path segment for compatibility.
  • Branch updates are persisted to local DB on changes.getStatus.

Summary by CodeRabbit

  • New Features

    • Automatic branch synchronization keeps workspace branch state aligned with the active Git branch.
    • Workspace info now surfaces branch name; hover cards and views display the branch.
  • Bug Fixes / Stability

    • UI now refreshes related views and clears stale data when branches diverge to prevent inconsistent displays.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 4, 2026

📝 Walkthrough

Walkthrough

Adds DB-side synchronization to align worktrees and branch-type workspaces with the active Git branch; surfaces branchName from worktree info; and introduces a hook with UI calls to invalidate workspace/worktree caches when local Git branch differs from a workspace branch.

Changes

Cohort / File(s) Summary
Backend Branch Synchronization
apps/desktop/src/lib/trpc/routers/changes/status.ts
Adds syncWorkspaceBranch(worktreePath, currentBranch) and invokes it from getStatus; updates worktrees.branch and related workspaces.branch (or project-level branch workspaces) when mismatches are detected; errors are caught and logged.
TRPC API Extension
apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts
getWorktreeInfo now returns branchName (from worktree.branch) in addition to existing fields.
UI Hooks & Exports
apps/desktop/src/renderer/screens/main/hooks/useBranchSyncInvalidation/useBranchSyncInvalidation.ts, .../index.ts, apps/desktop/src/renderer/screens/main/hooks/index.ts
Adds useBranchSyncInvalidation hook and re-exports it; the hook invalidates grouped/workspace/getWorktreeInfo queries when gitBranch differs from workspaceBranch.
Sidebar: List Item Invalidation
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
Invokes useBranchSyncInvalidation with gitBranch, workspaceBranch, and workspaceId to trigger cache invalidation on branch divergence.
Hover Card Display
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/.../WorkspaceHoverCard.tsx
Displays branchName (from worktreeInfo) instead of worktreeName in the Branch section and in repo/tree links.
Changes View Invalidation
apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx
Calls useBranchSyncInvalidation to invalidate caches when gitBranch and workspaceBranch diverge.

Sequence Diagram

sequenceDiagram
    participant UI as Renderer/UI
    participant Status as getStatus Handler
    participant Git as Git Ops
    participant DB as Local DB
    participant Cache as Query Cache

    UI->>Status: request worktree status
    Status->>Git: run git status / parse branch
    Git-->>Status: return parsed.branch
    Status->>DB: syncWorkspaceBranch(worktreePath, parsed.branch)
    DB->>DB: check worktree entry & project workspaces
    alt worktree exists
        DB->>DB: update worktrees.branch (if mismatched)
        DB->>DB: update related workspaces.branch (if mismatched)
    else worktree not found
        DB->>DB: find project by mainRepoPath
        DB->>DB: update project's branch-type workspaces (if mismatched)
    end
    DB-->>Status: sync complete (errors logged)
    Status-->>UI: return status (includes branch)
    UI->>Cache: hook detects branch divergence
    Cache->>Cache: invalidate grouped workspaces, workspace query, worktree info
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • CharlieHelps

Poem

🐇 I nudged the DB to follow Git's soft tune,
Branches aligned beneath the silver moon.
Worktrees whisper, workspaces reply,
Caches wake and flutter — hop, sync, and sigh.
🌿 — a little rabbit with a commit in its eye.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main changes: syncing workspace branch names and fixing hover card branch display to show the correct branch.
Description check ✅ Passed The description includes all required sections with clear explanations, related issues acknowledgment, type of change, comprehensive testing steps, and screenshots.

✏️ 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

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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/desktop/src/lib/trpc/routers/changes/status.ts (1)

253-253: ⚠️ Potential issue | 🟡 Minor

Empty catch block silently swallows errors.

The getBranchComparison function has an empty catch block that swallows all errors without any logging. This violates the guideline to never silently swallow errors. At minimum, log the error with context.

🛡️ Proposed fix
-  } catch {}
+  } catch (error) {
+    console.warn("[changes/status] Failed to get branch comparison:", error);
+  }

As per coding guidelines: "Never silently swallow errors with catch(() => {}) or catch(e) { return null }" and "Log errors at minimum if not re-throwing or explicitly handling them".

🧹 Nitpick comments (2)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx (1)

73-79: Consider narrowing the workspace dependency to specific fields.

Using the entire workspace object as a dependency means this effect will re-run whenever any workspace property changes, not just when the branch comparison is relevant. While the early return guards prevent unnecessary invalidations, the effect still executes on every workspace update.

♻️ Suggested improvement
 useEffect(() => {
   if (!workspace || !status?.branch || status.branch === "HEAD") return;
   if (workspace.branch !== status.branch) {
     utils.workspaces.getAllGrouped.invalidate();
     utils.workspaces.get.invalidate({ id: workspace.id });
   }
-}, [status?.branch, utils, workspace]);
+}, [status?.branch, utils, workspace?.id, workspace?.branch]);
apps/desktop/src/lib/trpc/routers/changes/status.ts (1)

36-39: Synchronous DB operations in async query handler.

syncWorkspaceBranch performs synchronous database operations (.run()) within an async tRPC query. While this works correctly with better-sqlite3's synchronous API, be aware this blocks the Node.js event loop briefly during each DB write. For the current use case with small, fast updates this is acceptable, but monitor if status queries become slow under load.

Copy link
Copy Markdown
Collaborator

@saddlepaddle saddlepaddle left a comment

Choose a reason for hiding this comment

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

Code Review: fix(desktop): sync workspace branch names and hover card branch display

Summary

The PR addresses a real UX bug — stale branch labels after external git switch. The approach is sound: syncWorkspaceBranch runs synchronously inside the getStatus tRPC query to update the local DB, and React effects on the client detect the mismatch to trigger cache invalidation.

Overall this is clean and well-structured. A few items to address below.


Key Observations

syncWorkspaceBranch is synchronous, not fire-and-forget. Since better-sqlite3 operations (.get(), .run(), .all()) are synchronous, the function executes fully before the subsequent Promise.all. This is correct — no await is needed and there's no race condition with the response.

Detached HEAD guard is good. The early return for currentBranch === "HEAD" correctly avoids clobbering stored branch names during detached HEAD states.

Error handling is appropriate. The try/catch with console.warn ensures a failed sync never breaks the status query. Correct tradeoff for a best-effort sync.


Issues

# Severity File Issue
1 Medium WorkspaceListItem.tsx, ChangesView.tsx Duplicate cache invalidation logic — extract to shared hook
2 Low ChangesView.tsx:81 Over-broad workspace object in useEffect dependency array
3 Nit status.ts Read-before-update pattern queries workspaces twice (once to check, once to update) — can be simplified

See inline comments for details.

utils.workspaces.getWorktreeInfo.invalidate({
workspaceId: workspace.id,
});
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Medium: Duplicate invalidation logic + over-broad dependency

Two issues here:

1. Duplicate logic (Medium): This useEffect is nearly identical to the one in WorkspaceListItem.tsx:138-147. Both detect branch mismatches and invalidate the same set of queries. Consider extracting a shared hook:

// e.g., hooks/useBranchSyncInvalidation.ts
function useBranchSyncInvalidation({
  gitBranch,
  workspaceBranch,
  workspaceId,
}: {
  gitBranch: string | undefined;
  workspaceBranch: string | undefined;
  workspaceId: string;
}) {
  const utils = electronTrpc.useUtils();
  useEffect(() => {
    if (!gitBranch || gitBranch === "HEAD" || !workspaceBranch) return;
    if (gitBranch !== workspaceBranch) {
      utils.workspaces.getAllGrouped.invalidate();
      utils.workspaces.get.invalidate({ id: workspaceId });
      utils.workspaces.getWorktreeInfo.invalidate({ workspaceId });
    }
  }, [gitBranch, workspaceBranch, workspaceId, utils]);
}

2. Over-broad dependency (Low): Using the full workspace object as a dependency means this effect re-evaluates on any workspace property change (e.g., updatedAt, lastOpenedAt). The guards prevent unnecessary invalidations, but narrowing prevents unnecessary effect executions:

-}, [status?.branch, utils, workspace]);
+}, [status?.branch, utils, workspace?.id, workspace?.branch]);


const hasWorkspaceMismatch = workspacesForWorktree.some(
(ws) => ws.branch !== currentBranch,
);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: Redundant read-before-update pattern

The function reads workspaces to check for mismatches, then runs the same WHERE clause again in the UPDATE. Since the UPDATE is a no-op when there are no matching rows, you could skip the read step:

// Instead of read-check-update, just update directly:
localDb
  .update(workspaces)
  .set({ branch: currentBranch })
  .where(
    and(
      eq(workspaces.worktreeId, worktree.id),
      isNull(workspaces.deletingAt),
      not(eq(workspaces.branch, currentBranch)),
    ),
  )
  .run();

This avoids the extra SELECT on every status poll. The same pattern applies to the project-level branch workspace update below (lines 178-202).

Not blocking — the current approach is clear and correct, this is just a performance nit since getStatus is polled every 2.5s.

utils.workspaces.get.invalidate({ id });
utils.workspaces.getWorktreeInfo.invalidate({ workspaceId: id });
}
}, [localChanges?.branch, branch, id, utils]);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Note: This effect only fires after hover (because localChanges depends on hasHovered), so it handles the case where the user externally switches branches in a non-active workspace. For the active workspace, ChangesView.tsx handles it via its 2.5s polling.

See inline comment on ChangesView.tsx regarding extracting a shared hook to deduplicate this logic.

ascrazy and others added 5 commits February 4, 2026 14:34
- Extract duplicate useEffect cache invalidation logic from
  WorkspaceListItem and ChangesView into shared useBranchSyncInvalidation hook
- Narrow useEffect dependencies to specific fields instead of full objects
- Simplify syncWorkspaceBranch by using direct UPDATE with not(eq()) clause
  instead of SELECT-then-conditionally-UPDATE pattern, reducing DB queries
  on every 2.5s status poll
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