refactor(worktree): split background pane exit-output recording out

The sequenced-command fix pushed launch-worktree-background-terminals.ts
to 303 effective lines, over the 300 cap. Moved persistExitedPaneOutput
and registerBackgroundPaneBuffer, which are one concern, into
background-pane-exit-output.ts. No behavior change: dropping the
registerEagerPtyBuffer call still reddens the background-terminals suite.
This commit is contained in:
Brennan Benson
2026-08-31 14:08:01 -07:00
committed by Merge Sim
parent d003872592
commit aca1238fef
2 changed files with 50 additions and 44 deletions
@@ -0,0 +1,49 @@
import {
registerEagerPtyBuffer,
type EagerPtyHandle
} from '@/components/terminal-pane/pty-dispatcher'
import { useAppStore } from '@/store'
/** The id a background pane got, plus which lifetime of it this spawn owns. */
export type SpawnedPane = { ptyId: string; incarnationId?: string }
function persistExitedPaneOutput(tabId: string, leafId: string, output: string): void {
const store = useAppStore.getState()
const layout = store.terminalLayoutsByTabId[tabId]
if (!layout) {
return
}
const { ptyIdsByLeafId: existingPtyIds, buffersByLeafId: existingBuffers, ...rest } = layout
const nextPtyIds = { ...existingPtyIds }
delete nextPtyIds[leafId]
const trimmedOutput = output.trim() ? output : ''
store.setTabLayout(tabId, {
...rest,
...(Object.keys(nextPtyIds).length > 0 ? { ptyIdsByLeafId: nextPtyIds } : {}),
...(trimmedOutput
? {
buffersByLeafId: {
...existingBuffers,
[leafId]: output
}
}
: existingBuffers
? { buffersByLeafId: existingBuffers }
: {})
})
}
// Why the incarnation: a relay-recycled id can hold the previous owner's exit, and draining that
// into this handler tears the pane down seconds after it launched.
export function registerBackgroundPaneBuffer(
tabId: string,
leafId: string,
pane: SpawnedPane
): void {
let eagerBuffer: EagerPtyHandle | null = null
const onExit = (exitPtyId: string): void => {
persistExitedPaneOutput(tabId, leafId, eagerBuffer?.flush() ?? '')
useAppStore.getState().clearTabPtyId(tabId, exitPtyId)
}
eagerBuffer = registerEagerPtyBuffer(pane.ptyId, onExit, pane.incarnationId)
}
@@ -1,7 +1,4 @@
import {
registerEagerPtyBuffer,
type EagerPtyHandle
} from '@/components/terminal-pane/pty-dispatcher'
import { registerBackgroundPaneBuffer, type SpawnedPane } from '@/lib/background-pane-exit-output'
import { createBrowserUuid } from '@/lib/browser-uuid'
import { getSettingsForWorktreeRuntimeOwner } from '@/lib/worktree-runtime-owner'
import { getActiveRuntimeTarget } from '@/runtime/runtime-rpc-client'
@@ -87,43 +84,6 @@ function buildSplitLayout(
}
}
function persistExitedPaneOutput(tabId: string, leafId: string, output: string): void {
const store = useAppStore.getState()
const layout = store.terminalLayoutsByTabId[tabId]
if (!layout) {
return
}
const { ptyIdsByLeafId: existingPtyIds, buffersByLeafId: existingBuffers, ...rest } = layout
const nextPtyIds = { ...existingPtyIds }
delete nextPtyIds[leafId]
const trimmedOutput = output.trim() ? output : ''
store.setTabLayout(tabId, {
...rest,
...(Object.keys(nextPtyIds).length > 0 ? { ptyIdsByLeafId: nextPtyIds } : {}),
...(trimmedOutput
? {
buffersByLeafId: {
...existingBuffers,
[leafId]: output
}
}
: existingBuffers
? { buffersByLeafId: existingBuffers }
: {})
})
}
// Why the incarnation: a relay-recycled id can hold the previous owner's exit, and draining that
// into this handler tears the pane down seconds after it launched.
function registerBackgroundPaneBuffer(tabId: string, leafId: string, pane: SpawnedPane): void {
let eagerBuffer: EagerPtyHandle | null = null
const onExit = (exitPtyId: string): void => {
persistExitedPaneOutput(tabId, leafId, eagerBuffer?.flush() ?? '')
useAppStore.getState().clearTabPtyId(tabId, exitPtyId)
}
eagerBuffer = registerEagerPtyBuffer(pane.ptyId, onExit, pane.incarnationId)
}
function buildSetupCommand(setup: WorktreeSetupLaunch): string {
// Why: a sequenced launch carries the gated command that records setup's outcome for a waiting
// agent terminal; rebuilding the bare runner here ran setup but recorded nothing, so the agent
@@ -139,9 +99,6 @@ function buildSetupCommand(setup: WorktreeSetupLaunch): string {
)
}
/** The id a background pane got, plus which lifetime of it this spawn owns. */
type SpawnedPane = { ptyId: string; incarnationId?: string }
async function spawnPane(args: {
worktree: Worktree
connectionId: string | null