diff --git a/src/renderer/src/runtime/close-mirrored-editor-tab.test.ts b/src/renderer/src/runtime/close-mirrored-editor-tab.test.ts index 060bac2a12d..bb63e6d5397 100644 --- a/src/renderer/src/runtime/close-mirrored-editor-tab.test.ts +++ b/src/renderer/src/runtime/close-mirrored-editor-tab.test.ts @@ -16,6 +16,11 @@ import { notifyHostOfMirroredEditorClose, type MirroredEditorCloseState } from './close-mirrored-editor-tab' +import { + isWebSessionCloseIntentPending, + resetWebSessionCloseIntentForTests +} from './web-session-close-intent' +import { toHostSessionTabId } from '../../../shared/terminal-surface-id' function buildState(overrides: Partial = {}): MirroredEditorCloseState { return { @@ -32,6 +37,16 @@ describe('notifyHostOfMirroredEditorClose', () => { closeWebRuntimeSessionTabMock.mockReset() getRuntimeEnvironmentIdForWorktreeMock.mockReset() getRuntimeEnvironmentIdForWorktreeMock.mockReturnValue('env-1') + resetWebSessionCloseIntentForTests() + }) + + it('records the host close intent SYNCHRONOUSLY (before the async close resolves)', () => { + const now = Date.now() + // No await: the intent must be pending the instant the call returns, so a + // host snapshot in the dynamic-import gap can't flash the old-path tab back. + notifyHostOfMirroredEditorClose(buildState(), 'wt-1', 'file-1') + + expect(isWebSessionCloseIntentPending('wt-1', toHostSessionTabId('host-tab-1'), now)).toBe(true) }) it('closes the mirrored editor tab on the host using the host tab id', async () => { diff --git a/src/renderer/src/runtime/close-mirrored-editor-tab.ts b/src/renderer/src/runtime/close-mirrored-editor-tab.ts index ae1372bb52a..250cd4c0ab3 100644 --- a/src/renderer/src/runtime/close-mirrored-editor-tab.ts +++ b/src/renderer/src/runtime/close-mirrored-editor-tab.ts @@ -2,6 +2,8 @@ import { getRuntimeEnvironmentIdForWorktree, type WorktreeRuntimeOwnerState } from '@/lib/worktree-runtime-owner' +import { recordWebSessionCloseIntent } from './web-session-close-intent' +import { toHostSessionTabId } from '../../../shared/terminal-surface-id' import type { OpenFile } from '@/store/slices/editor' import type { Tab } from '../../../shared/types' @@ -40,6 +42,10 @@ export function notifyHostOfMirroredEditorClose( if (!unifiedTab) { return false } + // Record the close intent SYNCHRONOUSLY (leaf modules, no cycle) so a host + // snapshot landing before the dynamic import below resolves can't flash the + // old-path tab back. closeWebRuntimeSessionTab re-records it idempotently. + recordWebSessionCloseIntent(worktreeId, toHostSessionTabId(unifiedTab.id), Date.now()) // Why: this helper is imported by the editor slice during store creation. // Importing web-runtime-session eagerly would import the store back and can // trip cyclic initialization in full-suite test/import order.