- call.js: CallManager — RTCPeerConnection lifecycle, offer/answer/ICE/bye state
machine, mute/camera toggles, hangup. STUN (Google + Cloudflare) for NAT.
- Signaling (SDP + ICE) rides the existing E2E message relay as {k:"call"}
payloads — never seen by the server in clear. Media is P2P, encrypted by
WebRTC's mandatory DTLS-SRTP, so audio/video is end-to-end and never hits R2.
- app.js: call buttons (📞/🎥) in DM headers, incoming/outgoing/connected call
overlay with video tiles + PiP, mute/camera/hangup, ring pulse, duration timer.
- Verified signaling state machine (mocked WebRTC): both sides reach 'connected'.
Group calls not supported yet (needs SFU). SW cache v5->v6.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>