README.md raw

smesh frontend architecture

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://...

Contexts

ContextSourceBuilt artifactPurpose
Page main threadweb/static/wasm-host.mjs (loader), web/static/index.htmlserved as-isSupervisor. Constructs all workers, holds DOM, brokers signer extension and DOM bridge calls.
Appweb/wasm/app/app.wasm + wasm-app-worker-host.mjsThe smesh app: routing, rendering, state. Runs in a Web Worker with a thin DOM-proxy bridge to the supervisor.
HTTP-proxy workerweb/wasm/http-proxy/http-proxy.wasm + http-proxy-wasm-host.mjsReceives FETCH requests, hits the relay's /proxy/<base64url> endpoint, returns Blob URLs. Concurrency-limited fetch queue with LRU.
Relay-proxy workerweb/wasm/relay-proxy/relay-proxy.wasm + relay-proxy-wasm-host.mjsWS 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 Workerweb/legacy/sw/ (production), web/wasm/sw/ (in-progress)/$sw/$entry.mjsHandles 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.

Message routing (page <-> SW <-> relay-proxy worker)

App-side outbound dispatch lives in routeMsg (web/wasm/app/main.mx). It examines the JSON-array message type and dispatches:

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

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).

PackageUsed byFunction set
domappDOM ops, localStorage proxy, websocket proxy, signer proxy. Most calls SAB-sync via the supervisor.
httpproxyapp (consumer) + http-proxy worker (internal)One package, two contexts; DCE strips unused imports per binary.
relayproxyapp (consumer) + relay-proxy worker (internal)Same pattern.
idbrelay-proxy worker, SWEvents + DM store. Each context has its own connection to the shared smesh IDB database.
wsrelay-proxy worker (and any context needing raw WS)Native browser WebSocket.
signerappWraps the smesh signer extension protocol.

State ownership

Single-writer-per-store discipline:

StoreOwnerReason
IDB eventsStore worker (writes via relay-proxy), various readersWorker is in the WS event flow, primary write path. Idempotent by event ID.
IDB dmsStore worker (writes via MLS/relay-proxy)Same pattern.
IDB kv_profiles, kv_settings, kv_cacheStore worker (writes via supervisor)App reads/writes routed through supervisor to store worker. Single IDB authority.
Cache APIService WorkerOwns fetch interception.
WS connectionsRelay-proxy workerPool of relay.Conn instances; flushSubs re-emits REQs on reconnect via ScheduleReconnect callback.
Signing keysBrowser extension (canonical)Per Q7 Option B: signer extension never shares keys; page mediates between worker and extension.

Worker boot order

Each worker boots WASM async; the supervisor waits for explicit READY before forwarding requests.

  1. wasm-host.mjs calls startHttpProxyWorker(), startRelayProxyWorker(), startWorker() (app).
  2. Each worker shim listens for {type: 'init', mode: 'root', wasmUrl: ...}, fetches the wasm, instantiates.
  3. Each worker's main() finishes setup and calls WorkerPost("[\"READY\"]") (or equivalent).
  4. Supervisor sees READY, sets window.__hpReady / window.__rpReady flags (used by tests; benign in production).
  5. Pending messages queued on the supervisor while workers booted are drained.

Build

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/.

Tests

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

/proxy/ caching (live)

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/.

Deferred items