From afdfe16931d292ed5de2772ccc1afd95120ff6b2 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Tue, 17 Feb 2026 13:05:30 -0800 Subject: [PATCH] Extract branch sync logic and limit sync to local mode - move branch-sync decision into `BranchToolbar.logic.ts` - avoid auto-sync when env mode is `worktree` or thread has worktree path - add unit tests for sync and no-sync cases --- .../components/BranchToolbar.logic.test.ts | 56 +++++++++++++++++++ .../web/src/components/BranchToolbar.logic.ts | 28 ++++++++++ apps/web/src/components/BranchToolbar.tsx | 18 ++++-- 3 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 apps/web/src/components/BranchToolbar.logic.test.ts create mode 100644 apps/web/src/components/BranchToolbar.logic.ts diff --git a/apps/web/src/components/BranchToolbar.logic.test.ts b/apps/web/src/components/BranchToolbar.logic.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..dd2a25d06999f166a6d838a349376b000b2c5246 --- /dev/null +++ b/apps/web/src/components/BranchToolbar.logic.test.ts @@ -0,0 +1,56 @@ +import type { GitBranch } from "@t3tools/contracts"; +import { describe, expect, it } from "vitest"; +import { deriveSyncedLocalBranch } from "./BranchToolbar.logic"; + +const branches: GitBranch[] = [ + { + name: "main", + current: true, + isDefault: true, + worktreePath: null, + }, + { + name: "feature/demo", + current: false, + isDefault: false, + worktreePath: null, + }, +]; + +describe("deriveSyncedLocalBranch", () => { + it("syncs to git current branch in local mode", () => { + const result = deriveSyncedLocalBranch({ + activeThreadId: "thread-1", + activeWorktreePath: null, + envMode: "local", + activeThreadBranch: "feature/demo", + queryBranches: branches, + }); + + expect(result).toBe("main"); + }); + + it("does not sync when creating a new worktree", () => { + const result = deriveSyncedLocalBranch({ + activeThreadId: "thread-1", + activeWorktreePath: null, + envMode: "worktree", + activeThreadBranch: "feature/demo", + queryBranches: branches, + }); + + expect(result).toBeNull(); + }); + + it("does not sync when thread already targets a worktree path", () => { + const result = deriveSyncedLocalBranch({ + activeThreadId: "thread-1", + activeWorktreePath: "/tmp/repo/worktrees/feature-demo", + envMode: "local", + activeThreadBranch: "feature/demo", + queryBranches: branches, + }); + + expect(result).toBeNull(); + }); +}); diff --git a/apps/web/src/components/BranchToolbar.logic.ts b/apps/web/src/components/BranchToolbar.logic.ts new file mode 100644 index 0000000000000000000000000000000000000000..03d04bc3cf39c65a66edd6480242c201abc9ca19 --- /dev/null +++ b/apps/web/src/components/BranchToolbar.logic.ts @@ -0,0 +1,28 @@ +import type { GitBranch } from "@t3tools/contracts"; + +interface DeriveSyncedLocalBranchInput { + activeThreadId: string | undefined; + activeWorktreePath: string | null; + envMode: "local" | "worktree"; + activeThreadBranch: string | null; + queryBranches: GitBranch[] | undefined; +} + +export function deriveSyncedLocalBranch({ + activeThreadId, + activeWorktreePath, + envMode, + activeThreadBranch, + queryBranches, +}: DeriveSyncedLocalBranchInput): string | null { + if (!activeThreadId || activeWorktreePath || envMode !== "local") { + return null; + } + + const currentBranch = queryBranches?.find((branch) => branch.current); + if (!currentBranch || currentBranch.name === activeThreadBranch) { + return null; + } + + return currentBranch.name; +} diff --git a/apps/web/src/components/BranchToolbar.tsx b/apps/web/src/components/BranchToolbar.tsx index 1e6dd9e923c3f22153196bd54f5fabc61a5d1e33..30e9327060664b1012d1b59b8a910e34042b6c43 100644 --- a/apps/web/src/components/BranchToolbar.tsx +++ b/apps/web/src/components/BranchToolbar.tsx @@ -9,6 +9,7 @@ import { gitCreateBranchAndCheckoutMutationOptions, } from "../lib/gitReactQuery"; import { useStore } from "../store"; +import { deriveSyncedLocalBranch } from "./BranchToolbar.logic"; import { Button } from "./ui/button"; import { Combobox, @@ -86,17 +87,22 @@ export default function BranchToolbar({ // Keep thread branch synced to git current branch for local threads. const queryBranches = branchesQuery.data?.branches; useEffect(() => { - if (!activeThreadId || activeWorktreePath) return; - const current = queryBranches?.find((branch) => branch.current); - if (!current) return; - if (current.name === activeThreadBranch) return; + const syncedBranch = deriveSyncedLocalBranch({ + activeThreadId, + activeWorktreePath, + envMode, + activeThreadBranch, + queryBranches, + }); + if (!syncedBranch) return; + dispatch({ type: "SET_THREAD_BRANCH", threadId: activeThreadId, - branch: current.name, + branch: syncedBranch, worktreePath: null, }); - }, [activeThreadId, activeWorktreePath, activeThreadBranch, queryBranches, dispatch]); + }, [activeThreadId, activeWorktreePath, activeThreadBranch, queryBranches, envMode, dispatch]); useEffect(() => { if (isBranchMenuOpen) return; -- 2.54.0