mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
Start a startup terminal while Orca's window is hidden instead of waiting for it to be shown (#28351)
* fix(terminal): don't hold a startup command for the grid settle while the window is hidden A startup pane waits for its grid to stop changing, counted in animation frames, so a TUI isn't drawn at a transient size. A covered or minimized window produces no frames, so the launch waited until Orca was visible again. Skip the settle while the window is hidden, as hidden panes already do, and end an in-flight settle when the window becomes hidden. * test(terminal): pass connectPanePty doubles through one checked cast
This commit is contained in:
@@ -341,6 +341,63 @@ describe('connectPanePty', () => {
|
||||
expect(transport.connect).toHaveBeenCalledWith(expect.objectContaining({ cols: 120, rows: 50 }))
|
||||
})
|
||||
|
||||
it.each(['before', 'during'] as const)(
|
||||
'spawns a startup command without waiting for frames when the window is hidden %s the grid settle',
|
||||
async (hiddenWhen) => {
|
||||
// A hidden window produces no animation frames, so only the queued ones below ever run.
|
||||
const frameCallbacks: FrameRequestCallback[] = []
|
||||
globalThis.requestAnimationFrame = vi.fn((callback: FrameRequestCallback) => {
|
||||
frameCallbacks.push(callback)
|
||||
return frameCallbacks.length
|
||||
})
|
||||
const visibilityListeners: (() => void)[] = []
|
||||
const fakeDocument = {
|
||||
visibilityState: hiddenWhen === 'before' ? 'hidden' : 'visible',
|
||||
addEventListener: vi.fn((type: string, listener: () => void) => {
|
||||
if (type === 'visibilitychange') {
|
||||
visibilityListeners.push(listener)
|
||||
}
|
||||
}),
|
||||
removeEventListener: vi.fn()
|
||||
}
|
||||
vi.stubGlobal('document', fakeDocument)
|
||||
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] })
|
||||
|
||||
const { connectPanePty } = await import('./pty-connection')
|
||||
const transport = createMockTransport()
|
||||
transportFactoryQueue.push(transport)
|
||||
const pane = createPane(1)
|
||||
pane.container = createMeasuredElement({ rect: () => createRect(1200, 800) })
|
||||
pane.fitAddon.proposeDimensions = vi.fn(() => ({ cols: 120, rows: 40 }))
|
||||
pane.fitAddon.fit = vi.fn(() => {
|
||||
pane.terminal.cols = 120
|
||||
pane.terminal.rows = 40
|
||||
})
|
||||
const deps = createDeps({ startup: { command: 'claude' } })
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the pane, manager and deps doubles implement every member connectPanePty reads on this startup path.
|
||||
const args = [pane, createManager(1), deps] as unknown as Parameters<typeof connectPanePty>
|
||||
connectPanePty(...args)
|
||||
|
||||
if (hiddenWhen === 'before') {
|
||||
vi.advanceTimersByTime(250)
|
||||
} else {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
frameCallbacks.shift()?.(0)
|
||||
}
|
||||
expect(transport.connect).not.toHaveBeenCalled()
|
||||
fakeDocument.visibilityState = 'hidden'
|
||||
for (const listener of visibilityListeners) {
|
||||
listener()
|
||||
}
|
||||
}
|
||||
|
||||
expect(createdTransportOptions[0]?.command).toBe('claude')
|
||||
expect(transport.connect).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ cols: 120, rows: 40 })
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
it('spawns the delayed main setup-split pane when its sibling owns the tab PTY fallback', async () => {
|
||||
const frameCallbacks: FrameRequestCallback[] = []
|
||||
globalThis.requestAnimationFrame = vi.fn((callback: FrameRequestCallback) => {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { reconcilePtySizeAcrossFrames } from '../pty-size-reconcile'
|
||||
import { shouldClaimRemoteDesktopViewport } from '../remote-desktop-viewport-claim'
|
||||
import { deferTerminalGeometryMutationDuringRebuild } from '@/lib/pane-manager/terminal-scroll-intent-rebuild'
|
||||
import { waitForStableStartupGrid } from '../terminal-startup-grid-settle'
|
||||
import { getWindowParkVisible, subscribeWindowParkVisibility } from '@/lib/window-park-visibility'
|
||||
|
||||
import { isSetupSplitGeometryReady } from './setup-split-geometry'
|
||||
|
||||
@@ -207,9 +208,12 @@ export function installPtyResizeGeometry(session: ConnectPanePtySession): void {
|
||||
const rows = session.pane.terminal.rows
|
||||
return cols > 0 && rows > 0 ? { cols, rows } : null
|
||||
}
|
||||
// Why: the settle only prevents visible artifacts, and a hidden pane or window paints none
|
||||
// (nor produces the frames the settle counts).
|
||||
session.shouldSettleStartupGridBeforeConnect = (): boolean =>
|
||||
Boolean(session.paneStartup?.command) &&
|
||||
session.deps.isVisibleRef.current &&
|
||||
getWindowParkVisible() &&
|
||||
!session.unservedSshTargetId &&
|
||||
session.runtimeEnvironmentId === null
|
||||
session.isStartupGridReadyForConnect = (): boolean => {
|
||||
@@ -223,7 +227,14 @@ export function installPtyResizeGeometry(session: ConnectPanePtySession): void {
|
||||
}
|
||||
session.settleStartupGridBeforeConnect = (connect: () => void): void => {
|
||||
session.startupGridSettleHandle?.cancel()
|
||||
let settledSynchronously = false
|
||||
let settled = false
|
||||
let unsubscribeWindowVisibility = (): void => {}
|
||||
const finish = (): void => {
|
||||
settled = true
|
||||
unsubscribeWindowVisibility()
|
||||
session.startupGridSettleHandle = null
|
||||
connect()
|
||||
}
|
||||
// Why: local startup commands can launch a TUI before the split-pane grid
|
||||
// has settled; spawn from a briefly stable grid so the TUI paints cleanly.
|
||||
const handle = waitForStableStartupGrid({
|
||||
@@ -232,11 +243,7 @@ export function installPtyResizeGeometry(session: ConnectPanePtySession): void {
|
||||
? session.isStartupGridReadyForConnect
|
||||
: undefined,
|
||||
measure: session.measureStartupGrid,
|
||||
onSettled: () => {
|
||||
settledSynchronously = true
|
||||
session.startupGridSettleHandle = null
|
||||
connect()
|
||||
},
|
||||
onSettled: finish,
|
||||
requestFrame: (callback) => requestAnimationFrame(callback),
|
||||
cancelFrame: (handle) => {
|
||||
if (typeof cancelAnimationFrame === 'function') {
|
||||
@@ -244,8 +251,21 @@ export function installPtyResizeGeometry(session: ConnectPanePtySession): void {
|
||||
}
|
||||
}
|
||||
})
|
||||
if (!settledSynchronously) {
|
||||
session.startupGridSettleHandle = handle
|
||||
if (settled) {
|
||||
return
|
||||
}
|
||||
// Why: a window hidden mid-settle stops producing frames; spawn now, not when it is shown again.
|
||||
unsubscribeWindowVisibility = subscribeWindowParkVisibility(() => {
|
||||
if (!getWindowParkVisible()) {
|
||||
handle.cancel()
|
||||
finish()
|
||||
}
|
||||
})
|
||||
session.startupGridSettleHandle = {
|
||||
cancel: () => {
|
||||
handle.cancel()
|
||||
unsubscribeWindowVisibility()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user