Commit Graph

21 Commits

Author SHA1 Message Date
a01c527157 claude-notes: update decisions.md (2026-04-11 03:53) 2026-04-11 03:53:46 +10:00
72020bb9b1 docs: remaining work 2026-04-10 2026-04-10 18:39:03 +10:00
ae61c06252 docs: current state 2026-04-10 2026-04-10 18:38:42 +10:00
c6f997b42d docs: session decisions 2026-04-10 2026-04-10 18:38:03 +10:00
7e79cca552 feat: init claude-notes directory 2026-04-10 18:37:16 +10:00
a241d0a7ab fix: replace incorrect logo.png with m8logo.svg
Removed logo.png (wrong logo). Added m8logo.svg from BrandingKit as
the correct M8Chat logo. Login screen now renders SVG via flutter_svg.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 16:22:12 +10:00
5a6d6cadaf fix: return focus to message input after sending
Added FocusNode to TextField, requestFocus() after send completes.
Also autofocus=true so cursor is in the input when entering a room.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 16:18:39 +10:00
c56c2d59fd fix: hide call signaling and state events from chat timeline
Filter out org.matrix.msc3401.call.member, org.matrix.msc4075.rtc.notification,
m.call.*, m.room.member, and other state events from the chat timeline.
Users only see actual messages, not protocol noise.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 16:12:19 +10:00
327cd36b6d fix: add MatrixSdkDatabase — timeline was empty without event storage
Without a databaseBuilder, the Matrix SDK has no event storage. The
initial sync populates room metadata but timeline events are not
retained in any queryable form. room.getTimeline() returns empty.

Added MatrixSdkDatabase('m8chat_matrix') which uses IndexedDB on web
and SQLite on mobile via Hive. Events are now persisted and timeline
loads room history correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 16:04:09 +10:00
fd7b64a89b fix: disable Olm on web — /keys/upload 400 crashed client init
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>
2026-04-03 15:56:46 +10:00
41accdba81 fix: remote video stuck on 'waiting' — widget never rebuilt after connect
_RemoteVideoView watched liveKitServiceProvider which returns the same
keepAlive instance. When activeRoom was set after connecting, Riverpod
didn't detect the field change so the widget stayed on the null path.

Fix: also watch callControllerProvider — when state transitions from
Connecting to Active, the widget rebuilds and finds activeRoom non-null,
then ListenableBuilder takes over for participant/track changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 07:09:17 +10:00
fa30c1f27c feat: auto-end call when remote party leaves
Listens for ParticipantDisconnectedEvent on the LiveKit Room. When no
remote participants remain (1:1 call ended by other side), waits 5
seconds then auto-hangs up. The delay allows for brief reconnections.

Also ensures our app properly cleans up when Element X user hangs up
on their end — our side won't sit connected to an empty LiveKit room.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 07:03:20 +10:00
e1d9ec788c fix: disconnect LiveKit before clearing state event
Element X stays connected because it sees the state event clear while
we're still in the LiveKit room. Reversed the order: disconnect from
LiveKit first (remote party sees us leave), then clear the state event.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 06:49:30 +10:00
d60edcf45f fix: remote video not showing — Room ChangeNotifier not listened to
_RemoteVideoView and _LocalVideoPip watched the Riverpod provider but
not the LiveKit Room's ChangeNotifier. When a remote participant joined
or published a track, notifyListeners() fired but no widget rebuilt.

Fix: wrap both views in ListenableBuilder(listenable: room) so they
rebuild on every Room event (participant connect, track subscribe, etc).

Added _AudioOnlyPlaceholder for when remote has audio but no video.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 06:47:53 +10:00
962c833142 feat: send MSC3401 call.member state events for Element X interop
When starting a call, the app now sends org.matrix.msc3401.call.member
state event to the Matrix room with LiveKit foci info. This is what
Element X listens for to show incoming call notifications.

On disconnect, the state event is cleared (empty memberships) so
Element X knows the call has ended.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 06:41:18 +10:00
41c16820e5 fix: calls — JWT field name 'jwt' not 'token', CORS dedup on server
- lk-jwt-service returns {jwt: '...', url: '...'} not {token: '...'}
  Now reads both 'jwt' and 'token' fields for compatibility
