From 954c933d1abc7d051731167e7b09856de37d1910 Mon Sep 17 00:00:00 2001 From: Thomas Ricouard Date: Sat, 31 Jan 2026 07:05:25 +0100 Subject: [PATCH 1/3] Add git diff preload setting --- src-tauri/src/types.rs | 8 ++++ src/App.tsx | 1 + .../app/hooks/useGitPanelController.ts | 15 +++++-- .../app/hooks/useSettingsModalState.ts | 1 + .../settings/components/SettingsView.test.tsx | 1 + .../settings/components/SettingsView.tsx | 41 ++++++++++++++++++- src/features/settings/hooks/useAppSettings.ts | 1 + src/types.ts | 1 + 8 files changed, 65 insertions(+), 4 deletions(-) diff --git a/src-tauri/src/types.rs b/src-tauri/src/types.rs index f8b84b45..5e3f421f 100644 --- a/src-tauri/src/types.rs +++ b/src-tauri/src/types.rs @@ -406,6 +406,8 @@ pub(crate) struct AppSettings { rename = "notificationSoundsEnabled" )] pub(crate) notification_sounds_enabled: bool, + #[serde(default = "default_preload_git_diffs", rename = "preloadGitDiffs")] + pub(crate) preload_git_diffs: bool, #[serde( default = "default_experimental_collab_enabled", rename = "experimentalCollabEnabled" @@ -592,6 +594,10 @@ fn default_notification_sounds_enabled() -> bool { true } +fn default_preload_git_diffs() -> bool { + true +} + fn default_experimental_collab_enabled() -> bool { false } @@ -752,6 +758,7 @@ impl Default for AppSettings { code_font_family: default_code_font_family(), code_font_size: default_code_font_size(), notification_sounds_enabled: true, + preload_git_diffs: default_preload_git_diffs(), experimental_collab_enabled: false, experimental_collaboration_modes_enabled: false, experimental_steer_enabled: false, @@ -849,6 +856,7 @@ mod tests { assert!(settings.code_font_family.contains("SF Mono")); assert_eq!(settings.code_font_size, 11); assert!(settings.notification_sounds_enabled); + assert!(settings.preload_git_diffs); assert!(!settings.experimental_steer_enabled); assert!(!settings.dictation_enabled); assert_eq!(settings.dictation_model_id, "base"); diff --git a/src/App.tsx b/src/App.tsx index c843b2c7..69f623d1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -341,6 +341,7 @@ function MainApp() { activeWorkspaceRef, } = useGitPanelController({ activeWorkspace, + gitDiffPreloadEnabled: appSettings.preloadGitDiffs, isCompact, isTablet, activeTab, diff --git a/src/features/app/hooks/useGitPanelController.ts b/src/features/app/hooks/useGitPanelController.ts index f3656686..6f481be4 100644 --- a/src/features/app/hooks/useGitPanelController.ts +++ b/src/features/app/hooks/useGitPanelController.ts @@ -7,6 +7,7 @@ import { useGitCommitDiffs } from "../../git/hooks/useGitCommitDiffs"; export function useGitPanelController({ activeWorkspace, + gitDiffPreloadEnabled, isCompact, isTablet, activeTab, @@ -17,6 +18,7 @@ export function useGitPanelController({ prDiffsError, }: { activeWorkspace: WorkspaceInfo | null; + gitDiffPreloadEnabled: boolean; isCompact: boolean; isTablet: boolean; activeTab: "projects" | "codex" | "git" | "log"; @@ -94,10 +96,17 @@ export function useGitPanelController({ centerMode === "diff" || (isCompact ? compactTab === "git" : gitPanelMode === "diff"); const shouldPreloadDiffs = Boolean( - activeWorkspace && !preloadedWorkspaceIdsRef.current.has(activeWorkspace.id), + gitDiffPreloadEnabled && + activeWorkspace && + !preloadedWorkspaceIdsRef.current.has(activeWorkspace.id), ); + const shouldLoadLocalDiffs = + Boolean(activeWorkspace) && + (shouldPreloadDiffs || + (diffUiVisible && (gitDiffPreloadEnabled || Boolean(selectedDiffPath)))); const shouldLoadDiffs = - Boolean(activeWorkspace) && (diffUiVisible || shouldPreloadDiffs); + Boolean(activeWorkspace) && + (diffSource === "local" ? shouldLoadLocalDiffs : diffUiVisible); const shouldLoadGitLog = gitPanelMode === "log" && Boolean(activeWorkspace); const { @@ -105,7 +114,7 @@ export function useGitPanelController({ isLoading: isDiffLoading, error: diffError, refresh: refreshGitDiffs, - } = useGitDiffs(activeWorkspace, gitStatus.files, shouldLoadDiffs); + } = useGitDiffs(activeWorkspace, gitStatus.files, shouldLoadLocalDiffs); useEffect(() => { if (!activeWorkspace || !shouldPreloadDiffs) { diff --git a/src/features/app/hooks/useSettingsModalState.ts b/src/features/app/hooks/useSettingsModalState.ts index 781124c3..2137ff6b 100644 --- a/src/features/app/hooks/useSettingsModalState.ts +++ b/src/features/app/hooks/useSettingsModalState.ts @@ -6,6 +6,7 @@ export type SettingsSection = | "dictation" | "shortcuts" | "open-apps" + | "git" | "codex" | "experimental"; diff --git a/src/features/settings/components/SettingsView.test.tsx b/src/features/settings/components/SettingsView.test.tsx index 96dbce94..e79afc48 100644 --- a/src/features/settings/components/SettingsView.test.tsx +++ b/src/features/settings/components/SettingsView.test.tsx @@ -52,6 +52,7 @@ const baseSettings: AppSettings = { "\"SF Mono\", \"SFMono-Regular\", Menlo, Monaco, monospace", codeFontSize: 11, notificationSoundsEnabled: true, + preloadGitDiffs: true, experimentalCollabEnabled: false, experimentalCollaborationModesEnabled: false, experimentalSteerEnabled: false, diff --git a/src/features/settings/components/SettingsView.tsx b/src/features/settings/components/SettingsView.tsx index f36b7352..e6df84d6 100644 --- a/src/features/settings/components/SettingsView.tsx +++ b/src/features/settings/components/SettingsView.tsx @@ -8,6 +8,7 @@ import SlidersHorizontal from "lucide-react/dist/esm/icons/sliders-horizontal"; import Mic from "lucide-react/dist/esm/icons/mic"; import Keyboard from "lucide-react/dist/esm/icons/keyboard"; import Stethoscope from "lucide-react/dist/esm/icons/stethoscope"; +import GitBranch from "lucide-react/dist/esm/icons/git-branch"; import TerminalSquare from "lucide-react/dist/esm/icons/terminal-square"; import FileText from "lucide-react/dist/esm/icons/file-text"; import Trash2 from "lucide-react/dist/esm/icons/trash-2"; @@ -174,7 +175,8 @@ type SettingsSection = | "composer" | "dictation" | "shortcuts" - | "open-apps"; + | "open-apps" + | "git"; type CodexSection = SettingsSection | "codex" | "experimental"; type ShortcutSettingKey = | "composerModelShortcut" @@ -1036,6 +1038,14 @@ export function SettingsView({ Open in + + + + )} {activeSection === "codex" && (
Codex
diff --git a/src/features/settings/hooks/useAppSettings.ts b/src/features/settings/hooks/useAppSettings.ts index 4d118e10..1c3ed8c1 100644 --- a/src/features/settings/hooks/useAppSettings.ts +++ b/src/features/settings/hooks/useAppSettings.ts @@ -52,6 +52,7 @@ const defaultSettings: AppSettings = { codeFontFamily: DEFAULT_CODE_FONT_FAMILY, codeFontSize: CODE_FONT_SIZE_DEFAULT, notificationSoundsEnabled: true, + preloadGitDiffs: true, experimentalCollabEnabled: false, experimentalCollaborationModesEnabled: false, experimentalSteerEnabled: false, diff --git a/src/types.ts b/src/types.ts index d69d04ea..a42ceda8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -146,6 +146,7 @@ export type AppSettings = { codeFontFamily: string; codeFontSize: number; notificationSoundsEnabled: boolean; + preloadGitDiffs: boolean; experimentalCollabEnabled: boolean; experimentalCollaborationModesEnabled: boolean; experimentalSteerEnabled: boolean; From bf5fe437b72ae95244c4563f6883d2628ca385b0 Mon Sep 17 00:00:00 2001 From: Thomas Ricouard Date: Sat, 31 Jan 2026 07:23:53 +0100 Subject: [PATCH 2/3] fix: load local diffs when panel opens to avoid empty state --- src/features/app/hooks/useGitPanelController.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/features/app/hooks/useGitPanelController.ts b/src/features/app/hooks/useGitPanelController.ts index 6f481be4..5d8f35cb 100644 --- a/src/features/app/hooks/useGitPanelController.ts +++ b/src/features/app/hooks/useGitPanelController.ts @@ -103,7 +103,8 @@ export function useGitPanelController({ const shouldLoadLocalDiffs = Boolean(activeWorkspace) && (shouldPreloadDiffs || - (diffUiVisible && (gitDiffPreloadEnabled || Boolean(selectedDiffPath)))); + diffUiVisible || + Boolean(selectedDiffPath)); const shouldLoadDiffs = Boolean(activeWorkspace) && (diffSource === "local" ? shouldLoadLocalDiffs : diffUiVisible); From d9056f132014f82464c3598aa8950f0e4c036cd5 Mon Sep 17 00:00:00 2001 From: Thomas Ricouard Date: Sat, 31 Jan 2026 07:29:55 +0100 Subject: [PATCH 3/3] test: guard git diff preloading behind panel visibility --- .../app/hooks/useGitPanelController.test.tsx | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 src/features/app/hooks/useGitPanelController.test.tsx diff --git a/src/features/app/hooks/useGitPanelController.test.tsx b/src/features/app/hooks/useGitPanelController.test.tsx new file mode 100644 index 00000000..8b95c75e --- /dev/null +++ b/src/features/app/hooks/useGitPanelController.test.tsx @@ -0,0 +1,130 @@ +// @vitest-environment jsdom +import { act, renderHook } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { WorkspaceInfo } from "../../../types"; +import { useGitPanelController } from "./useGitPanelController"; + +const useGitDiffsMock = vi.fn(); +const useGitStatusMock = vi.fn(); +const useGitLogMock = vi.fn(); +const useGitCommitDiffsMock = vi.fn(); + +vi.mock("../../git/hooks/useGitDiffs", () => ({ + useGitDiffs: (...args: unknown[]) => useGitDiffsMock(...args), +})); + +vi.mock("../../git/hooks/useGitStatus", () => ({ + useGitStatus: (...args: unknown[]) => useGitStatusMock(...args), +})); + +vi.mock("../../git/hooks/useGitLog", () => ({ + useGitLog: (...args: unknown[]) => useGitLogMock(...args), +})); + +vi.mock("../../git/hooks/useGitCommitDiffs", () => ({ + useGitCommitDiffs: (...args: unknown[]) => useGitCommitDiffsMock(...args), +})); + +const workspace: WorkspaceInfo = { + id: "workspace-1", + name: "CodexMonitor", + path: "/tmp/codex-monitor", + connected: true, + settings: { sidebarCollapsed: false }, +}; + +function makeProps(overrides?: Partial[0]>) { + return { + activeWorkspace: workspace, + gitDiffPreloadEnabled: false, + isCompact: false, + isTablet: false, + activeTab: "codex" as const, + tabletTab: "codex" as const, + setActiveTab: vi.fn(), + prDiffs: [], + prDiffsLoading: false, + prDiffsError: null, + ...overrides, + }; +} + +function getLastEnabledArg() { + const { calls } = useGitDiffsMock.mock; + if (calls.length === 0) { + return undefined; + } + return calls[calls.length - 1]?.[2]; +} + +beforeEach(() => { + useGitStatusMock.mockReturnValue({ + status: { + branchName: "main", + files: [], + stagedFiles: [], + unstagedFiles: [], + totalAdditions: 0, + totalDeletions: 0, + }, + refresh: vi.fn(), + }); + useGitDiffsMock.mockReturnValue({ + diffs: [], + isLoading: false, + error: null, + refresh: vi.fn(), + }); + useGitLogMock.mockReturnValue({ + entries: [], + total: 0, + ahead: 0, + behind: 0, + aheadEntries: [], + behindEntries: [], + upstream: null, + isLoading: false, + error: null, + refresh: vi.fn(), + }); + useGitCommitDiffsMock.mockReturnValue({ + diffs: [], + isLoading: false, + error: null, + }); + useGitDiffsMock.mockClear(); +}); + +describe("useGitPanelController preload behavior", () => { + it("does not preload diffs when disabled and panel is hidden", () => { + const { result } = renderHook(() => useGitPanelController(makeProps())); + + const initialEnabled = getLastEnabledArg(); + expect(initialEnabled).toBe(true); + + act(() => { + result.current.setGitPanelMode("issues"); + }); + + const lastEnabled = getLastEnabledArg(); + expect(lastEnabled).toBe(false); + }); + + it("loads diffs when the panel becomes visible even if preload is disabled", () => { + const { result } = renderHook(() => useGitPanelController(makeProps())); + + act(() => { + result.current.setGitPanelMode("issues"); + }); + + const hiddenEnabled = getLastEnabledArg(); + expect(hiddenEnabled).toBe(false); + + act(() => { + result.current.setGitPanelMode("diff"); + }); + + const visibleEnabled = getLastEnabledArg(); + expect(visibleEnabled).toBe(true); + }); +});