Skip to content

fix(desktop): fix git state mapping to wrong workspace in sidebar#1453

Merged
saddlepaddle merged 1 commit into
mainfrom
kitenite/sidebar-not-mapping-correctly
Feb 14, 2026
Merged

fix(desktop): fix git state mapping to wrong workspace in sidebar#1453
saddlepaddle merged 1 commit into
mainfrom
kitenite/sidebar-not-mapping-correctly

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Feb 13, 2026

Summary

  • Root cause: syncWorkspaceBranch inside the getStatus query used path-based worktree lookup instead of workspace ID, which could match the wrong DB record (no unique constraint on worktrees.path). This ran as a side effect on every 2.5s poll and every hover.
  • Cascade: The mismatch triggered useBranchSyncInvalidation to call invalidate(getAllGrouped), re-rendering all sidebar items and triggering accumulated queries in a loop — causing flickering diffs and wrong branch labels.
  • Fix: Moved branch syncing to a dedicated syncBranch mutation using workspace ID, and replaced nuclear cache invalidation with in-place cache patching.

Changes

  • changes/status.ts: Removed syncWorkspaceBranch function and its call from getStatus — query is now a pure read with no DB side effects
  • workspaces/procedures/status.ts: Added syncBranch mutation that looks up workspace by ID and updates both workspaces.branch and worktrees.branch
  • useBranchSyncInvalidation.ts: Rewrote to call syncBranch mutation, patch getAllGrouped cache in-place via setData, and only invalidate the specific workspace's queries (with dedup guard via syncingRef)

Test Plan

  • Open app with multiple worktree workspaces for the same project
  • Externally git switch <branch> in one worktree — sidebar branch label should update correctly
  • Other workspaces should NOT change their branch labels
  • Diff stats should stay stable (no flickering)
  • Switch between workspaces — ChangesView should show correct data

Summary by CodeRabbit

  • New Features

    • Added an explicit branch sync action to reliably update workspace and worktree branch state when needed.
  • Refactor

    • Removed implicit local-database side effects from status checks; status now proceeds without auto-syncing.
    • Sync now uses a guarded, mutation-driven flow that updates cached workspace state on change.
  • Bug Fixes

    • Prevent duplicate concurrent branch syncs; only trigger sync when git and workspace branches differ.
  • Chores

    • Adjusted default path for local Superset database clone.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 13, 2026

📝 Walkthrough

Walkthrough

Removed implicit local-DB branch synchronization from the changes status flow, added an explicit syncBranch RPC to update workspace (and worktree) branch records, updated the client hook to call that mutation when local git and workspace branches diverge, and adjusted a superset setup path.

Changes

Cohort / File(s) Summary
Changes status endpoint
apps/desktop/src/lib/trpc/routers/changes/status.ts
Removed local-DB sync imports and the syncWorkspaceBranch invocation; eliminated the preliminary DB synchronization step before status retrieval and minor formatting tweak in error handling.
Workspace status router
apps/desktop/src/lib/trpc/routers/workspaces/procedures/status.ts
Added syncBranch RPC mutation: validates branch, fetches workspace, updates workspace.branch and (when applicable and different) worktree.branch, and returns standardized success/changed/no-change responses.
Client branch-sync hook
apps/desktop/src/renderer/screens/main/hooks/useBranchSyncInvalidation/useBranchSyncInvalidation.ts
Introduced a useRef syncing guard and mutation-driven flow that calls syncBranch.mutate when gitBranchworkspaceBranch; updates cached grouped workspace data and invalidates related queries on change.
Superset setup script
.superset/setup.sh
Changed source path used in local DB cloning from $HOME/.superset-dev/local.db to $HOME/.superset/local.db in step_clone_local_db.

Sequence Diagram

sequenceDiagram
    actor User
    participant Hook as "useBranchSyncInvalidation\n(Hook)"
    participant Client as "TRPC Client"
    participant Server as "syncBranch RPC\n(Server)"
    participant DB as "Database"

    User->>Hook: Git branch changes
    Hook->>Hook: Detect branch mismatch (gitBranch ≠ workspaceBranch)
    alt Guard active or branches match
        Hook->>Hook: Skip sync
    else Proceed
        Hook->>Hook: Set syncingRef
        Hook->>Client: mutate(workspaceId, branch)
        Client->>Server: syncBranch request
        Server->>Server: Validate branch
        alt Invalid branch
            Server-->>Client: { success: false, reason: "invalid-branch" }
        else Valid branch
            Server->>DB: Fetch workspace
            alt Not found
                Server-->>Client: { success: false, reason: "not-found" }
            else Found
                alt Branch unchanged
                    Server-->>Client: { success: true, changed: false }
                else Branch differs
                    Server->>DB: Update workspace.branch
                    opt workspace has worktreeId
                        Server->>DB: Update worktree.branch
                    end
                    Server-->>Client: { success: true, changed: true }
                end
            end
        end
        Client->>Hook: Mutation settled
        alt changed = true
            Hook->>Hook: Update cached workspace groups
            Hook->>Hook: Invalidate related queries
        end
        Hook->>Hook: Clear syncingRef
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

Poem

