Renderer runtime adapter implementation patterns¶
1. The runtime/ facade module¶
apps/web/src/runtime is the renderer's single home for runtime adapter code — the only renderer module that wraps mail transport. Its layout:
runtime/types.ts adapter contract and request/response types
runtime/adapter.ts active adapter selection + test override seam
runtime/httpAdapter.ts HTTP-backed adapter (in-process / loopback transport)
runtime/nearEnd.ts thin shim over the wasm near-end engine (open/reconnect/resume; MUST NOT retry — the engine owns reconnects)
runtime/views.ts runtime view/read facade
runtime/runtimeStream.ts low-level link / frame-stream facade
runtime/linkClient.ts shared renderer link demux over one frame stream
runtime/mutations.ts runtime mutation facade
runtime/resources.ts runtime resource (body/attachment/logo) facade
runtime/mailListSelfMaintained.ts self-maintained mail-list view glue
runtime/useRuntimeObjectView.ts object-view hook
runtime/releaseChannel.ts release-channel metadata
runtime/fakeAdapter.ts authority-server-free test adapter (with fakeAdapterSupport.ts, fakeAdapterQueues.ts)
runtime/replica/ client-layer replica node host glue (replication client-link L2/L3): the wasm store worker (storeWorker.ts), its main-thread port (workerStorePort.ts), the worker/in-process resolver (storePortResolver.ts), and the in-process handle (handle.ts)
runtime/wasm/ committed wasm-bindgen replica bindings (posthaste_client_node_wasm*)
The main-thread reactive live-store (apps/web/src/live-store/store.ts) is a sibling to runtime/ — the dumb main-thread mirror of the worker-owned replica (§6, §7).
Components and hooks call the intent facades (runtime/views, runtime/linkClient, runtime/mutations, runtime/resources) and the live-store hooks — not api/client mail functions — for any path a runtime operation exists for. (The former runtime/subscriptions.ts facade was removed; renderer push state is the link-scoped RuntimeFrame stream via runtime/linkClient.)
2. Transport containment¶
The HTTP adapter is the only place transport lives; components never build URLs or attach auth.
- Runtime resources are requested by descriptor and fetched as bytes through the adapter. Components do not build attachment/logo URLs and do not attach bearer headers.
- Runtime link streams: components call
runtimeLinkClientto open views, submit named mutations, and subscribe to frames, while/runtime/sessions/{session_id}/streamURL construction,sourceIdscoping hints, reconnect transport, and stream auth headers stay inruntime/httpAdapter.ts. - Loopback connection details and tokens stay in the connection layer and the HTTP adapter. Desktop bootstrap injects runtime mode metadata for adapter selection without exposing tokens to components.
3. Adapter selection¶
runtime/adapter.ts selects the adapter from the injected runtime mode at bootstrap:
- the HTTP adapter (
httpRuntimeAdapter) is the default, talking to the embedded authority server over in-process / loopback transport; - the replica adapter (
createReplicaAdapter, behindVITE_RUNTIME_REPLICA, default off) backs the renderer with the client-layer replica node (replication client-link L3) — opaque to components; - an unsupported injected mode resolves to an adapter that fails closed (rejects every operation) rather than silently falling back to loopback.
A test seam (setRuntimeAdapterForTesting) overrides the selection.
4. The fake adapter test pattern¶
Renderer tests use createFakeRuntimeAdapter() + setRuntimeAdapterForTesting() (fakeAdapter.ts, fakeAdapterSupport.ts, fakeAdapterQueues.ts): no authority-server start, no injected loopback globals. Tests queue fake results when they assert returned runtime state; the fake records calls so tests assert user intent without coupling to URLs.
5. Compatibility wrappers¶
A few legacy call sites reach runtime state through thin, transport-neutral shims that delegate straight to the adapter, e.g.:
These wrappers carry no URL building, auth headers, or endpoint-specific branching — that stays in runtime/httpAdapter.ts.
6. The wasm replica in a Web Worker¶
When the runtime is a client-layer replica, the wasm EntityStoreHandle (kernel + projector + near-end, replication client-link L3) runs off the UI thread in a Web Worker, not inline on the main thread:
runtime/replica/storeWorker.tshosts the wasm handle in the worker and servesStorePortcalls overpostMessage(a one-time{type:'ready'}handshake on load). The CPU-bound work — ingest, fold, view projection, settlement, dirty/retired draining — happens here.runtime/replica/workerStorePort.ts(WorkerStorePort implements StorePort,createWorkerStorePort) is the main-thread port; it spawns the worker as a separate Vite chunk so the ~700 KB wasm blob loads off-thread.runtime/replica/storePortResolver.tsprobes worker viability via thereadyhandshake and falls back to the in-process handle (runtime/replica/handle.ts) when a webview cannot run the module worker.
React, React Query, mutation translation, and the durable pending set / undo history stay on the main thread; only the store computation crosses into the worker.
7. The main-thread reactive live-store¶
apps/web/src/live-store/store.ts is the dumb main-thread mirror of the worker-owned replica (the wasm store remains the sole source of domain truth; the live-store holds no fold/projection logic). It is a useSyncExternalStore-backed store exposing three read slices through hooks:
useLiveView— the latest view projection for aviewKey;useMailboxCounts— per-account/mailbox{ unread, total };useConnectionHealth—'healthy' | 'degraded' | 'recovering'(a placeholder the M44 health FSM will drive; see §8).
Purity is grep-gated: the live-store imports nothing from React Query or the adapter (types only). Counts are fully migrated onto it; view projections dual-write during migration (M49 residual).
8. Resilience (partially shipped)¶
The renderer's resilience model is owned by the wasm near-end engine, not the TS shims (which never retry — runtime/nearEnd.ts delegates reconnect/resume to the engine). Landed vs pending, per RFC-L2-client-resilience:
- M40 (landed) — link re-prepare on a stale-link 4xx: on a permanent stream error the engine re-prepares and resumes without a page reload (
nearEnd.tsonResetsurfaces a gap the adapter re-seeds from; regression-tested inapps/web/test/harness/scenarios/streamSeverReopen.test.ts). - M46 (landed) — the reactive live-store (§7).
- The worker watchdog ships in
workerStorePort.ts: a per-call liveness deadline (WORKER_CALL_TIMEOUT_MS) terminates and respawns a wedged worker and replays the request, bounded byWORKER_CALL_MAX_RESTARTS. - M41–M45 (pending) — enqueue-failure paths (M41), the full worker re-seed protocol (M42), the subscription-readiness handshake (M43), the connection-health FSM that drives
useConnectionHealth(M44), and the degraded-state indicator (M45) are not yet built.
9. Assertions¶
| ID | Sev. | Assertion |
|---|---|---|
| runtime-module-owns-http-bridge | MUST | Mail HTTP transport lives behind apps/web/src/runtime/httpAdapter.ts. |
| migrated-components-use-runtime-adapter | MUST | Components and hooks use runtime intent facades for mail state, not api/client mail functions, where a runtime operation exists. |
| fake-adapter-no-authority-server | MUST | Renderer tests run with createFakeRuntimeAdapter() and no authority-server startup. |
| compatibility-wrappers-thin | SHOULD | Legacy client wrappers delegate directly to the runtime adapter without transport logic. |
| resource-descriptors-not-urls | MUST | Components request runtime resources by descriptor and do not build HTTP attachment/logo URLs. |
| resource-auth-contained | MUST | Bearer headers for resource byte fetches stay inside the runtime HTTP adapter. |
| event-transport-contained | MUST | SSE URLs, reconnect transport, and runtime link stream auth headers stay inside the runtime HTTP adapter. |
| one-renderer-link-client | MUST | Hooks share runtimeLinkClient instead of opening independent renderer push streams. |
| named-mutations-use-link-client | MUST | Renderer mutations submit named runtime mutations through runtimeLinkClient and receive mutationSettlement frames. |
| desktop-mode-metadata | SHOULD | Desktop bootstrap exposes runtime mode metadata for adapter selection without exposing tokens to components. |
| unsupported-adapter-fails-closed | SHOULD | Unsupported injected runtime modes reject through the adapter instead of silently falling back to loopback. |
| replica-store-off-thread | SHOULD | The wasm replica store runs in a Web Worker (storeWorker.ts) with an in-process fallback, not inline on the UI thread. |
| live-store-holds-no-logic | MUST | The main-thread live-store mirrors worker-owned state via useSyncExternalStore and holds no fold/projection logic; the wasm store is the sole source of domain truth. |
| near-end-shims-never-retry | MUST | Renderer transport shims (nearEnd.ts) delegate reconnect/resume/retry to the wasm near-end engine and do not implement their own retry loop. |