fix(runtime): join the resize repaint's seed without per-PTY seed state

Replaces the rendererSeedsByPtyId map with a check over existing state. If
an on-data seed was already considered before the subscribe's hydrate, the
subscribe awaits the model's write chain and reads the outcome from the
hydration state, the installed model and the provider-preferred flag.

Claude-Session: lane-host-hydrate
This commit is contained in:
Jinwoo-H
2026-10-06 00:56:33 -04:00
parent 944b57b8d8
commit 67701cf87d
4 changed files with 20 additions and 14 deletions
@@ -130,8 +130,6 @@ export class OrcaRuntimeWithFitOverrideListeners extends OrcaRuntimeWithStopRequ
// Absent → hydration has not been considered yet for this PTY. // Absent → hydration has not been considered yet for this PTY.
// See docs/mobile-prefer-renderer-scrollback.md. // See docs/mobile-prefer-renderer-scrollback.md.
protected headlessHydrationState = new Map<string, 'pending' | 'done'>() protected headlessHydrationState = new Map<string, 'pending' | 'done'>()
/** The model's renderer seed, resolving true once written; lives as long as the model. */
protected rendererSeedsByPtyId = new Map<string, Promise<boolean>>()
// Why: mobile-fit overrides are keyed by ptyId (not terminal handle) because // Why: mobile-fit overrides are keyed by ptyId (not terminal handle) because
// handles can be reissued while the PTY identity is stable. In-memory only — // handles can be reissued while the PTY identity is stable. In-memory only —
@@ -110,19 +110,32 @@ export class OrcaRuntimeWithMaybeHydrateHeadlessFromRenderer extends OrcaRuntime
} }
} }
}) })
const seed = state.writeChain.then(() => seeded) return state.writeChain.then(() => seeded)
this.rendererSeedsByPtyId.set(ptyId, seed)
return seed
} }
/** Public: a phone fit resizes the PTY, but an idle TUI sends no byte to hydrate on. Resolves /** Public: a phone fit resizes the PTY, but an idle TUI sends no byte to hydrate on. Resolves
* true once the model holds the pane's screen reflowed onto the PTY's current grid. */ * true once the model holds the pane's screen reflowed onto the PTY's current grid. */
hydrateHeadlessTerminalFromRenderer(ptyId: string): Promise<boolean> { hydrateHeadlessTerminalFromRenderer(ptyId: string): Promise<boolean> {
// Why join: a resize repaint can run the on-data seed first; its result answers this caller too. const priorHydration = this.headlessHydrationState.get(ptyId)
const seed = this.maybeHydrateHeadlessFromRenderer(ptyId)
if (seed) {
return seed
}
// Why: a resize repaint can run the on-data seed before this call; join its outcome instead.
return priorHydration ? this.readRendererSeedOutcome(ptyId) : Promise.resolve(false)
}
private async readRendererSeedOutcome(ptyId: string): Promise<boolean> {
const state = this.headlessTerminals.get(ptyId)
if (!state) {
return false
}
await state.writeChain
// Why: a failed seed also ends 'done'; treated as seeded, since that model is on the PTY grid.
return ( return (
this.maybeHydrateHeadlessFromRenderer(ptyId) ?? this.headlessHydrationState.get(ptyId) === 'done' &&
this.rendererSeedsByPtyId.get(ptyId) ?? this.headlessTerminals.get(ptyId) === state &&
Promise.resolve(false) !this.providerSnapshotPreferredPtys.has(ptyId)
) )
} }
@@ -75,7 +75,6 @@ export class OrcaRuntimeWithSerializeHeadlessTerminalBuffer extends OrcaRuntimeW
protected disposeHeadlessTerminal(ptyId: string): void { protected disposeHeadlessTerminal(ptyId: string): void {
this.headlessHydrationState.delete(ptyId) this.headlessHydrationState.delete(ptyId)
this.rendererSeedsByPtyId.delete(ptyId)
const state = this.headlessTerminals.get(ptyId) const state = this.headlessTerminals.get(ptyId)
if (!state) { if (!state) {
return return
@@ -41,10 +41,6 @@ const CLEARED_BY_REAPER_HELPER: Record<string, { helper: string; module: string
agentPromptPermissionSequenceByPtyId: { agentPromptPermissionSequenceByPtyId: {
helper: 'advancePtyLifecycleGeneration', helper: 'advancePtyLifecycleGeneration',
module: 'src/main/runtime/orca-runtime-record-agent-prompt-lifecycle-state.ts' module: 'src/main/runtime/orca-runtime-record-agent-prompt-lifecycle-state.ts'
},
rendererSeedsByPtyId: {
helper: 'disposeHeadlessTerminal',
module: 'src/main/runtime/orca-runtime-serialize-headless-terminal-buffer.ts'
} }
} }