A realtime app written in Mere, built outside the compiler monorepo to dogfood the package system and surface real pain. See PAIN.md for the running friction log — that log is the point of this project as much as the app is.
main.mere is the collaborative note (milestone 4c). Earlier milestones
are preserved in git history.
- Single-instance WebSocket chat — JS-side auto-relay hub + server
announcements via
ws_broadcast. (m1tag.) - Multi-instance SSE + Redis pub/sub fan-out — a POST to any
instance reaches SSE clients on every instance. Switched from WS to
SSE+POST because cross-instance fan-out needs the server to see each
message (PAIN P5/P6). (in the
Milestone 2commit.) - Collaborative note (LWW) — a shared document with Redis as the
authoritative store; edits POST the full text and fan out live. WS
inbound was avoided via POST+Redis (PAIN P5). (
Milestone 3commit.) - CRDT collaborative note. Edits are character-level RGA operations
(crdt.mere) on a Redis op log, so concurrent edits
converge with nothing lost — fixing M3's last-write-wins (PAIN P8).
- 4b: server maps index intents to ops (convergent, but positions
can drift under simultaneous typing). (
Milestone 4bcommit.) - 4c ← you are here: the RGA runs client-side and emits ops with
real element ids, so editing is intent-preserving; the server is
a thin op relay. Full Unicode code points client-side. (
GET /api/noteserver-render is ASCII-only — Mere strings are byte-oriented, PAIN P10.)
- 4b: server maps index intents to ops (convergent, but positions
can drift under simultaneous typing). (
Declared in mere.toml and installed with mere install
(v0.2 package system) — .mere_modules/ is generated, not committed:
http—contrib/httpfrommerelang/mere(router / sse / escape / …)db—contrib/dbfrommerelang/mere(redis / …)
mere install also pulls the transitive log package (contrib/http's
access_log imports ../log/log.mere) and writes mere.lock
with the resolved commit + a content hash. Packages land under their bare
source-dir names so .mere_modules/ mirrors contrib/ and cross-package
relative imports resolve — see PAIN P2.
Only a mere binary is needed — mere install fetches both the contrib
dependencies (.mere_modules/) and the Node runtime host (.mere_host/,
from the [host] entry), so no compiler source tree is required at
runtime. mere serve runs the compiled wasm on that vendored host.
mere install # deps + host from mere.toml
docker run -d --name mere-notes-redis -p 15604:6379 redis:7 # pub/sub
mere -w main.mere > notes.wat
wat2wasm --enable-tail-call notes.wat -o notes.wasm
mere serve notes.wasm # → :8080
# two instances, to see cross-instance convergence (two shells)
PORT=8080 mere serve notes.wasm
PORT=8081 mere serve notes.wasm
# open http://localhost:8080/ and http://localhost:8081/ , type in one → the other converges(mere serve just runs node .mere_host/run_http_server.js notes.wasm.)
Realtime needs the persistent node host (run_http_server.js + the
sse_redis_bridge.js subscribe bridge), not a stateless Cloudflare
Worker — long-lived streaming connections don't fit the request-scoped
Worker model. Those host files are now vendored into .mere_host/ by
mere install (PAIN P7), so the app carries its own runtime.