import { rnxExit } from '../../run-rnx' import { inspectWaitReady, resolveMaxMsFlag, waitReadyReason } from './core' import { waitForSootsimIdle } from './settling' import type { WsBridge } from '../../ws-bridge' // blocks until the guest app bundle has finished loading and painted real // inspectable content. the persistent window flag // `__sootsimExternalAppReady` is necessary but not sufficient: shell-owned // canvas can expose a stable launch/loading tree before the app content lands. // the engine owns the polling loop; this handler sends one request, then // reports the typed result. export async function runWaitReadySubcommand(opts: { bridge: WsBridge args: string[] }): Promise { const { bridge, args } = opts // default 20s because cold guest bundles (uniswap, expensify, bluesky) // routinely take 10-15s to evaluate + mount. const timeoutMs = resolveMaxMsFlag(args, 20000) const { ready, elapsedMs, nodes, targets, liveFrameActive, liveFrameChannels, flag, loadingText, externalReady, externalStatus, externalError, suppressedEntryError, errors, bridgeError, } = await inspectWaitReady(bridge, timeoutMs, { ...(bridge.plane !== 'cloud' ? { onProgress(status) { console.error( ` still waiting after ${status.elapsedMs}ms — ${waitReadyReason(status)} (nodes: ${status.nodes}, targets: ${status.targets}, live surfaces: ${status.liveFrameActive ? status.liveFrameChannels : 0}, errors: ${status.errors})`, ) }, } : {}), }) if (ready) { if (bridge.plane === 'cloud') { console.log( ` ready in ${elapsedMs}ms: ${nodes} nodes, ${targets} targets${liveFrameActive ? `, ${liveFrameChannels} live surfaces` : ''}`, ) return } const remainingMs = timeoutMs - elapsedMs const idleBudgetMs = Math.max(100, Math.min(10_000, remainingMs)) const idle = await waitForSootsimIdle({ bridge, maxMs: idleBudgetMs, pollMs: 32, stablePolls: 2, }) if (!idle.settled) { console.error( ` ⚠ wait ready timed out after ${elapsedMs + idle.elapsed}ms — app mounted but did not settle (nodes: ${nodes}, targets: ${targets})`, ) rnxExit(1) } console.log( ` ready in ${elapsedMs + idle.elapsed}ms: ${nodes} nodes, ${targets} targets${liveFrameActive ? `, ${liveFrameChannels} live surfaces` : ''}`, ) return } const reason = waitReadyReason({ externalError, loadingText, externalReady, externalStatus, flag, targets, suppressedEntryError, bridgeError, }) console.error( ` ⚠ wait ready timed out after ${elapsedMs}ms — ${reason} (nodes: ${nodes}, targets: ${targets}, live surfaces: ${liveFrameActive ? liveFrameChannels : 0}, errors: ${errors})`, ) rnxExit(1) }