mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 00:02:56 +00:00
* Revert "feat(mobile): time relay dial stages so diagnostics say where a slow connect went (#19245)" This reverts commit83b1558ecc. * Revert "perf(mobile): race the direct and relay dials from t=0 on every reconnect (#19308)" This reverts commitceafdcad2f. * Revert "feat(mobile): draw the last known tab strip while a session reconnects (mobile pass) (#19281)" This reverts commit643571def6. * Revert "perf(mobile): open a session with parallel startup RPCs and a pre-warmed terminal engine (#19260)" This reverts commitc37413271e. * Revert "perf(mobile): cut the relay reconnect critical path and admit dead sockets faster (mobile pass) (#19280)" This reverts commite628090ad4. * chore: keep the react-doctor suppression for the startup timers The pattern it covers (a variable number of timers cleared through one cleanup) predates #19260 and is unchanged by the revert; dropping the entry only re-exposed a pre-existing finding to the changed-code gate.
294 lines
14 KiB
TypeScript
294 lines
14 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
readMobileSessionRouteSource,
|
|
readMobileSessionRouteSourceFamily
|
|
} from './mobile-session-route-source-family.test-support'
|
|
|
|
const source = readMobileSessionRouteSourceFamily()
|
|
const startupSource = readMobileSessionRouteSource('./use-mobile-session-startup.ts')
|
|
const tabReconciliationSource = readMobileSessionRouteSource(
|
|
'./use-mobile-session-tab-reconciliation.ts'
|
|
)
|
|
const bulkCloseSource = readMobileSessionRouteSource('./use-mobile-session-bulk-close.ts')
|
|
const presentationSource = readMobileSessionRouteSource('./use-mobile-session-presentation.ts')
|
|
const tabSwitchingSource = readMobileSessionRouteSource('./use-mobile-session-tab-switching.ts')
|
|
const sheetsSource = readMobileSessionRouteSource('./MobileSessionSheets.tsx')
|
|
const reconciliationHookSource = readMobileSessionRouteSource(
|
|
'./use-mobile-session-tabs-reconciliation.ts'
|
|
)
|
|
const terminalInventoryRecoverySource = readMobileSessionRouteSource(
|
|
'./use-mobile-terminal-inventory-recovery.ts'
|
|
)
|
|
const terminalSubscriptionSource = readMobileSessionRouteSource(
|
|
'./use-mobile-session-terminal-subscription.ts'
|
|
)
|
|
const terminalListSource = readMobileSessionRouteSource('./use-mobile-session-terminal-list.ts')
|
|
const tabReconciliationOwnerSource = readMobileSessionRouteSource(
|
|
'./use-mobile-session-tab-reconciliation.ts'
|
|
)
|
|
const autoCreateHookSource = readMobileSessionRouteSource(
|
|
'./use-initial-session-terminal-autocreate.ts'
|
|
)
|
|
const foundationSource = readMobileSessionRouteSource('./use-mobile-session-foundation.ts')
|
|
const terminalRuntimeSource = readMobileSessionRouteSource(
|
|
'./use-mobile-session-terminal-runtime.ts'
|
|
)
|
|
const terminalSubscriptionSourceForIdentity = readMobileSessionRouteSource(
|
|
'./use-mobile-session-terminal-subscription.ts'
|
|
)
|
|
const lifecycleSource = readMobileSessionRouteSource('./use-mobile-session-lifecycle.ts')
|
|
|
|
function sliceBetween(startPattern: string, endPattern: string, targetSource = source): string {
|
|
const start = targetSource.indexOf(startPattern)
|
|
expect(start).toBeGreaterThanOrEqual(0)
|
|
const end = targetSource.indexOf(endPattern, start)
|
|
expect(end).toBeGreaterThan(start)
|
|
return targetSource.slice(start, end)
|
|
}
|
|
|
|
describe('mobile session startup', () => {
|
|
it('auto-creates one terminal for a newly created empty session', () => {
|
|
expect(source).toContain('useWorktreeSessionTabsLoaded(worktreeId)')
|
|
expect(source).toContain(
|
|
'initialSessionAutoCreateRef.current = createInitialSessionAutoCreateState()'
|
|
)
|
|
|
|
const autoCreateCall = sliceBetween(
|
|
'useInitialSessionTerminalAutoCreate({',
|
|
'const connectionVerdict =',
|
|
presentationSource
|
|
)
|
|
expect(autoCreateCall).toContain('stateRef: initialSessionAutoCreateRef')
|
|
expect(autoCreateCall).toContain(
|
|
'consumeCreationRoute: () => router.setParams({ created: undefined })'
|
|
)
|
|
expect(autoCreateCall).toContain("newlyCreatedWorkspace: created === '1'")
|
|
expect(autoCreateCall).toContain('visibleTabCount: visibleTabs.length')
|
|
expect(autoCreateCall).toContain('createTerminal: () => void handleCreateTerminal()')
|
|
|
|
expect(autoCreateHookSource).toContain('shouldAutoCreateInitialSessionTerminal({')
|
|
expect(autoCreateHookSource).toContain('stateRef.current.autoCreatedForWorktree === worktreeId')
|
|
expect(autoCreateHookSource).toContain('stateRef.current.autoCreatedForWorktree = worktreeId')
|
|
expect(autoCreateHookSource).toContain("connState === 'connected'")
|
|
expect(autoCreateHookSource).toContain('(visibleTabCount > 0 || activeHandle !== null)')
|
|
// Why: both callbacks are re-created every render, so the effect must reach them
|
|
// through useEffectEvent rather than deps or a render-time ref write.
|
|
expect(autoCreateHookSource).toContain('useEffectEvent(args.consumeCreationRoute)')
|
|
expect(autoCreateHookSource).toContain('useEffectEvent(args.createTerminal)')
|
|
expect(autoCreateHookSource).toContain('consumeCreationRoute()')
|
|
expect(autoCreateHookSource).toContain('createTerminal()')
|
|
})
|
|
|
|
it('arms the auto-create only until the route has published a tab (#9717)', () => {
|
|
// Emptiness after a populated list is a close, not a cold hydrate.
|
|
expect(source).toContain(
|
|
'initialSessionAutoCreateRef.current.sawSessionTabs ||= nextTabs.length > 0'
|
|
)
|
|
|
|
const autoCreateCall = sliceBetween(
|
|
'useInitialSessionTerminalAutoCreate({',
|
|
'const connectionVerdict =',
|
|
presentationSource
|
|
)
|
|
expect(autoCreateCall).toContain('stateRef: initialSessionAutoCreateRef')
|
|
expect(autoCreateHookSource).toContain('sawSessionTabs: stateRef.current.sawSessionTabs')
|
|
})
|
|
|
|
it('delegates stream ownership while retaining degraded polling and a certified sweep', () => {
|
|
expect(source).toContain('useMobileSessionTabsReconciliation<')
|
|
expect(source).toContain('const applicationRevision = ++appliedSessionTabsRevisionRef.current')
|
|
expect(source).toContain('getApplicationRevision: getSessionTabsApplicationRevision')
|
|
expect(source).not.toContain("client.subscribe(\n 'session.tabs.subscribe'")
|
|
expect(reconciliationHookSource).toContain("client.subscribe(\n 'session.tabs.subscribe'")
|
|
expect(reconciliationHookSource).toContain("if (AppState.currentState !== 'active')")
|
|
expect(reconciliationHookSource).toContain('suspendTerminalInventoryRecovery(true)')
|
|
expect(reconciliationHookSource).toContain('controller.poll()')
|
|
expect(reconciliationHookSource).toContain('tabsRequest !== null')
|
|
expect(reconciliationHookSource).toContain('void refreshTerminalInventory()')
|
|
expect(reconciliationHookSource).toContain("AppState.addEventListener('change'")
|
|
expect(reconciliationHookSource).toContain('const interval = setInterval(')
|
|
expect(reconciliationHookSource).toContain('RECONCILIATION_INTERVAL_MS = 2000')
|
|
expect(terminalInventoryRecoverySource).toContain('CERTIFIED_TERMINAL_SWEEP_MS = 60_000')
|
|
expect(reconciliationHookSource).toContain('controller.setReconciliationActive(false)')
|
|
expect(reconciliationHookSource).toContain('clearInterval(interval)')
|
|
expect(reconciliationHookSource).toContain('appStateSubscription.remove()')
|
|
})
|
|
|
|
it('binds terminal identity to the shared client before subscription effects run', () => {
|
|
expect(foundationSource).toContain('const { client, clientId, state: connState }')
|
|
expect(foundationSource).toContain(' clientId,')
|
|
expect(terminalRuntimeSource).toContain('useRef<string | null>(clientId)')
|
|
expect(terminalRuntimeSource).toContain('deviceTokenRef.current = clientId')
|
|
expect(terminalRuntimeSource).toContain('inputGate.canSend && clientId !== null')
|
|
expect(terminalSubscriptionSourceForIdentity).toContain('if (clientId === null)')
|
|
expect(terminalSubscriptionSourceForIdentity).toContain(
|
|
"client: { id: clientId, type: 'mobile' as const }"
|
|
)
|
|
expect(lifecycleSource).not.toContain('deviceTokenRef.current = host.deviceToken')
|
|
})
|
|
|
|
it('confirms terminal stream teardown with a committed inventory-recovery bridge', () => {
|
|
expect(terminalSubscriptionSource).toContain(
|
|
"if (data.type === 'end' || data.type === 'error')"
|
|
)
|
|
expect(terminalSubscriptionSource).toContain('signalTerminalInventoryRecovery()')
|
|
expect(terminalInventoryRecoverySource).toContain('actionRef.current = recoveryAction')
|
|
expect(terminalInventoryRecoverySource).toContain('pendingSignalScopeRef.current = scopeKey')
|
|
expect(terminalInventoryRecoverySource).toContain(
|
|
'committedScope !== null && committedScope !== scopeKey'
|
|
)
|
|
expect(terminalListSource).toContain('return terminalInventoryRequest.activate()')
|
|
expect(terminalListSource).toContain('if (!isCurrent() || !response.ok)')
|
|
expect(terminalInventoryRecoverySource).toContain(
|
|
'TERMINAL_INVENTORY_CONFIRMATION_DELAY_MS = 750'
|
|
)
|
|
expect(terminalInventoryRecoverySource).toContain(
|
|
'refreshTerminalInventory({ allowEmptyLoaded: true })'
|
|
)
|
|
})
|
|
|
|
it('loads session tabs without waiting for desktop activation', () => {
|
|
const startupEffect = sliceBetween(
|
|
'void (async () => {',
|
|
'return () => {\n disposed = true',
|
|
startupSource
|
|
)
|
|
|
|
expect(startupEffect).toContain("void client\n .sendRequest('worktree.activate'")
|
|
expect(startupEffect).toContain("if (client && created !== '1' && !isFloatingWorkspaceRoute)")
|
|
expect(startupEffect).toContain("if (client && created === '1' && !isFloatingWorkspaceRoute)")
|
|
expect(startupEffect).toContain('notifyClients: false')
|
|
expect(startupEffect).toContain("navigation: 'caller'")
|
|
expect(startupEffect).not.toContain("await client\n .sendRequest('worktree.activate'")
|
|
expect(startupEffect.indexOf("sendRequest('worktree.activate'")).toBeLessThan(
|
|
startupEffect.indexOf('await ensureSessionTabs()')
|
|
)
|
|
expect(startupEffect).toContain('headlessActivationNeedsHostRenderer(response.result)')
|
|
expect(startupEffect).toContain("showToast('Open Orca on the host to wake sleeping agents.'")
|
|
})
|
|
|
|
it('fails runtime capability gates closed before probing a replacement client', () => {
|
|
const capabilityEffect = sliceBetween(
|
|
'const hostQueryReplyInputSupportedRef = useRef(false)',
|
|
'return {\n consumeAcceptedSessionTabs',
|
|
tabReconciliationSource
|
|
)
|
|
const probeStart = capabilityEffect.indexOf('startRuntimeCapabilityProbe(client,')
|
|
|
|
expect(probeStart).toBeGreaterThanOrEqual(0)
|
|
for (const reset of [
|
|
'setBrowserScreencastSupported(null)',
|
|
'setAgentSessionHistorySupported(null)',
|
|
'setQuickCommandsSupported(null)',
|
|
'setShowQuickCommands(false)',
|
|
'hostQueryReplyInputSupportedRef.current = false'
|
|
]) {
|
|
const resetIndex = capabilityEffect.lastIndexOf(reset)
|
|
expect(resetIndex).toBeGreaterThanOrEqual(0)
|
|
expect(resetIndex).toBeLessThan(probeStart)
|
|
}
|
|
})
|
|
|
|
it('activates an already-selected pending terminal tab after hydration', () => {
|
|
expect(source).toContain(
|
|
'const pendingTerminalActivationAttemptRef = useRef<string | null>(null)'
|
|
)
|
|
expect(source).toContain('pendingTerminalActivationAttemptRef.current = null')
|
|
|
|
const pendingActivationEffect = sliceBetween(
|
|
"if (!client || connState !== 'connected' || !activePendingTerminalTab) {",
|
|
'return {\n bulkCloseActions',
|
|
bulkCloseSource
|
|
)
|
|
expect(pendingActivationEffect).toContain(
|
|
'pendingTerminalActivationAttemptRef.current === activationKey'
|
|
)
|
|
expect(pendingActivationEffect).toContain('activateMobileSessionTab(client,')
|
|
expect(pendingActivationEffect).toContain('tabId: activePendingTerminalTab.id')
|
|
expect(pendingActivationEffect).toContain('leafId: activePendingTerminalTab.leafId')
|
|
expect(pendingActivationEffect).toContain('notifyClients: false')
|
|
expect(pendingActivationEffect).toContain("navigation: 'caller'")
|
|
expect(pendingActivationEffect).toContain(
|
|
'applySessionTabs((response as RpcSuccess).result as SessionTabsResult)'
|
|
)
|
|
expect(pendingActivationEffect).toContain('scheduleDelayedAction(() => void fetchSessionTabs()')
|
|
})
|
|
|
|
it('keeps ready terminal taps local while publishing caller selection', () => {
|
|
const readyTerminalSwitch = sliceBetween(
|
|
'const switchTab = useCallback(',
|
|
'const switchSessionTab = useCallback(',
|
|
tabSwitchingSource
|
|
)
|
|
|
|
expect(readyTerminalSwitch).not.toContain('focusMobileTerminal(client, handle)')
|
|
expect(readyTerminalSwitch).toContain('activateMobileSessionTab(client,')
|
|
expect(readyTerminalSwitch).toContain('notifyClients: false')
|
|
expect(readyTerminalSwitch).toContain("navigation: 'caller'")
|
|
})
|
|
|
|
it('keeps background and pending session-tab activation local to the phone', () => {
|
|
const activationRequests = source.split('activateMobileSessionTab(client,').slice(1)
|
|
|
|
expect(activationRequests).toHaveLength(4)
|
|
for (const request of activationRequests) {
|
|
expect(request.slice(0, request.indexOf('})'))).toContain('notifyClients: false')
|
|
expect(request.slice(0, request.indexOf('})'))).toContain("navigation: 'caller'")
|
|
}
|
|
})
|
|
|
|
it('keeps dynamic agent rows above fixed New Tab actions', () => {
|
|
const newTabActions = sliceBetween(
|
|
'title="New Tab"',
|
|
'onClose={() => setShowCreateTabDrawer',
|
|
sheetsSource
|
|
)
|
|
|
|
expect(newTabActions.indexOf('...createTabAgentActions')).toBeLessThan(
|
|
newTabActions.indexOf("label: 'Terminal'")
|
|
)
|
|
expect(newTabActions.indexOf("label: 'Terminal'")).toBeLessThan(
|
|
newTabActions.indexOf("label: 'Browser'")
|
|
)
|
|
expect(newTabActions.indexOf("label: 'Browser'")).toBeLessThan(
|
|
newTabActions.indexOf("label: 'Markdown Note'")
|
|
)
|
|
})
|
|
|
|
it('wires pending-handle recovery through its bounded context (STA-4256)', () => {
|
|
const applySessionTabs = sliceBetween(
|
|
'const applySessionTabs = useCallback(',
|
|
'const consumeAcceptedSessionTabs = useCallback('
|
|
)
|
|
const recoveryContext = sliceBetween(
|
|
'const pendingTerminalRecoveryContextCache = useMemo(',
|
|
'const sessionTabsFetchReporting',
|
|
tabReconciliationOwnerSource
|
|
)
|
|
|
|
const tabsRefWrite = 'sessionTabsRef.current = nextTabs'
|
|
const tabsStateWrite = 'setSessionTabs((prev)'
|
|
const activeRefWrite = 'activeSessionTabIdRef.current = active?.id ?? null'
|
|
const activeStateWrite = 'setActiveSessionTabId(active?.id ?? null)'
|
|
for (const write of [tabsRefWrite, tabsStateWrite, activeRefWrite, activeStateWrite]) {
|
|
expect(applySessionTabs).toContain(write)
|
|
}
|
|
expect(applySessionTabs.indexOf(tabsRefWrite)).toBeLessThan(
|
|
applySessionTabs.indexOf(tabsStateWrite)
|
|
)
|
|
expect(applySessionTabs.indexOf(activeRefWrite)).toBeLessThan(
|
|
applySessionTabs.indexOf(activeStateWrite)
|
|
)
|
|
expect(recoveryContext).toContain('() => new PendingTerminalHandleRecoveryContextCache()')
|
|
expect(recoveryContext).toContain('sessionTabsRef.current,')
|
|
expect(recoveryContext).toContain('activeSessionTabIdRef.current')
|
|
expect(recoveryContext).toContain(
|
|
'const pendingTerminalRecoveryContextKey = getPendingTerminalRecoveryContextKey()'
|
|
)
|
|
expect(source).toContain('hasRecoveryNeed: hasSessionTabsRecoveryNeed')
|
|
expect(source).toContain('getPendingTerminalRecoveryContextKey,')
|
|
expect(source).toContain('onPendingTerminalRecoveryParked: setParkedPendingTerminalContext')
|
|
expect(source).toContain('retryPendingTerminalRecovery()')
|
|
})
|
|
})
|