diff --git a/src/main/ipc/session.ts b/src/main/ipc/session.ts index cdde869a5e6..b0b8860c633 100644 --- a/src/main/ipc/session.ts +++ b/src/main/ipc/session.ts @@ -3,6 +3,7 @@ import type { Store } from '../persistence' import type { OrcaRuntimeService } from '../runtime/orca-runtime' import { parseTerminalSurfaceCloseTarget } from '../../shared/terminal-surface-close-target' import { isFrozenOrcadSourceSessionPartition } from '../ssh/orcad-retained-source' +import { visibleSessionHostIds } from '../ssh/orcad-retained-source-session' import { markAgentLaunchesClosedByUser } from '../agent-launch/agent-launch-pane-attachment' import { windowViewPatchForMain, windowViewSaveForMain } from '../persistence/window-save-view' import { reportWindowSaveDrift } from '../persistence/window-save-divergence' @@ -25,7 +26,7 @@ export function registerSessionHandlers(store: Store, runtime: OrcaRuntimeServic // Repo catalogs omit folder-only SSH hosts; enumerate session partitions directly (#12723). ipcMain.handle('session:list-host-ids', () => { - return store.getWorkspaceSessionHostIds() + return visibleSessionHostIds(store, store.getWorkspaceSessionHostIds()) }) // Main owns layout membership; merge the window's view and facts, then report layout drift. diff --git a/src/main/ssh/orcad-retained-source-lists.test.ts b/src/main/ssh/orcad-retained-source-lists.test.ts index bb931a7b38e..6d0f2821300 100644 --- a/src/main/ssh/orcad-retained-source-lists.test.ts +++ b/src/main/ssh/orcad-retained-source-lists.test.ts @@ -25,6 +25,7 @@ vi.mock('../../shared/app-environment', () => ({ const { isFrozenOrcadSourceSessionPartition, visibleProjectGroups, visibleProjectHostSetups } = await import('./orcad-retained-source') +const { visibleSessionHostIds } = await import('./orcad-retained-source-session') const FENCED: SshTarget = { id: 'ssh-box', @@ -108,6 +109,26 @@ describe('lists while a converted host keeps its source rows', () => { ]) }) + // #33539: adopting the hidden source's session at startup restored tabs closed on the server. + it('leaves the hidden host session out of the partition census until the host changes', () => { + const hostIds: ExecutionHostId[] = ['local', 'ssh:ssh-box', 'ssh:other', 'runtime:env-1'] + expect(visibleSessionHostIds(catalog([FENCED]), hostIds)).toEqual([ + 'local', + 'ssh:other', + 'runtime:env-1' + ]) + const changed = { ...FENCED, orcadFence: { environmentId: 'env-1', sourceChangedAt: 'x' } } + expect(visibleSessionHostIds(catalog([changed]), hostIds)).toEqual(hostIds) + // A fenced move that has not committed still shows its repos, so their session stays too. + const inFlight = mkdtempSync(join(tmpdir(), 'orcad-retained-lists-')) + try { + writeOrcadMigrationSourceCutover(inFlight, orcadMigrationCutoverFixture('m-1', FENCED.id)) + expect(visibleSessionHostIds(catalog([FENCED]), hostIds, () => inFlight)).toEqual(hostIds) + } finally { + rmSync(inFlight, { recursive: true, force: true }) + } + }) + it('shows them until the migration commits, while freezing the session from the fence on', () => { const userDataPath = mkdtempSync(join(tmpdir(), 'orcad-retained-lists-')) try { diff --git a/src/main/ssh/orcad-retained-source-session.ts b/src/main/ssh/orcad-retained-source-session.ts new file mode 100644 index 00000000000..d9fdfbf33e6 --- /dev/null +++ b/src/main/ssh/orcad-retained-source-session.ts @@ -0,0 +1,16 @@ +import { parseExecutionHostId, type ExecutionHostId } from '../../shared/execution-host' +import type { Store } from '../persistence' +import { appUserDataPath, hiddenRetainedSourceTargetIds } from './orcad-retained-source' + +/** A hidden source's session goes with its repos: a window adopting it at startup brings closed tabs back (#33539). */ +export function visibleSessionHostIds( + store: Pick, + hostIds: readonly ExecutionHostId[], + getUserDataPath = appUserDataPath +): ExecutionHostId[] { + const hidden = new Set(hiddenRetainedSourceTargetIds(getUserDataPath, store.getSshTargets())) + return hostIds.filter((hostId) => { + const parsed = parseExecutionHostId(hostId) + return parsed?.kind !== 'ssh' || !hidden.has(parsed.targetId) + }) +} diff --git a/src/main/ssh/orcad-retained-source.ts b/src/main/ssh/orcad-retained-source.ts index 9b3f29100b7..dc8c3354835 100644 --- a/src/main/ssh/orcad-retained-source.ts +++ b/src/main/ssh/orcad-retained-source.ts @@ -36,7 +36,7 @@ import { withoutWslMovedRepos } from '../persistence/moving-wsl-projects/wsl-move-scope-guard' -const appUserDataPath = (): string => getAppEnvironment().getPath('userData') +export const appUserDataPath = (): string => getAppEnvironment().getPath('userData') type CatalogStore = Pick type TargetStore = Pick @@ -64,7 +64,7 @@ function isHiddenRetainedSourceTarget( } /** Delay journal reads until a host is fenced. */ -function hiddenRetainedSourceTargetIds( +export function hiddenRetainedSourceTargetIds( getUserDataPath: () => string, targets: readonly SshTarget[] ): string[] {