- LiveKit call E2EE: CallE2EEManager exchanges encryption keys via Matrix
to-device events (m.rtc.encryption_keys) for interop with Element X
- Olm bootstrapped in index.html before Flutter init; main.dart logs result
- Encrypted messages shown with lock icon and informative fallback text
- Profile screen: key restore dialog + security setup (cross-signing/backup)
- Jitsi feature: welcome screen (public, no login), conference tab, full-screen
embed via JitsiMeetExternalAPI, JitsiLink parser for all common link formats
- Help tab: expandable cards for encryption, video calls, account management
- Web security model: no session persistence — device ID only across visits
- Media auth: MSC3916 authenticated endpoint for avatars (Synapse 1.120+)
- Router: welcome route as public landing page; jitsi route as public
- Manifest/index.html: M8Chat branding, dark theme colours
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The web session generates new Olm keys for an existing device ID.
Synapse rejects the upload (400 — keys mismatch) which crashes the
entire client init. Result: no sync, no room history, no calls.
Proper E2EE requires persistent Olm account storage (IndexedDB) so
the same device ID always sends the same keys. Deferred to Phase 3.
Without Olm loaded, the Matrix SDK gracefully skips encryption init.
Unencrypted rooms work fully. E2EE rooms show 'Encrypted message'.
Also unregisters stale service workers that served cached old JS
(caused calls to use the old GET URL instead of POST /sfu/get).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>