diff --git a/src/renderer/src/components/right-sidebar/file-explorer-stale-retry.test.tsx b/src/renderer/src/components/right-sidebar/file-explorer-stale-retry.test.tsx index 9d0d2a749a5..f791e9a248f 100644 --- a/src/renderer/src/components/right-sidebar/file-explorer-stale-retry.test.tsx +++ b/src/renderer/src/components/right-sidebar/file-explorer-stale-retry.test.tsx @@ -5,7 +5,8 @@ import type { DirCache } from './file-explorer-types' import { useFileExplorerTreeLoadEffects } from './use-file-explorer-tree-load-effects' import { noteUserWorkspaceSwitch, - resetWorkspaceSwitchUserActsForTests + resetWorkspaceSwitchUserActsForTests, + WORKSPACE_SWITCH_MOUNT_WINDOW_MS } from '@/lib/workspace-switch-user-act' afterEach(() => { @@ -141,6 +142,19 @@ it("only the user's own switch to a workspace lets its open read start a stopped renderHook(useFileExplorerTreeLoadEffects, { initialProps: props }) expect(props.resetAndLoad).toHaveBeenCalledExactlyOnceWith({ wakeWslDistro: true }) }) +it('an old switch no consumer used is not redeemed by a later mount (E9)', () => { + vi.useFakeTimers() + try { + const { props } = params() + // The user switched here with the sidebar closed; the explorer mounts long after. + noteUserWorkspaceSwitch(props.workspaceKey) + vi.advanceTimersByTime(WORKSPACE_SWITCH_MOUNT_WINDOW_MS + 1) + renderHook(useFileExplorerTreeLoadEffects, { initialProps: props }) + expect(props.resetAndLoad).toHaveBeenCalledExactlyOnceWith(undefined) + } finally { + vi.useRealTimers() + } +}) it('a restore, remount or re-open with no new switch never wakes WSL', () => { const { props } = params() renderHook(useFileExplorerTreeLoadEffects, { initialProps: props }) 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 e7b13532838..8c6049edc46 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,15 +4,13 @@ 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' +import { takeRecentWorkspaceSwitchWake } from '@/lib/workspace-switch-user-act' const EMPTY_GIT_HISTORY_STATE: GitHistoryPanelState = { status: 'idle' } /** `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({ @@ -169,10 +167,7 @@ export function useSourceControlGitHistory({ // 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 - })) + (activeWorktreeId !== null && takeRecentWorkspaceSwitchWake(activeWorktreeId, 'git-history')) void refreshGitHistoryRef.current(userAct ? SHOWN_HISTORY_LOAD : undefined) }, [ // History uses compareBaseRef, which can move independently of effectiveBaseRef. diff --git a/src/renderer/src/components/right-sidebar/use-file-explorer-tree-load-effects.ts b/src/renderer/src/components/right-sidebar/use-file-explorer-tree-load-effects.ts index de568a2691a..048ef32f8ee 100644 --- a/src/renderer/src/components/right-sidebar/use-file-explorer-tree-load-effects.ts +++ b/src/renderer/src/components/right-sidebar/use-file-explorer-tree-load-effects.ts @@ -6,7 +6,7 @@ import { decideExpandedDirLoad } from './file-explorer-stale-dir-cache' import { clearFileExplorerUndoHistory } from './fileExplorerUndoRedo' import type { DirCache } from './file-explorer-types' import { splitPathSegments } from './path-tree' -import { takeWorkspaceSwitchWake } from '@/lib/workspace-switch-user-act' +import { takeRecentWorkspaceSwitchWake } from '@/lib/workspace-switch-user-act' type UseFileExplorerTreeLoadEffectsParams = { /** The store worktree id, keying the user's own switches to this workspace. */ @@ -91,7 +91,7 @@ export function useFileExplorerTreeLoadEffects({ resetSelection() setNameFilterQuery('') // Only the user's own switch here may start its stopped WSL distro (E9), once per switch. - const wake = workspaceKey !== null && takeWorkspaceSwitchWake(workspaceKey, 'explorer') + const wake = workspaceKey !== null && takeRecentWorkspaceSwitchWake(workspaceKey, 'explorer') resetAndLoad(wake ? { wakeWslDistro: true } : undefined) clearFileExplorerUndoHistory() }, [visibleFilesWorktreePath, resetSelection]) // eslint-disable-line react-hooks/exhaustive-deps diff --git a/src/renderer/src/components/right-sidebar/useGitStatusPolling.ts b/src/renderer/src/components/right-sidebar/useGitStatusPolling.ts index d8e47f67145..1256299a899 100644 --- a/src/renderer/src/components/right-sidebar/useGitStatusPolling.ts +++ b/src/renderer/src/components/right-sidebar/useGitStatusPolling.ts @@ -5,7 +5,7 @@ import { isGitRepoKind } from '../../../../shared/repo-kind' import { getExecutionHostIdForWorktree } from '@/lib/worktree-runtime-owner' import { refreshGitStatusForWorktree } from './git-status-refresh' import { isWindowVisible } from '@/lib/window-visibility-interval' -import { takeWorkspaceSwitchWake } from '@/lib/workspace-switch-user-act' +import { takeRecentWorkspaceSwitchWake } from '@/lib/workspace-switch-user-act' import { hasInteractiveActiveGitStatusConsumer, shouldPollActiveGitStatus @@ -134,7 +134,7 @@ export function useGitStatusPolling(options: { enabled?: boolean } = {}): void { } // Only the first status after the user's own switch here may start WSL (E9); polls, remounts // and restores stay gated. - const wakeWslDistro = takeWorkspaceSwitchWake(activeWorktreeId, 'git-status') + const wakeWslDistro = takeRecentWorkspaceSwitchWake(activeWorktreeId, 'git-status') await refreshGitStatusForWorktree({ target: copy.transport, ownerHostId: copy.hostId, diff --git a/src/renderer/src/components/terminal-pane/pty-connection/wsl-distro-stopped-hold-pane.ts b/src/renderer/src/components/terminal-pane/pty-connection/wsl-distro-stopped-hold-pane.ts index 7ce56c01e94..e6c37e2a1b5 100644 --- a/src/renderer/src/components/terminal-pane/pty-connection/wsl-distro-stopped-hold-pane.ts +++ b/src/renderer/src/components/terminal-pane/pty-connection/wsl-distro-stopped-hold-pane.ts @@ -1,4 +1,4 @@ -import { takeWorkspaceSwitchWake } from '@/lib/workspace-switch-user-act' +import { takeRecentWorkspaceSwitchWake } from '@/lib/workspace-switch-user-act' import { holdPaneForStoppedWslDistro } from '../wsl-distro-stopped-hold-state' import { recordPtyConnectDiagnostic } from './pty-connect-limits' import type { ConnectPanePtySession } from './connect-pane-pty-session' @@ -7,9 +7,6 @@ import type { WslDistroHoldState } from '../../../../../shared/wsl-distro-stoppe // Clears the saved screen the hold painted so the restore below paints it once. const CLEAR_HELD_SCREEN = '\x1b[0m\x1b[2J\x1b[3J\x1b[H' -// The mounts a user's switch itself causes land well inside this; a later remount does not. -const SWITCH_MOUNT_WINDOW_MS = 10_000 - /** * Whether a local restore may start a stopped WSL distro: only Start, or this pane's first mount * after the user's own switch to its workspace (once per switch, while recent). Anything else is @@ -22,9 +19,7 @@ export function wslRestoreStartArgs( if ( session.runtimeEnvironmentId || userAct || - takeWorkspaceSwitchWake(session.deps.worktreeId, `pane-restore:${session.cacheKey}`, { - withinMs: SWITCH_MOUNT_WINDOW_MS - }) + takeRecentWorkspaceSwitchWake(session.deps.worktreeId, `pane-restore:${session.cacheKey}`) ) { return {} } diff --git a/src/renderer/src/lib/workspace-switch-user-act.ts b/src/renderer/src/lib/workspace-switch-user-act.ts index a2a16621159..cf832e29033 100644 --- a/src/renderer/src/lib/workspace-switch-user-act.ts +++ b/src/renderer/src/lib/workspace-switch-user-act.ts @@ -30,6 +30,9 @@ export function lastUserWorkspaceSwitchAt(workspaceKey: string): number | undefi return lastSwitchAt.get(workspaceKey) } +/** How recent a switch must be for the mounts it causes to count as that switch (`withinMs`). */ +export const WORKSPACE_SWITCH_MOUNT_WINDOW_MS = 10_000 + /** * True once per noted switch for each consumer; a remount or re-render with no new switch is false. * `withinMs` also requires the switch to be that recent (the mounts a switch itself causes). @@ -52,6 +55,13 @@ export function takeWorkspaceSwitchWake( return true } +/** `takeWorkspaceSwitchWake` for a consumer the switch itself mounts or runs. */ +export function takeRecentWorkspaceSwitchWake(workspaceKey: string, consumer: string): boolean { + return takeWorkspaceSwitchWake(workspaceKey, consumer, { + withinMs: WORKSPACE_SWITCH_MOUNT_WINDOW_MS + }) +} + export function resetWorkspaceSwitchUserActsForTests(): void { lastSwitchAt.clear() switchCounts.clear()