From 28824cda7847ef021d70eaad58a015f92a9ae057 Mon Sep 17 00:00:00 2001 From: Lesley Murfin Date: Mon, 22 Jun 2026 14:35:32 -0600 Subject: [PATCH] Don't show local Windows shells for serve-runtime worktrees (#5947) * Don't show local Windows shells for serve-runtime worktrees The Windows shell menu (PowerShell/CMD/Git Bash) was offered for a serve/remote-runtime worktree whose host is not Windows (e.g. a Linux orca serve), where those local shell choices are meaningless and the plain New Terminal already opens the runtime's default shell. AND a new runtimeHostIsNonWindows exclusion into the existing shouldShowWindowsShellMenu gate, keyed on the probed runtime host platform so a LOCAL Windows-WSL project runtime (hostPlatform === win32) keeps its shell menu. Adds regression tests. Rebased onto current main (was 75 commits stale); net delta unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) * Hide runtime Windows shells until host is known Co-authored-by: Orca --------- Co-authored-by: Claude Opus 4.8 (1M context) Co-authored-by: Jinwoo-H Co-authored-by: Orca --- .../src/components/tab-bar/TabBar.tsx | 20 +++--- .../TabBar.windows-shell-launch.test.ts | 64 +++++++++++++++++++ .../windows-shell-menu-visibility.test.ts | 59 +++++++++++++++++ .../tab-bar/windows-shell-menu-visibility.ts | 16 +++++ 4 files changed, 150 insertions(+), 9 deletions(-) create mode 100644 src/renderer/src/components/tab-bar/windows-shell-menu-visibility.test.ts create mode 100644 src/renderer/src/components/tab-bar/windows-shell-menu-visibility.ts diff --git a/src/renderer/src/components/tab-bar/TabBar.tsx b/src/renderer/src/components/tab-bar/TabBar.tsx index 313186f46f2..a6ecdc5917b 100644 --- a/src/renderer/src/components/tab-bar/TabBar.tsx +++ b/src/renderer/src/components/tab-bar/TabBar.tsx @@ -76,6 +76,7 @@ import { TabStripScrollIndicator } from './TabStripScrollIndicator' import { getTabStripScrollMaskClassName } from './tab-strip-scroll-metrics' import { useTabStripOverflowNavigation } from './tab-strip-overflow-navigation' import { useTabStripDragScrollHandlers } from './tab-strip-drag-scroll' +import { shouldShowWindowsShellMenu } from './windows-shell-menu-visibility' const isWindows = navigator.userAgent.includes('Windows') const isMacOs = navigator.userAgent.includes('Mac') @@ -374,13 +375,14 @@ function TabBarInner({ windowsTerminalCapabilityOwnerKey, runtimeTarget ) - // Why: SSH-backed PTYs ignore local Windows shell overrides; showing these - // entries there promises PowerShell/CMD/Git Bash but opens the remote shell. - const shouldShowWindowsShellMenu = - (isWindows || windowsTerminalCapabilities.hostPlatform === 'win32') && - !worktreeHasRemoteConnection + const showWindowsShellMenu = shouldShowWindowsShellMenu({ + activeRuntimeEnvironmentId, + hostPlatform: windowsTerminalCapabilities.hostPlatform, + isWindowsClient: isWindows, + worktreeHasRemoteConnection + }) const localProjectRuntime = useMemo(() => { - if (!shouldShowWindowsShellMenu || activeRuntimeEnvironmentId?.trim()) { + if (!showWindowsShellMenu || activeRuntimeEnvironmentId?.trim()) { return undefined } return getLocalProjectExecutionRuntimeContext( @@ -410,7 +412,7 @@ function TabBarInner({ projects, repos, settings, - shouldShowWindowsShellMenu, + showWindowsShellMenu, windowsTerminalCapabilities.isLoading, windowsTerminalCapabilities.wslAvailable, windowsTerminalCapabilities.wslDistros, @@ -491,7 +493,7 @@ function TabBarInner({ pendingNewTabMenuFocusRef.current = () => focusTerminalTabSurface(tabId) } const windowsShellEntries = useMemo(() => { - if (!shouldShowWindowsShellMenu || !onNewTerminalWithShell) { + if (!showWindowsShellMenu || !onNewTerminalWithShell) { return undefined } const includeHostShells = projectRuntimeShellMenuMode !== 'wsl' @@ -538,7 +540,7 @@ function TabBarInner({ defaultWindowsShell, onNewTerminalWithShell, projectRuntimeShellMenuMode, - shouldShowWindowsShellMenu, + showWindowsShellMenu, windowsTerminalCapabilities.gitBashAvailable, windowsTerminalCapabilities.wslAvailable ]) diff --git a/src/renderer/src/components/tab-bar/TabBar.windows-shell-launch.test.ts b/src/renderer/src/components/tab-bar/TabBar.windows-shell-launch.test.ts index 4a7da1c89de..7c92ba6aa86 100644 --- a/src/renderer/src/components/tab-bar/TabBar.windows-shell-launch.test.ts +++ b/src/renderer/src/components/tab-bar/TabBar.windows-shell-launch.test.ts @@ -751,4 +751,68 @@ describe('TabBar PowerShell launch wiring', () => { expect(findDropdownMenuItemByText(expandNode(element), 'New Terminal: PowerShell')).toBeNull() expect(findDropdownMenuItemByText(expandNode(element), 'New Terminal')).not.toBeNull() }) + + it('hides local Windows shell rows for a non-Windows serve runtime', async () => { + // Why: a Windows desktop client paired to a Linux `orca serve` runs its PTY on + // the serve host. The local Windows shell choices (PowerShell/CMD/WSL) are + // meaningless there; the plain "New Terminal" already opens the serve's default + // shell. Sibling tests above assert that a win32 remote host still shows the + // rows, so the LOCAL Windows-WSL project-runtime menu (hostPlatform 'win32') + // is unaffected by this suppression. + vi.stubGlobal('navigator', { userAgent: 'Windows' }) + vi.stubGlobal('__ORCA_WEB_CLIENT__', false) + vi.stubGlobal('window', { + api: { + wsl: { + isAvailable: vi.fn().mockResolvedValue(false), + listDistros: vi.fn().mockResolvedValue([]) + }, + pwsh: { isAvailable: vi.fn().mockResolvedValue(false) }, + gitBash: { isAvailable: vi.fn().mockResolvedValue(false) }, + runtime: { getStatus: vi.fn().mockResolvedValue({ hostPlatform: 'linux' }) } + } + }) + appStoreSnapshot.activeRuntimeEnvironmentId = 'serve-env-1' + const capabilities = await import('@/lib/windows-terminal-capabilities') + await capabilities.loadWindowsTerminalCapabilities({ + force: true, + ownerKey: 'runtime:serve-env-1' + }) + + const tabBarModule = await import('./TabBar') + const candidate = tabBarModule.default ?? tabBarModule + const TabBar = + typeof candidate === 'function' + ? candidate + : typeof (candidate as { type?: unknown }).type === 'function' + ? (candidate as { type: (props: Record) => unknown }).type + : null + expect(TabBar).not.toBeNull() + + const element = TabBar!({ + tabs: [], + activeTabId: null, + worktreeId: 'wt-1', + expandedPaneByTabId: {}, + onActivate: () => {}, + onClose: () => {}, + onCloseOthers: () => {}, + onCloseToRight: () => {}, + onNewTerminalTab: () => {}, + onNewTerminalWithShell: vi.fn(), + onNewBrowserTab: () => {}, + onSetCustomTitle: () => {}, + onSetTabColor: () => {}, + onTogglePaneExpand: () => {} + }) + + expect( + findDropdownMenuItemByText(expandNode(element), 'New Terminal: PowerShell') + ).toBeNull() + expect( + findDropdownMenuItemByText(expandNode(element), 'New Terminal: CMD Prompt') + ).toBeNull() + expect(findDropdownMenuItemByText(expandNode(element), 'New Terminal: WSL')).toBeNull() + expect(findDropdownMenuItemByText(expandNode(element), 'New Terminal')).not.toBeNull() + }) }) diff --git a/src/renderer/src/components/tab-bar/windows-shell-menu-visibility.test.ts b/src/renderer/src/components/tab-bar/windows-shell-menu-visibility.test.ts new file mode 100644 index 00000000000..fefc1ca44a4 --- /dev/null +++ b/src/renderer/src/components/tab-bar/windows-shell-menu-visibility.test.ts @@ -0,0 +1,59 @@ +import { describe, expect, it } from 'vitest' +import { shouldShowWindowsShellMenu } from './windows-shell-menu-visibility' + +describe('shouldShowWindowsShellMenu', () => { + it('shows local Windows shells for a local Windows client without a runtime owner', () => { + expect( + shouldShowWindowsShellMenu({ + activeRuntimeEnvironmentId: null, + hostPlatform: null, + isWindowsClient: true, + worktreeHasRemoteConnection: false + }) + ).toBe(true) + }) + + it('keeps the menu hidden while a runtime host platform is unknown', () => { + expect( + shouldShowWindowsShellMenu({ + activeRuntimeEnvironmentId: 'serve-env-1', + hostPlatform: null, + isWindowsClient: true, + worktreeHasRemoteConnection: false + }) + ).toBe(false) + }) + + it('shows runtime Windows shells only after the runtime host is known to be Windows', () => { + expect( + shouldShowWindowsShellMenu({ + activeRuntimeEnvironmentId: 'serve-env-1', + hostPlatform: 'win32', + isWindowsClient: false, + worktreeHasRemoteConnection: false + }) + ).toBe(true) + }) + + it('hides local Windows shells for a non-Windows runtime host', () => { + expect( + shouldShowWindowsShellMenu({ + activeRuntimeEnvironmentId: 'serve-env-1', + hostPlatform: 'linux', + isWindowsClient: true, + worktreeHasRemoteConnection: false + }) + ).toBe(false) + }) + + it('hides local Windows shells for SSH worktrees', () => { + expect( + shouldShowWindowsShellMenu({ + activeRuntimeEnvironmentId: null, + hostPlatform: 'win32', + isWindowsClient: true, + worktreeHasRemoteConnection: true + }) + ).toBe(false) + }) +}) diff --git a/src/renderer/src/components/tab-bar/windows-shell-menu-visibility.ts b/src/renderer/src/components/tab-bar/windows-shell-menu-visibility.ts new file mode 100644 index 00000000000..48af2956238 --- /dev/null +++ b/src/renderer/src/components/tab-bar/windows-shell-menu-visibility.ts @@ -0,0 +1,16 @@ +export function shouldShowWindowsShellMenu(args: { + activeRuntimeEnvironmentId: string | null | undefined + hostPlatform: NodeJS.Platform | null + isWindowsClient: boolean + worktreeHasRemoteConnection: boolean +}): boolean { + // Why: runtime terminals execute on the runtime host. Until that host is known + // to be Windows, local Windows shell choices would advertise the wrong target. + const runtimeHostIsNotKnownWindows = + Boolean(args.activeRuntimeEnvironmentId?.trim()) && args.hostPlatform !== 'win32' + return ( + (args.isWindowsClient || args.hostPlatform === 'win32') && + !args.worktreeHasRemoteConnection && + !runtimeHostIsNotKnownWindows + ) +}