concept-collection / commonroom
commonroom / CLAUDE.md
11.4 KBPreviewCodeBlameHistoryRaw

CLAUDE.md#

Tips for future agents working in this repo. It combines the p2p techniques of the sibling projects commonview (auto-connecting mesh) and commoncall (WebRTC media, settings, screen share) — read those first; this file only covers what is different here.

Architecture#

src/p2p/
  identity.ts  schnorr keypair; pubkey hex = peer ID     — ported from commoncall
  nostr.ts     minimal relay client + topic scheme        — ported (roomTopic takes a room ID)
  peer.ts      WebRTC wrapper: media + control channel    — ported (replaceTrack generalized to audio|video)
  settings.ts  shared ROOM settings, quality presets      — default quality is 'medium', not 'auto'
  turn.ts      optional TURN: build-time endpoint, credential fetch, sanitizers
  network.ts   the heart: rooms, presence, mesh, media, settings sync, relay sharing
src/transcribe/
  capture.ts   AudioWorklet → 40 ms linear16 frames + RMS; SpeechGate (the VAD)
  deepgram.ts  one streaming WebSocket per speaker; subprotocol auth, KeepAlive
  store.ts     the transcript: paragraph merging, per-room localStorage persistence
  transcriber.ts  one capture+socket pipeline per participant, reconciled per snapshot
  *.test.mjs   node checks for the gate and the store (see Testing)
src/App.tsx    landing form (light) + in-room view (dark), video grid with
               click-to-spotlight (gallery ↔ one big tile + filmstrip; Esc or
               click again to return), control bar, chat and transcript side
               panels (one at a time; docked wide, overlay ≤700px)
worker/        Cloudflare Worker that mints TURN credentials — deployed
               separately (wrangler), NOT part of `npm run build`

Key design decisions#

Testing#

npm run dev, then open the room in two browsers (identity is per-browser-profile via localStorage, so two tabs in one profile are the SAME peer — use a private window or second browser). npm run build type-checks (tsc -b) and bundles. Let the user test multi-party media in real browsers; don't try to automate camera/mic flows.

The Worker CAN be tested without a browser: cd worker && cp .dev.vars.example .dev.vars && npm run dev, then curl it. With the example values, token checks work (goodtoken passes, anything else 401s) and the upstream call 404s, which surfaces as a 502 — enough to cover auth, CORS and method handling. cd worker && npx tsc --noEmit type-checks it; the root tsc -b does not (it only includes src). The sanitizers in turn.ts are pure and testable under node via npx esbuild src/p2p/turn.ts --format=esm --define:import.meta.env='{}'.

The two riskiest pieces of the transcription path have node checks, by the same bundle-then-run recipe (there is no test runner in this repo; run them by hand after touching either file):

npx esbuild src/transcribe/capture.ts --format=esm --outfile=/tmp/capture.mjs
node src/transcribe/gate.test.mjs /tmp/capture.mjs     # VAD: what gets paid for
npx esbuild src/transcribe/store.ts --format=esm --outfile=/tmp/store.mjs
node src/transcribe/store.test.mjs /tmp/store.mjs      # persistence: what must not be lost

What they cannot cover is whether Deepgram accepts the audio at all — that needs a real key and a real browser. The transcript panel's "N s sent" readout is the quickest check that gating works: it should climb while someone talks and sit still while nobody does. Whether a relay is actually USED can only be seen in a real browser (chrome://webrtc-internals, candidate pair type relay).