From 04a600b222bac1717d80384ee94d0c7d15264194 Mon Sep 17 00:00:00 2001 From: Satya Patel Date: Fri, 13 Feb 2026 20:12:56 -0800 Subject: [PATCH] fix(desktop): fix git state mapping to wrong workspace in sidebar - 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) --- .superset/setup.sh | 2 +- .../src/lib/trpc/routers/changes/status.ts | 90 +------------------ .../routers/workspaces/procedures/status.ts | 49 +++++++++- .../useBranchSyncInvalidation.ts | 58 +++++++++--- bun.lock | 1 + 5 files changed, 96 insertions(+), 104 deletions(-) diff --git a/.superset/setup.sh b/.superset/setup.sh index 8ca42b97009..213c08f1262 100755 --- a/.superset/setup.sh +++ b/.superset/setup.sh @@ -166,7 +166,7 @@ step_clone_local_db() { return 0 fi - local source_db="$HOME/.superset-dev/local.db" + local source_db="$HOME/.superset/local.db" local dest_dir="$HOME/.superset-${sanitized}" local dest_db="$dest_dir/local.db" diff --git a/apps/desktop/src/lib/trpc/routers/changes/status.ts b/apps/desktop/src/lib/trpc/routers/changes/status.ts index cc4b1fea638..a059c764684 100644 --- a/apps/desktop/src/lib/trpc/routers/changes/status.ts +++ b/apps/desktop/src/lib/trpc/routers/changes/status.ts @@ -1,6 +1,3 @@ -import { projects, workspaces, worktrees } from "@superset/local-db"; -import { and, eq, isNull, not } from "drizzle-orm"; -import { localDb } from "main/lib/local-db"; import type { ChangedFile, GitChangesStatus } from "shared/changes-types"; import simpleGit from "simple-git"; import { z } from "zod"; @@ -29,16 +26,9 @@ export const createStatusRouter = () => { const git = simpleGit(input.worktreePath); const defaultBranch = input.defaultBranch || "main"; - // First, get status (needed for subsequent operations) - // Use --no-optional-locks to avoid holding locks on the repository const status = await getStatusNoLock(input.worktreePath); const parsed = parseGitStatus(status); - syncWorkspaceBranch({ - worktreePath: input.worktreePath, - currentBranch: parsed.branch, - }); - // Run independent operations in parallel const [branchComparison, trackingStatus] = await Promise.all([ getBranchComparison(git, defaultBranch), getTrackingBranchStatus(git), @@ -101,81 +91,6 @@ export const createStatusRouter = () => { }); }; -/** - * Update local DB branch fields to match the current git branch for a worktree - * or main repo workspace path. - */ -function syncWorkspaceBranch({ - worktreePath, - currentBranch, -}: { - worktreePath: string; - currentBranch: string; -}): void { - if (!currentBranch || currentBranch === "HEAD") { - return; - } - - try { - const worktree = localDb - .select({ id: worktrees.id }) - .from(worktrees) - .where(eq(worktrees.path, worktreePath)) - .get(); - - if (worktree) { - localDb - .update(worktrees) - .set({ branch: currentBranch }) - .where( - and( - eq(worktrees.id, worktree.id), - not(eq(worktrees.branch, currentBranch)), - ), - ) - .run(); - - localDb - .update(workspaces) - .set({ branch: currentBranch }) - .where( - and( - eq(workspaces.worktreeId, worktree.id), - isNull(workspaces.deletingAt), - not(eq(workspaces.branch, currentBranch)), - ), - ) - .run(); - - return; - } - - const project = localDb - .select({ id: projects.id }) - .from(projects) - .where(eq(projects.mainRepoPath, worktreePath)) - .get(); - if (!project) { - return; - } - - localDb - .update(workspaces) - .set({ branch: currentBranch }) - .where( - and( - eq(workspaces.projectId, project.id), - eq(workspaces.type, "branch"), - isNull(workspaces.deletingAt), - not(eq(workspaces.branch, currentBranch)), - ), - ) - .run(); - } catch (error) { - console.warn("[changes/status] Failed to sync branch:", error); - } -} - interface BranchComparison { commits: GitChangesStatus["commits"]; againstBase: ChangedFile[]; @@ -229,7 +144,6 @@ async function getBranchComparison( return { commits, againstBase, ahead, behind }; } -/** Max file size for line counting (1 MiB) - skip larger files to avoid OOM */ const MAX_LINE_COUNT_SIZE = 1 * 1024 * 1024; async function applyUntrackedLineCount( @@ -245,9 +159,7 @@ async function applyUntrackedLineCount( const lineCount = content.split("\n").length; file.additions = lineCount; file.deletions = 0; - } catch { - // Skip files that fail validation or reading - } + } catch {} } } diff --git a/apps/desktop/src/lib/trpc/routers/workspaces/procedures/status.ts b/apps/desktop/src/lib/trpc/routers/workspaces/procedures/status.ts index 022713f31aa..230db02f0a7 100644 --- a/apps/desktop/src/lib/trpc/routers/workspaces/procedures/status.ts +++ b/apps/desktop/src/lib/trpc/routers/workspaces/procedures/status.ts @@ -1,5 +1,5 @@ -import { workspaces } from "@superset/local-db"; -import { and, eq, isNull } from "drizzle-orm"; +import { workspaces, worktrees } from "@superset/local-db"; +import { and, eq, isNull, not } from "drizzle-orm"; import { localDb } from "main/lib/local-db"; import { z } from "zod"; import { publicProcedure, router } from "../../.."; @@ -129,5 +129,50 @@ export const createStatusProcedures = () => { return { success: true, workspaceId: input.workspaceId }; }), + + syncBranch: publicProcedure + .input( + z.object({ + workspaceId: z.string(), + branch: z.string(), + }), + ) + .mutation(({ input }) => { + const { workspaceId, branch } = input; + + if (!branch || branch === "HEAD") { + return { success: false as const, reason: "invalid-branch" as const }; + } + + const workspace = getWorkspaceNotDeleting(workspaceId); + if (!workspace) { + return { success: false as const, reason: "not-found" as const }; + } + + if (workspace.branch === branch) { + return { success: true as const, changed: false as const }; + } + + localDb + .update(workspaces) + .set({ branch }) + .where(eq(workspaces.id, workspaceId)) + .run(); + + if (workspace.worktreeId) { + localDb + .update(worktrees) + .set({ branch }) + .where( + and( + eq(worktrees.id, workspace.worktreeId), + not(eq(worktrees.branch, branch)), + ), + ) + .run(); + } + + return { success: true as const, changed: true as const }; + }), }); }; diff --git a/apps/desktop/src/renderer/screens/main/hooks/useBranchSyncInvalidation/useBranchSyncInvalidation.ts b/apps/desktop/src/renderer/screens/main/hooks/useBranchSyncInvalidation/useBranchSyncInvalidation.ts index 6f8425c92c6..ef597ceabcd 100644 --- a/apps/desktop/src/renderer/screens/main/hooks/useBranchSyncInvalidation/useBranchSyncInvalidation.ts +++ b/apps/desktop/src/renderer/screens/main/hooks/useBranchSyncInvalidation/useBranchSyncInvalidation.ts @@ -1,12 +1,6 @@ -import { useEffect } from "react"; +import { useCallback, useEffect, useRef } from "react"; import { electronTrpc } from "renderer/lib/electron-trpc"; -/** - * Invalidates workspace-related caches when the git branch (from status polling) - * diverges from the branch stored in the local DB workspace record. - * - * This keeps sidebar labels and hover cards in sync after external `git switch`. - */ export function useBranchSyncInvalidation({ gitBranch, workspaceBranch, @@ -17,13 +11,53 @@ export function useBranchSyncInvalidation({ workspaceId: string; }) { const utils = electronTrpc.useUtils(); + const { mutate } = electronTrpc.workspaces.syncBranch.useMutation(); + const syncingRef = useRef(null); + + const doSync = useCallback( + (branch: string) => { + mutate( + { workspaceId, branch }, + { + onSuccess: (result) => { + if (!result.success || !("changed" in result) || !result.changed) { + syncingRef.current = null; + return; + } + + utils.workspaces.getAllGrouped.setData(undefined, (oldData) => { + if (!oldData) return oldData; + return oldData.map((group) => ({ + ...group, + workspaces: group.workspaces.map((ws) => + ws.id === workspaceId ? { ...ws, branch } : ws, + ), + })); + }); + + utils.workspaces.get.invalidate({ id: workspaceId }); + utils.workspaces.getWorktreeInfo.invalidate({ + workspaceId, + }); + }, + onError: () => { + syncingRef.current = null; + }, + }, + ); + }, + [mutate, workspaceId, utils], + ); 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 }); + if (gitBranch === workspaceBranch) { + syncingRef.current = null; + return; } - }, [gitBranch, workspaceBranch, workspaceId, utils]); + if (syncingRef.current === gitBranch) return; + syncingRef.current = gitBranch; + + doSync(gitBranch); + }, [gitBranch, workspaceBranch, doSync]); } diff --git a/bun.lock b/bun.lock index a26e19da3c3..a458df61983 100644 --- a/bun.lock +++ b/bun.lock @@ -684,6 +684,7 @@ "dependencies": { "@modelcontextprotocol/sdk": "^1.25.3", "@superset/db": "workspace:*", + "@superset/shared": "workspace:*", "drizzle-orm": "0.45.1", "zod": "^4.3.5", },