- Server nginx: added proxy_hide_header to strip upstream CORS headers
  preventing duplicate Access-Control-Allow-Origin (browser rejects)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 06:32:55 +10:00
1f58c9e21d fix: calls — correct MSC4143 JWT flow + message order
Calls:
- JWT fetch now uses correct MSC4143 flow: get OpenID token from
  Synapse, then POST to /_matrix/livekit/jwt/sfu/get (was using
  GET with Bearer token to wrong path — returned 301→404)
- Error messages now visible for 3 seconds before popping screen
  (was flashing away instantly — user couldn't see failure reason)
- Voice vs video calls differentiated via ?video=0/1 query param
- Debug logging added to JWT flow for troubleshooting

Messages:
- Chat timeline now shows newest at bottom (standard behaviour).
  Was reversed twice: SDK returns newest-first, code reversed to
  oldest-first, then ListView(reverse:true) put oldest at bottom.
  Removed the extra .reversed — newest-first + reverse:true = correct.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 06:21:08 +10:00
b941cdfe4b refactor: /simplify — 22 fixes from 3-agent code review
Critical:
- Fix MXC URI resolution: all avatars/images now resolve mxc:// to HTTP
- Sync persistence: only write changed rooms, batch message upserts
- lastActivityAt uses room.lastEvent.originServerTs, not creation time

High:
- Shared MatrixAvatar widget replaces 6 duplicate implementations
- CallScreen decodes roomId before LiveKit JWT fetch
- Decline button actually dismisses incoming call overlay
- EventTypes constants replace raw string literals
- LiveKitService uses lazy auth reads, onDispose disconnects

Medium:
- CallController is keepAlive with timer/room cleanup
- authRepository is keepAlive (used from keepAlive notifier)
- StreamController not closed in stopListening (crash fix)
- Index on messages.roomId for query performance
- 400ms debounce on user search
- Static DateFormat in MessageBubble
- Hardcoded strings replaced with AppConfig refs
- Duplicate isDirectMessage field removed from RoomModel
- E2EE profile claim corrected to Phase 3

Shared utilities:
- lib/shared/widgets/matrix_avatar.dart
- lib/shared/utils/mxc_url.dart
- lib/shared/utils/room_preview.dart
- lib/shared/utils/matrix_id.dart

rawJson column removed (unused, caused main-thread jsonEncode)
Schema migrated to v2 with roomId index.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 13:19:22 +10:00
96550c3411 fix: session restore timeout + block .git access
- Add 12s timeout to restoreSession() — client.init() could hang
  indefinitely if token expired or server unreachable, leaving user
  on spinner forever. Now clears stale credentials and shows login.
- Sync persistence startup wrapped in try-catch — non-fatal
- .htaccess blocks .git directory (was returning 200 to scanners)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 11:55:27 +10:00
f12a7ac1fd feat: Phase 2 complete — calls, media, spaces, persistence, chat improvements
- LiveKit/MatrixRTC voice+video calls with full call screen UI
- Incoming call overlay (accept/decline)
- Media upload/download — file picker, image rendering, file download
- Spaces navigation — space list + expandable child rooms
- Drift persistence — rooms + messages written on every sync
- Sync persistence auto-starts on login and session restore
- Chat: typing indicators, long-press menu, reply, emoji reactions
- User search dialog + start DM from rooms screen
- Android: INTERNET + CAMERA + RECORD_AUDIO permissions in main manifest
- Emoji picker for reactions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 06:48:03 +10:00
8f13c725a4 feat: Phase 1 complete — Matrix login, rooms, chat, profile
- Direct m.login.password auth against matrix.m8chat.au
- Room list with unread badges, last message, timestamps
- Chat timeline (text, images, files, replies, reactions)
- Profile screen with expandable Notifications and Security sections
- Olm E2EE initialisation (web WASM bootstrap)
- Global error handler preventing Matrix SDK crashes
- GoRouter with refreshListenable (no recreation on auth change)
- Feature-first clean architecture: Riverpod + GoRouter + Drift
- Deployed to https://app2.m8chat.au

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 06:26:57 +10:00