From 0192e015ce519a37cc54af599fb5947c5ac80575 Mon Sep 17 00:00:00 2001 From: Serhii Vecherenko Date: Mon, 8 Jun 2026 21:59:42 -0700 Subject: [PATCH] fix(renderer): preserve cached worktree stats on fetch refresh - skip worktree status re-fetches during fetch refreshes - add regression coverage for cached diff stats --- src/renderer/state/gitRefresh.test.ts | 63 +++++++++++++++++++++++++++ src/renderer/state/gitRefresh.ts | 2 +- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/renderer/state/gitRefresh.test.ts b/src/renderer/state/gitRefresh.test.ts index c7545cd..f722f31 100644 --- a/src/renderer/state/gitRefresh.test.ts +++ b/src/renderer/state/gitRefresh.test.ts @@ -454,6 +454,69 @@ describe("watcher git status refresh", () => { expect(useGitStore.getState().worktreeStatuses["/repo-wt"]).toEqual(worktreeStatus); }); + it("preserves cached worktree diff stats during fetch refreshes", async () => { + const cachedWorktreeStatus: GitStatusResult = { + ...status, + branch: "feature/wt", + unstaged: [ + { + path: "src/existing-change.ts", + status: "M", + staged: false, + insertions: 3, + deletions: 1, + }, + ], + totalInsertions: 3, + totalDeletions: 1, + }; + const gitWatchWorktrees = vi.fn<() => Promise>().mockResolvedValue(undefined); + const gitWorktreeStatusBatch = vi + .fn< + (payload: { + projectLocation: ProjectLocation; + worktreePaths: string[]; + detail?: "summary" | "full"; + }) => Promise<{ statuses: Record }> + >() + .mockResolvedValue({ statuses: {} }); + const gitProjectSnapshot = vi + .fn< + () => Promise<{ + status: GitStatusResult; + branches: { current: string; branches: unknown[] }; + worktrees: { path: string; branch: string; commit: string; isMain: boolean }[]; + ghAvailable: boolean; + }> + >() + .mockResolvedValue({ + status, + branches: { current: "feature/pr-checks", branches: [] }, + worktrees: [{ path: "/repo", branch: "feature/pr-checks", commit: "abc123", isMain: true }], + ghAvailable: false, + }); + Object.defineProperty(window, "lightcode", { + configurable: true, + value: { + platform: "darwin", + gitProjectSnapshot, + gitWatchWorktrees, + gitWorktreeStatusBatch, + }, + }); + useGitStore.getState().setWorktreeStatus("/repo-wt", cachedWorktreeStatus); + useAppStore.setState({ threads: [worktreeThread] }); + + await refreshGitProject(project, "fetch", "full"); + + expect(gitProjectSnapshot).toHaveBeenCalledWith({ + projectLocation: location, + includeGhCheck: true, + }); + expect(gitWorktreeStatusBatch).not.toHaveBeenCalled(); + expect(useGitStore.getState().worktreeStatuses["/repo-wt"]).toEqual(cachedWorktreeStatus); + }); + it("promotes watcher refresh to a full snapshot after a project becomes a Git repo", async () => { const nonRepoStatus: GitStatusResult = { isRepo: false, diff --git a/src/renderer/state/gitRefresh.ts b/src/renderer/state/gitRefresh.ts index 535d39c..bd58573 100644 --- a/src/renderer/state/gitRefresh.ts +++ b/src/renderer/state/gitRefresh.ts @@ -540,7 +540,7 @@ export async function refreshGitProject( ); const statusesPromise = - watchWorktreePaths.length === 0 + reason === "fetch" || watchWorktreePaths.length === 0 ? Promise.resolve() : readBridge() .gitWorktreeStatusBatch({