Five execution contexts on the page side, plus the relay binary and remote nostr relays. Single-concern per context; cross-context comms are postMessage-shaped.
+--------------------------+
| page (app.wasm) |
| - DOM, routing, state |
| - 2 MessagePorts + SW |
+-+--------+-------------+-+
| | |
MessageChannel MessageChannel navigator.serviceWorker
| | .controller.postMessage
| | |
+---------v--+ +--v---------+ +-v--------------+
| HTTP-proxy | | Relay-proxy| | Service Worker |
| worker | | worker | | (legacy / |
| | | | | cache layer) |
| fetch queue| | WS pool | | Cache API |
| /proxy/... | | IDB writes | | fetch intercept|
+-----+------+ +-+--------+-+ +----------------+
| | |
v v v
smesh relay remote IDB (events, dms)
/proxy/X relays
wss://...
| Context | Source | Built artifact | Purpose |
|---|---|---|---|
| Page main thread | web/static/wasm-host.mjs (loader), web/static/index.html | served as-is | Supervisor. Constructs all workers, holds DOM, brokers signer extension and DOM bridge calls. |
| App | web/wasm/app/ | app.wasm + wasm-app-worker-host.mjs | The smesh app: routing, rendering, state. Runs in a Web Worker with a thin DOM-proxy bridge to the supervisor. |
| HTTP-proxy worker | web/wasm/http-proxy/ | http-proxy.wasm + http-proxy-wasm-host.mjs | Receives FETCH requests, hits the relay's /proxy/<base64url> endpoint, returns Blob URLs. Concurrency-limited fetch queue with LRU. |
| Relay-proxy worker | web/wasm/relay-proxy/ | relay-proxy.wasm + relay-proxy-wasm-host.mjs | WS pool to remote nostr relays. Handles REQ/CLOSE/EVENT/PUBLISHTO/PROXY/SETPUBKEY/SETWRITERELAYS/ENCKEY/DMLIST/DM_HISTORY. Owns IDB events store. Reconnect via 5s timer. |
| Service Worker | web/legacy/sw/ (production), web/wasm/sw/ (in-progress) | /$sw/$entry.mjs | Handles install/activate/fetch lifecycle and legacy MLS handlers (dead in current production - kept only because the MLS path is unused, not because anyone is calling them). Will shrink to cache-only when the in-progress wasm SW takes over. |
App-side outbound dispatch lives in routeMsg (web/wasm/app/main.mx). It examines the JSON-array message type and dispatches:
relayproxy.Send: REQ, CLOSE, EVENT, PUBLISHTO, PROXY, SETPUBKEY, SETWRITERELAYS, ENCKEY, CRYPTORESULT.mlsw.Send: MLSINIT, MLSSENDDM, MLSSENDGROUP, MLSRATCHETDM, MLSDMLIST, MLSDM_HISTORY.dom.PostToSW: activate-update only.App-side inbound (onSWMessage) handles both sources uniformly. Both workers register the same callback:
dom.OnSWMessage(onSWMessage)
relayproxy.OnMessage(onSWMessage)
Message type prefixes are unique per source so there is no collision (worker emits EVENT/EOSE/OK, SW emits update-available/reload/SW_LOG/MLS responses).
Bridge packages live in web/common/jsbridge/<name>/ for smesh-specific shims, and upstream in ../moxie/jsruntime/<name>/ for generic Web APIs (per the moxie CLAUDE.md "Browser Target: WASM Only" rule).
| Package | Used by | Function set |
|---|---|---|
dom | app | DOM ops, localStorage proxy, websocket proxy, signer proxy. Most calls SAB-sync via the supervisor. |
httpproxy | app (consumer) + http-proxy worker (internal) | One package, two contexts; DCE strips unused imports per binary. |
relayproxy | app (consumer) + relay-proxy worker (internal) | Same pattern. |
idb | relay-proxy worker, SW | Events + DM store. Each context has its own connection to the shared smesh IDB database. |
ws | relay-proxy worker (and any context needing raw WS) | Native browser WebSocket. |
signer | app | Wraps the smesh signer extension protocol. |
Single-writer-per-store discipline:
| Store | Owner | Reason |
|---|---|---|
IDB events | Store worker (writes via relay-proxy), various readers | Worker is in the WS event flow, primary write path. Idempotent by event ID. |
IDB dms | Store worker (writes via MLS/relay-proxy) | Same pattern. |
IDB kv_profiles, kv_settings, kv_cache | Store worker (writes via supervisor) | App reads/writes routed through supervisor to store worker. Single IDB authority. |
| Cache API | Service Worker | Owns fetch interception. |
| WS connections | Relay-proxy worker | Pool of relay.Conn instances; flushSubs re-emits REQs on reconnect via ScheduleReconnect callback. |
| Signing keys | Browser extension (canonical) | Per Q7 Option B: signer extension never shares keys; page mediates between worker and extension. |
Each worker boots WASM async; the supervisor waits for explicit READY before forwarding requests.
wasm-host.mjs calls startHttpProxyWorker(), startRelayProxyWorker(), startWorker() (app).{type: 'init', mode: 'root', wasmUrl: ...}, fetches the wasm, instantiates.main() finishes setup and calls WorkerPost("[\"READY\"]") (or equivalent).window.__hpReady / window.__rpReady flags (used by tests; benign in production).make build # all targets
make build-relay # native relay binary
make build-app-wasm # app.wasm
make build-http-proxy-wasm
make build-relay-proxy-wasm
make build-sw # legacy moxiejs SW (current production)
make build-ext # signer extension XPI + Chrome ZIP
Each *.wasm artifact has a paired *-wasm-host.mjs shim in web/static/. Bridges that any moxie web app could reasonably want live upstream in ../moxie/jsruntime/; smesh-specific protocol shims live in web/common/jsbridge/.
Selenium + Firefox via the pytest harness in test/conftest.py. The relay fixture boots smesh on 127.0.0.1:23334 with web/static/ as the static root.
Phase 1 + 2 worker tests live in test/test_http_proxy_worker.py. Each phase ships with its own end-to-end test on a fresh build before being declared done; that is the project's "Reporting Completion" rule.
python3 -m pytest test/test_http_proxy_worker.py -v
The legacy SW (web/legacy/sw/) intercepts /proxy/<base64url> requests via sw.RespondWithProxyCache. The cache key is the decoded upstream URL (so two different base64url encodings of the same upstream share one entry), wrapped in /__proxy/<encodeURIComponent(decoded)>. Each cached response carries an X-Smesh-Cached-At header. Cache hits younger than 30 days are returned immediately; older or missing entries fall through to network and are re-cached on success. onActivate runs sw.PruneProxyCache(30days) to delete stale entries (best-effort).
The runtime functions are upstream in ../moxie/jsruntime/sw.mjs. The bridge declarations are in web/common/jsbridge/sw/.
web/legacy/sw/mls.mx. The MLS path is unused in current production, so the legacy handlers stay dead in the SW rather than being migrated. If MLS comes back into scope, the migration follows the same Option B pattern as DM (CRYPTO_REQ to the page for sign/encrypt/decrypt).web/wasm/sw/ exists as a stub; the production SW is still legacy moxiejs in web/legacy/sw/. After the wasm SW migration, the wasm SW host shim needs sw_respond_with_proxy_cache and sw_prune_proxy_cache bridges wired (the moxie-side wasmimports already exist in web/common/jsbridge/sw/sw_wasm.mx).pkg/relay/server/server.mx has a single-threaded epoll loop; mediaproxy.Fetch blocks it during proxy fetches. The refactor needs routeHTTP to return a "pending" sentinel and an event mechanism that integrates Moxie spawn channels into the epoll loop (likely via eventfd). Substantial, separate ticket. Acceptable at personal scale, needs work for production scale.