# Runtime startup and preview fixes — 2026-08-31

## Observed failure

`npm create vite` could scaffold/install successfully, then `npm run dev` printed its script header and returned to the shell without a ready banner. Other runs left a port alive after the owning process had stopped. These were SandboxedJs runtime lifecycle problems; changing the generated Vite project or replacing its commands did not address them.

Earlier startup failures also exposed host prerequisites: synchronous child processes need a worker and shared-memory channel, and Rolldown's browser WASI binding needs cross-origin isolation. The library's worker boot diagnostics and local `sandboxedjs-serve` host make those requirements explicit.

## Runtime changes

| Problem | Correction |
| --- | --- |
| The guest appeared idle while host/WASM compiler promises were still pending | A per-process host-module tracker accounts for async native export and class-method calls. Startup no longer relies on a guessed grace period inferred from unreferenced timers. |
| Native CommonJS re-exports could store a process-owned wrapper in the shared binding | Assignment/property definition unwrap tracked functions before writing them back. A later process does not inherit a disposed tracker. |
| Completed native work could run guest continuations after cancellation and create an orphan server | Disposing the tracker prevents late continuations from resuming the stopped guest. |
| Listening/closed/unreferenced HTTP servers were not consistently reflected in process lifetime | Server reference state participates in liveness checks, and worker port closure is forwarded to the host. |
| Killing a wrapper did not reliably stop owned children and release ports | Cancellation follows owned process relationships; workers release their child handles. The terminal targets its foreground pipeline while preserving unrelated background jobs. |
| Input for a synchronous child went to the parent blocked in `Atomics.wait` | Inherited input and raw-mode changes are routed to the child and restored when it exits. |
| Input-only `readline.createInterface({ input })` incorrectly acquired a default output and raw mode | Input-only interfaces now remain non-terminal unless explicitly configured otherwise. Terminal interfaces also implement Ctrl+C's `SIGINT`/close behavior. |
| `timers/promises` bypassed guest resource accounting | Promise timers now use the tracked timer API. |
| A small service-worker bundle could be inlined into a `data:` URL rejected by registration | The default preview asset URL uses `?no-inline`, preserving a real worker file under Vite builds. |

The changes live in the main library, principally `src/runtime/host-module-tracker.ts`, the local/worker runtime pods, `readline-module.ts`, `virtual-http.ts`, terminal/kernel process handling, and `src/preview/register.ts`. No external Vite/npm package was patched.

## CLI integration

The companion CLI now uses the same container for terminal execution and site preview. Its browser icon toggles a pane with a port selector, reload and close controls. The pane sits on the left on desktop and below the terminal on mobile; its divider supports dragging and keyboard resizing.

The app depends on the local SandboxedJs checkout through a `file:` dependency. Build the library before rebuilding the app. These changes have not been published as a new npm release; the local package still identifies as 0.1.30.

## Verification and remaining scope

The final runtime suite passed **339 tests**, with **13 skipped**. Regression coverage includes delayed host work, late completion after kill, repeated native re-exports, server close/unref lifetime, promise timers, synchronous child cleanup/input and readline behavior. Type checking and library/application builds passed.

Browser checks exercised create-vite's interactive install-and-start flow, a Vite 8 ready banner, HTML and transformed TypeScript responses, the rendered page and its counter, keyboard shortcuts, and desktop/mobile pane resizing and close/reopen. The user also confirmed the current preview renders; the later screenshot showing connection refusal was from older output. The final shared-binding restart correction has a two-process runtime regression test; do not confuse that with a completed end-to-end browser restart matrix.

The CLI's three focused session/cancellation tests passed. Its full existing suite still has four backup-related failures because the adapter only accepts verified 0.1.27/0.1.28 snapshots while the installed runtime is 0.1.30. That compatibility guard was not relaxed as part of this work.

HTTP preview does not imply WebSocket HMR, secure execution of arbitrary untrusted browser code, multi-tenant routing, or parity for every native package on every host. See [Render a site running inside SandboxedJs](server-previews.md) for integration examples, prerequisites, security boundaries and current limitations.
