From cd821abb2cac5d0ef7438ed0bc28bc975395b88b Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Thu, 29 Oct 2026 02:47:20 -0700 Subject: [PATCH] fix(wsl): Commits history wakes a stopped distro only on the user's own act (#35854) * fix(wsl): Commits history wakes a stopped distro only on the user's own act Showing Commits woke the distro on every mount, remount and re-show. Now only the user expanding Commits in the same worktree, or the user's own switch to the workspace (once per switch), may start it; the Refresh button keeps its explicit wake (E9). * test: a mount and a base-ref move read Commits history in the background #35778's base-move refetch test expected every shown-history read to wake a stopped WSL distro; only the user's own act does now. --- .../sync/use-git-history-wsl-wake.test.tsx | 100 ++++++++++++++---- .../source-control/sync/use-git-history.ts | 23 +++- .../use-source-control-git-history.test.tsx | 8 +- 3 files changed, 101 insertions(+), 30 deletions(-) diff --git a/src/renderer/src/components/right-sidebar/source-control/sync/use-git-history-wsl-wake.test.tsx b/src/renderer/src/components/right-sidebar/source-control/sync/use-git-history-wsl-wake.test.tsx index e12d8d57792..d51f79b9bbc 100644 --- a/src/renderer/src/components/right-sidebar/source-control/sync/use-git-history-wsl-wake.test.tsx +++ b/src/renderer/src/components/right-sidebar/source-control/sync/use-git-history-wsl-wake.test.tsx @@ -1,7 +1,11 @@ // @vitest-environment happy-dom import { act, cleanup, renderHook } from '@testing-library/react' -import { afterEach, describe, expect, it, vi } from 'vitest' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { + noteUserWorkspaceSwitch, + resetWorkspaceSwitchUserActsForTests +} from '@/lib/workspace-switch-user-act' const getRuntimeGitHistoryMock = vi.hoisted(() => vi.fn()) vi.mock('@/runtime/runtime-git-status-client', () => ({ @@ -11,33 +15,83 @@ vi.mock('@/lib/connection-context', () => ({ getConnectionId: () => null })) import { useSourceControlGitHistory } from './use-git-history' +type Props = Parameters[0] + +const props = (overrides: Partial = {}): Props => ({ + activeRepoTarget: { kind: 'local' }, + activeWorktreeId: 'wt-1', + worktreePath: '\\\\wsl.localhost\\Ubuntu\\repo', + compareBaseRef: 'main', + isFolder: false, + isBranchVisible: true, + isGitHistoryExpanded: true, + isGitHistoryVisible: true, + worktreeMap: new Map([ + ['wt-1', {}], + ['wt-2', {}] + ]), + ...overrides +}) + +const wakes = (): unknown[] => + getRuntimeGitHistoryMock.mock.calls.map((call) => call[1].wakeWslDistro) + +beforeEach(() => { + getRuntimeGitHistoryMock.mockReset().mockResolvedValue({ items: [] }) + resetWorkspaceSwitchUserActsForTests() +}) afterEach(cleanup) -// E9: showing Commits (or its Refresh) is a user act; a refresh after a Git action is not. +// E9: only the user's own act may start a stopped WSL distro for a Commits read. describe('Commits history and a stopped WSL distro', () => { - it('wakes for the read that shows Commits, not for a later ref-driven refresh', async () => { - getRuntimeGitHistoryMock.mockReset().mockResolvedValue({ items: [] }) - const { result } = renderHook(() => - useSourceControlGitHistory({ - activeRepoTarget: { kind: 'local' }, - activeWorktreeId: 'wt-1', - worktreePath: '\\\\wsl.localhost\\Ubuntu\\repo', - compareBaseRef: 'main', - isFolder: false, - isBranchVisible: true, - isGitHistoryExpanded: true, - isGitHistoryVisible: true, - worktreeMap: new Map([['wt-1', {}]]) - }) - ) + it('stays background on a mount or remount with Commits already open', async () => { + const first = renderHook(() => useSourceControlGitHistory(props())) + await act(async () => {}) + first.unmount() + renderHook(() => useSourceControlGitHistory(props())) + await act(async () => {}) + expect(wakes()).toEqual([undefined, undefined]) + }) + + it('wakes once for the user switching to the workspace, never again on a remount', async () => { + noteUserWorkspaceSwitch('wt-1') + const first = renderHook(() => useSourceControlGitHistory(props())) + await act(async () => {}) + first.unmount() + renderHook(() => useSourceControlGitHistory(props())) + await act(async () => {}) + expect(wakes()).toEqual([true, undefined]) + }) + + it('wakes when the user expands Commits, not when Orca re-shows it or a refresh follows', async () => { + const { result, rerender } = renderHook((p: Props) => useSourceControlGitHistory(p), { + initialProps: props({ isGitHistoryExpanded: false }) + }) + await act(async () => {}) + rerender(props()) + await act(async () => {}) + // Hidden and shown again by Orca (a sidebar re-show), with no switch. + rerender(props({ isGitHistoryVisible: false })) + rerender(props()) await act(async () => {}) await act(async () => result.current.refreshGitHistoryRef.current()) - await act(async () => result.current.refreshGitHistory({ wakeWslDistro: true })) + expect(wakes()).toEqual([true, undefined, undefined]) + }) - expect(getRuntimeGitHistoryMock.mock.calls.map((call) => call[1].wakeWslDistro)).toEqual([ - true, - undefined, - true - ]) + it("does not take another worktree's restored expanded view as a click", async () => { + const { rerender } = renderHook((p: Props) => useSourceControlGitHistory(p), { + initialProps: props({ isGitHistoryExpanded: false }) + }) + await act(async () => {}) + rerender(props({ activeWorktreeId: 'wt-2' })) + await act(async () => {}) + expect(wakes()).toEqual([undefined]) + }) + + it("keeps the Refresh button's explicit wake", async () => { + const { result } = renderHook(() => useSourceControlGitHistory(props())) + await act(async () => {}) + await act(async () => result.current.refreshGitHistory({ wakeWslDistro: true })) + expect(wakes()).toEqual([undefined, true]) }) }) diff --git a/src/renderer/src/components/right-sidebar/source-control/sync/use-git-history.ts b/src/renderer/src/components/right-sidebar/source-control/sync/use-git-history.ts index ccf6ff93d3f..e7b13532838 100644 --- a/src/renderer/src/components/right-sidebar/source-control/sync/use-git-history.ts +++ b/src/renderer/src/components/right-sidebar/source-control/sync/use-git-history.ts @@ -4,12 +4,15 @@ import type { RuntimeClientTarget } from '../../../../../../shared/runtime-clien import { getConnectionId } from '@/lib/connection-context' import { getRuntimeGitHistory } from '@/runtime/runtime-git-status-client' import type { GitHistoryPanelState } from './git-history-panel' +import { takeWorkspaceSwitchWake } from '@/lib/workspace-switch-user-act' const EMPTY_GIT_HISTORY_STATE: GitHistoryPanelState = { status: 'idle' } -/** `wakeWslDistro`: showing Commits or its Refresh, a user act that may start a stopped WSL distro. */ +/** `wakeWslDistro`: the user opening Commits or its Refresh, a user act that may start a stopped WSL distro. */ type HistoryRefresh = { wakeWslDistro?: boolean } const SHOWN_HISTORY_LOAD: HistoryRefresh = { wakeWslDistro: true } +// The mounts and re-shows a user's own workspace switch causes land within this window. +const SWITCH_MOUNT_WINDOW_MS = 10_000 /** Cache/prune history per worktree and reject superseded requests; refreshGitHistoryRef lets effects refresh without callback resubscription. */ export function useSourceControlGitHistory({ @@ -150,13 +153,27 @@ export function useSourceControlGitHistory({ refreshGitHistoryRef.current = refreshGitHistory }, [refreshGitHistory]) + // Expanded at mount, or restored with another worktree's view, is not a click: only an expand + // within the same worktree counts as the user opening Commits. + const lastExpandedRef = useRef({ worktreeId: activeWorktreeId, expanded: isGitHistoryExpanded }) useEffect(() => { + const last = lastExpandedRef.current + const userExpanded = + isGitHistoryExpanded && !last.expanded && last.worktreeId === activeWorktreeId + lastExpandedRef.current = { worktreeId: activeWorktreeId, expanded: isGitHistoryExpanded } // Defer Git subprocesses until Commits is expanded and visible, especially for large/remote repos. if (!isBranchVisible || !isGitHistoryExpanded || !isGitHistoryVisible) { return } - // The user is looking at Commits; refreshes after Git actions stay background (E9). - void refreshGitHistoryRef.current(SHOWN_HISTORY_LOAD) + // Only the user's own act may start a stopped WSL distro: opening Commits, or switching to this + // workspace (once per switch). A remount, restore or Orca's own re-show stays background (E9). + const userAct = + userExpanded || + (activeWorktreeId !== null && + takeWorkspaceSwitchWake(activeWorktreeId, 'git-history', { + withinMs: SWITCH_MOUNT_WINDOW_MS + })) + void refreshGitHistoryRef.current(userAct ? SHOWN_HISTORY_LOAD : undefined) }, [ // History uses compareBaseRef, which can move independently of effectiveBaseRef. activeWorktreeId, diff --git a/src/renderer/src/components/right-sidebar/use-source-control-git-history.test.tsx b/src/renderer/src/components/right-sidebar/use-source-control-git-history.test.tsx index 5e11178cebc..39dcfd72640 100644 --- a/src/renderer/src/components/right-sidebar/use-source-control-git-history.test.tsx +++ b/src/renderer/src/components/right-sidebar/use-source-control-git-history.test.tsx @@ -219,8 +219,8 @@ describe('useSourceControlGitHistory stale completion', () => { await flush() expect(mocks.getRuntimeGitHistory).toHaveBeenLastCalledWith( expect.objectContaining({ worktreeId: 'A', worktreePath: '/a' }), - // Showing Commits is a user act, so it may start a stopped WSL distro. - { limit: 50, baseRef: 'origin/main', wakeWslDistro: true } + // A mount with Commits already open is restored state, not a user act: no WSL wake (E9). + { limit: 50, baseRef: 'origin/main' } ) await act(async () => { @@ -228,10 +228,10 @@ describe('useSourceControlGitHistory stale completion', () => { }) await flush() expect(mocks.getRuntimeGitHistory).toHaveBeenCalledTimes(2) + // A base-ref move refetches in the background; it never starts a stopped distro. expect(mocks.getRuntimeGitHistory).toHaveBeenLastCalledWith(expect.anything(), { limit: 50, - baseRef: 'origin/dev', - wakeWslDistro: true + baseRef: 'origin/dev' }) })