🐰
I hop where branches once auto-tuned,
Now I nudge with a mutation—sync attuned.
Guard set, cache spruced, no silent surprise,
A tidy hop where alignment lies. ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main fix: correcting incorrect git state mapping to wrong workspace in the sidebar, which is the core issue being resolved.
Description check ✅ Passed The description is comprehensive and covers the summary, root cause, cascade effect, changes, and test plan. While some template sections (Related Issues, Screenshots) are not filled, the core content is complete and well-structured.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ 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/sidebar-not-mapping-correctly

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
apps/desktop/src/lib/trpc/routers/changes/status.ts (1)

162-162: Empty catch block silently swallows errors.

The coding guidelines state to never swallow errors silently. While skipping unreadable files is reasonable, a debug-level log would help diagnose issues with untracked file stat/read failures.

That said, this is a pre-existing pattern (only the comment inside the catch was removed), so this is a nit rather than something blocking this PR.

Optional: add minimal logging
-		} catch {}
+		} catch (err) {
+			console.debug("[changes/status] failed to read untracked file", file.path, err);
+		}
apps/desktop/src/renderer/screens/main/hooks/useBranchSyncInvalidation/useBranchSyncInvalidation.ts (2)

14-49: syncingRef is not cleared on the success-with-change path — relies on indirect cache update.

When the mutation succeeds and result.changed is true, syncingRef.current stays set to gitBranch. It only gets cleared when the cache patch/invalidation causes workspaceBranch to update and the effect re-runs at Line 54–56.

This works as designed — it prevents duplicate fire while waiting for the workspace data to propagate. However, if the cache patch or invalidation doesn't reach the component providing workspaceBranch (e.g., the parent reads from a different query), syncing will be permanently blocked for that branch.

Consider adding a safety net:

Optional: clear ref in onSuccess after cache updates as a safety fallback
 					utils.workspaces.getWorktreeInfo.invalidate({
 						workspaceId,
 					});
+					syncingRef.current = null;
 				},

17-49: Rapid branch switches can cause stale cache patches from earlier mutations.

mutate is fire-and-forget — if the user switches branches quickly (A→B→C), concurrent mutations for B and C race, and the last onSuccess to resolve wins the cache. The final state will be correct only if mutation C finishes last. In practice the 2.5s polling corrects this, but it can cause a brief flicker.

If this becomes an issue, consider aborting the previous mutation or using a nonce to discard stale callbacks.


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

🤖 Fix all issues with AI agents
In
`@apps/desktop/src/renderer/screens/main/hooks/useBranchSyncInvalidation/useBranchSyncInvalidation.ts`:
- Around line 14-56: The syncingRef used in useBranchSyncInvalidation
(syncingRef.current) is never cleared if
electronTrpc.workspaces.syncBranch.useMutation().mutate fails, which blocks
future retries; update the mutate call inside doSync to provide an onSettled (or
onError) callback that sets syncingRef.current = null so a subsequent effect run
can retry, and keep the existing success logic intact in onSuccess; reference
the mutate call inside doSync and the syncingRef variable to locate where to add
the onSettled/onError handler.
🧹 Nitpick comments (3)
apps/desktop/src/lib/trpc/routers/changes/status.ts (1)

149-164: Empty catch {} silently swallows errors.

Per coding guidelines, errors should not be swallowed silently. While skipping individual file failures is reasonable here (files may vanish between status check and read), a debug-level log would help troubleshoot issues.

Optional: add minimal logging
-		} catch {}
+		} catch (err) {
+			console.debug(`[changes/status] Failed to read untracked file: ${file.path}`, err);
+		}

As per coding guidelines: "Never swallow errors silently; at minimum log errors with context before rethrowing or handling them explicitly."

apps/desktop/src/lib/trpc/routers/workspaces/procedures/status.ts (2)

147-150: Inconsistent error handling: return-value vs throwing.

Other mutations in this file (update, setUnread, setActive) throw new Error(...) when a workspace is not found, but syncBranch returns { success: false, reason: "not-found" }. This is defensible for a polling-driven sync (not-found isn't exceptional), but the inconsistency means callers need two different handling strategies.

Consider documenting the rationale or aligning the pattern. If the return-value approach is preferred for sync operations, that's fine — just worth being intentional about the distinction.


156-160: Use touchWorkspace to bump updatedAt when changing branch.

The local-db workspaces schema doesn't have an $onUpdate hook for updatedAt. The direct .set({ branch }) update won't update the timestamp. Other mutations in this file (e.g., patch updates) use touchWorkspace, which handles both the field change and timestamp updates. Since touchWorkspace accepts branch in additionalFields, it should be used here for consistency:

Use touchWorkspace for branch updates
-				localDb
-					.update(workspaces)
-					.set({ branch })
-					.where(eq(workspaces.id, workspaceId))
-					.run();
+				touchWorkspace(workspaceId, { branch });

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 14, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch
  • ✅ Electric Fly.io app
  • ✅ Streams Fly.io app

Thank you for your contribution! 🎉

- Move branch sync from path-based lookup in changes/status to workspace-aware
  syncBranch mutation called from renderer with correct workspaceId
- Fix flickering by extracting stable mutate ref and clearing sync guard only
  when workspaceBranch catches up
- Fix setup.sh local DB source path (~/.superset instead of ~/.superset-dev)
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