fix: release scroll intents for closed parked tabs

This commit is contained in:
m4air
2026-09-15 17:58:57 -07:00
parent e4d7adf175
commit 62b77fce3c
2 changed files with 32 additions and 1 deletions
@@ -7,6 +7,10 @@ import {
reconcileParkedWatcherPtyIds,
resolveParkedTerminalPaneCandidates
} from './terminal-parked-watcher-reconciliation'
import {
readTerminalScrollIntentKeyRetention,
writeKeyedTerminalScrollIntent
} from '../../lib/pane-manager/terminal-scroll-intent-key-store'
const TAB_ID = 'tab-1'
const WORKTREE_ID = 'repo::/worktree'
@@ -86,6 +90,23 @@ describe('paired parked-watcher reconciliation', () => {
})
})
it('releases captured scroll-intent keys when a parked tab is closed', () => {
writeKeyedTerminalScrollIntent(FIRST_LEAF_ID, {
kind: 'pinnedViewport',
bufferType: 'normal',
viewportY: 4,
baseY: 12,
revision: 1
})
captureParkedTerminalPaneCandidates(TAB_ID, WORKTREE_ID, [
{ ptyId: FIRST_PTY_ID, paneId: 1, leafId: FIRST_LEAF_ID, drivesTabTitle: true }
])
expect(readTerminalScrollIntentKeyRetention().intents).toBe(1)
retireParkedTerminalTab(TAB_ID)
expect(readTerminalScrollIntentKeyRetention().intents).toBe(0)
})
// Why: the sole-newborn parity flag is a fact about the captured PTY, so the
// layout-fallback rescue must carry it only while the leaf still binds that PTY.
describe('untouchedFreshSpawn carry through the layout-fallback rescue', () => {
@@ -10,6 +10,7 @@
import { discardPreHandlerPtyState, hasPreHandlerPtyExit } from './pty-pre-handler-buffer'
import { parseRemoteRuntimePtyId } from '../../../../shared/remote-runtime-pty-id'
import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../../shared/constants'
import { releaseTerminalScrollIntentKey } from '../../lib/pane-manager/terminal-scroll-intent-key-store'
export type ParkedTerminalPaneCapture = {
ptyId: string | null
@@ -140,7 +141,16 @@ export function retireParkedTerminalTab(tabId: string): void {
// Why: explicit tab retirement permanently invalidates both live parked
// observers and unmounted-pane candidates; neither may reattach later.
disposeParkedTabWatchers(tabId)
capturedPanesByTabId.delete(tabId)
const capture = capturedPanesByTabId.get(tabId)
if (capture) {
// Parked panes never run PaneManager's close teardown. Release their
// strong scroll-intent keys here or every closed parked tab leaks one per
// leaf for the renderer lifetime.
for (const pane of capture.panes) {
releaseTerminalScrollIntentKey(pane.leafId)
}
capturedPanesByTabId.delete(tabId)
}
}
/**