diff --git a/src/main/persistence/migrating-orcad-catalog/orcad-source-workspace-session-eligibility.ts b/src/main/persistence/migrating-orcad-catalog/orcad-source-workspace-session-eligibility.ts index 2a43579a9b8..44e48a5a57a 100644 --- a/src/main/persistence/migrating-orcad-catalog/orcad-source-workspace-session-eligibility.ts +++ b/src/main/persistence/migrating-orcad-catalog/orcad-source-workspace-session-eligibility.ts @@ -97,9 +97,8 @@ export function countUnsupportedSessionState( ? 1 : 0 count += session.activeWorkspaceExecutionHostId === scope.hostId && !sourceHostPartition ? 1 : 0 - count += (session.activeConnectionIdsAtShutdown ?? []).filter( - (targetId) => targetId === scope.targetId - ).length + // activeConnectionIdsAtShutdown is not counted: it is the renderer's live "connected now" hint, and + // the remote work it can stand for (tab PTYs, remote session ids, leases) is counted on its own. count += session.activeTabId && terminalTabIds.has(session.activeTabId) && !sourceHostPartition ? 1 : 0 for (const [ownerKey, files] of Object.entries(session.openFilesByWorktree ?? {})) { diff --git a/src/main/ssh/ssh-target-orcad-dependents.test.ts b/src/main/ssh/ssh-target-orcad-dependents.test.ts index ab663c2ed5b..5c37a6a41d1 100644 --- a/src/main/ssh/ssh-target-orcad-dependents.test.ts +++ b/src/main/ssh/ssh-target-orcad-dependents.test.ts @@ -25,6 +25,29 @@ describe('client state that still references an SSH target', () => { expect(blockersFor({ getWorkspaceSessionHostIds: () => ['local', HOST] })).toEqual([]) }) + it('ignores a connected host: the reconnect hint and global copies in its partition', () => { + const connected = { + ...getDefaultWorkspaceSession(), + activeConnectionIdsAtShutdown: ['ssh-1'], + activeWorktreeId: 'local-wt', + browserUrlHistory: [ + { + url: 'https://example.com', + normalizedUrl: 'https://example.com', + title: 'Example', + lastVisitedAt: 1, + visitCount: 1 + } + ] + } + expect( + blockersFor({ + getWorkspaceSessionHostIds: () => ['local', HOST], + getWorkspaceSession: () => connected + }) + ).toEqual([]) + }) + it('blocks on the host session partition and names the fields holding state', () => { const blockers = blockersFor({ getWorkspaceSessionHostIds: () => ['local', HOST], diff --git a/src/main/ssh/ssh-target-orcad-dependents.ts b/src/main/ssh/ssh-target-orcad-dependents.ts index 9845bb46a80..453a33238e6 100644 --- a/src/main/ssh/ssh-target-orcad-dependents.ts +++ b/src/main/ssh/ssh-target-orcad-dependents.ts @@ -5,6 +5,7 @@ import type { Store } from '../persistence' import { getDefaultWorkspaceSession } from '../../shared/constants' import { toSshExecutionHostId } from '../../shared/execution-host' +import { GLOBAL_WORKSPACE_SESSION_FIELDS } from '../../shared/workspace-session-host-field-ownership' import { ORCAD_MIGRATION_DEPENDENCY_KINDS, type OrcadMigrationBlocker, @@ -112,7 +113,7 @@ export function collectUntransferredDependentBlockers( : [] } -/** Non-default fields of the host's session partition, plus a local session pointed at the host. */ +/** Non-default host-owned fields of the host's partition, plus a local session pointed at the host. */ function workspaceSessionReferences(store: DependentStateStore, hostId: string): string[] { const references: string[] = [] if (store.getWorkspaceSession().activeWorkspaceExecutionHostId === hostId) { @@ -122,7 +123,12 @@ function workspaceSessionReferences(store: DependentStateStore, hostId: string): return references } const defaults: Record = { ...getDefaultWorkspaceSession() } + // A partition's global fields are copies of client-wide focus and history, not host state. + const globalFields = new Set(GLOBAL_WORKSPACE_SESSION_FIELDS) for (const [field, value] of Object.entries(store.getWorkspaceSession(hostId))) { + if (globalFields.has(field)) { + continue + } if (!isEmptyValue(value) && JSON.stringify(value) !== JSON.stringify(defaults[field])) { references.push(field) } diff --git a/src/main/ssh/ssh-target-orcad-preflight.test.ts b/src/main/ssh/ssh-target-orcad-preflight.test.ts index e282f862079..3c7ea3eab84 100644 --- a/src/main/ssh/ssh-target-orcad-preflight.test.ts +++ b/src/main/ssh/ssh-target-orcad-preflight.test.ts @@ -72,6 +72,18 @@ describe('migration export preflight', () => { ]) }) + it('previews a connected but unused host as movable with no blockers', () => { + const store = preflightStore((store) => { + store.addSshTarget(TARGET) + // What the renderer writes on connect: the reconnect hint names the now-connected target. + store.patchWorkspaceSession({ activeConnectionIdsAtShutdown: [TARGET.id] }) + }) + expect(preflightOrcadMigrationExport(store, TARGET.id)).toMatchObject({ + claimable: true, + blockers: [] + }) + }) + it('blocks on a live relay terminal, which cannot move', () => { const lease: SshRemotePtyLease = { targetId: TARGET.id,