Commit Graph

36 Commits

Author SHA1 Message Date
f9c21fddc4 feat(chat): scroll-to-latest button + panic hold-progress ring (v1.10.0+14)
B6 scroll-to-latest: ScrollController on the timeline; a small
  arrow-down FAB fades in once scrolled >400px up and animates back
  to the newest message on tap (overlaid on the list, not the FAB slot)
B5 panic hold-ring: 700ms AnimationController drives a red progress
  ring around the panic icon; haptic + fire on completion, silent
  reset on early release; tap-hint and configured guard preserved

Panic ring verified in-browser (ring fills, alert fires to the room).
Scroll-to-latest verified with a deterministic widget test because the
CanvasKit surface rejects synthetic scroll input in headless Chromium.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 08:32:48 +10:00
f55f8dc698 feat(chat): message grouping, day separators, quick-reaction row (v1.9.0+13)
B1 grouping: same-sender runs within 5 min collapse to one name + one
  avatar (last-of-run), tight padding, tail corner only on last bubble
B2 day separators: Today/Yesterday/d MMM chip above each local day's
  first message, Column wraps the gesture widget (long-press preserved)
B4 quick reactions: six preset emoji + full-picker button atop the
  long-press menu, wired to the existing sendReaction provider

Verified end-to-end in headless Chromium on the production build;
tapping a preset sends a real m.reaction (confirmed server-side).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 08:10:11 +10:00
d2a2a1ab07 feat(ui): mobile UI v2 — contrast, avatars, room-list density, compose FAB, gradient login (v1.8.0+12)
From a 7-agent design workflow (3 lenses judged + synthesised). Shipped the
high-impact/low-risk tier:
- message bubbles: self #1265ED (WCAG AA pass), incoming #1C2452 + hairline
- deterministic per-user avatar colours (8-hue brand palette)
- room tile: brighter unread preview, compact density, aligned timestamp
- FAB -> New message (thumb zone), explore -> AppBar
- login + welcome brand gradient, card outlines
- input maxLines cap, bubble max-width viewport fraction, AppBar hairline

All verified on production build in headless Chromium at mobile size.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 07:49:53 +10:00
a36df1c961 feat: encrypted-history prompt, incoming-call alerts, message edit, panic button (v1.7.0+11)
P1 key_restore_prompt: offer recovery-key restore right after login
P2 incoming calls: mount overlay in app.dart + detect MSC3401 call.member
   (Element X signalling) — calls were previously invisible
P3 message edit: editMessage/EditMessage + Edit dialog (reactions/delete
   were already wired)
P5 panic button: new feature/panic module — hold-to-send alert text +
   m.location to a configured room; alert sent before geolocation so a
   denied permission never blocks it

All verified end-to-end in headless Chromium against production build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 07:30:57 +10:00
923c0ad878 fix: Jitsi meeting join rendered blank page — call JitsiMeetExternalAPI with 'new'
- Bind JitsiMeetExternalAPI as extension type external constructor;
  the old external function binding invoked the ES6 class without 'new'
  and threw at runtime (silently, in release builds)
- joinMeeting: wait up to 5s for platform-view container div (post-frame
  race), build options via setProperty (jsify cannot carry DOM nodes),
  return success bool, log failures to browser console
- jitsi_screen: show 'Could not start the meeting' UI instead of blank
- v1.6.2+10, deployed to app2.m8chat.au

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 07:05:32 +10:00
3568b17f0f feat: brand retheme to m8chat.au blues, fix black logo, v1.6.0+8
- m8logo.svg: inline fills (flutter_svg ignores <style> blocks; logo rendered black)
- theme.dart v2.0.0: purple -> site palette (#1265ED / #3B8BFF / navy darks)
- app_config.dart: appVersion 1.3.0 -> 1.6.0 (profile screen showed stale version)
- manifest.json: theme/background colours to match
- Remove em-dashes from user-visible UI strings (global style rule)
- chat_screen.dart: fix use_build_context_synchronously in _leaveRoom
- Deployed to app2.m8chat.au after full functionality audit (all green)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 05:16:11 +10:00
3865391829 merge: reconcile Gitea claude-notes commits (10-11 Apr) with local Phase 3+ code history
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 04:54:27 +10:00
0672f6b28b fix: backport SPA routing block added directly on production (2026-05-01) to web/.htaccess
Production app2.m8chat.au/.htaccess gained Flutter HTML5 routing rules
edited live on brisbane01; local repo never received them. Full-tree
md5 comparison (45 files) shows this was the only drift.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 04:53:53 +10:00
1cffe09faf feat: web push notifications, image fullscreen, public room browser
- Web Push: push_sw.js service worker + VAPID subscription registered
  on login; Matrix pusher registered pointing to Sygnal on
  https://matrix.m8chat.au/_matrix/push/v1/notify
- Chat: tap image -> fullscreen InteractiveViewer (pinch/zoom)
- Rooms: public room browser via Matrix room directory (FAB + empty state)

Build fixes applied to web_push_service.dart:
  - dart:js_interop_unsafe import added for getProperty on JSObject
  - client.setPusher -> client.postPusher (matrix 0.33.0 API)
  - keyBytes.toJS removed (JSUint8Array already a JS type)
  - register() script URL coerced with .toJS (String -> JSAny)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 06:48:48 +10:00
542a5e5221 feat: image fullscreen viewer, public room browser
- Chat: tap any image to open fullscreen InteractiveViewer (pinch/zoom)
- Rooms: public room browser (Find rooms FAB + empty state button)
  searches Matrix room directory via queryPublicRooms, join from results
- Rooms: empty state now shows 'Find rooms' button to onboard new users

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 06:23:27 +10:00
c1b5b31d48 feat: room search, room options sheet, .htaccess for E2EE
- Rooms screen: inline search filter with toggle — replaces the no-op button
- Chat screen: room options sheet — member list, invite, leave room
- rooms_repository: remove debug prints
- web/.htaccess: COOP+COEP headers for SharedArrayBuffer / LiveKit E2EE worker
  (was on production but missing from source; adds it to build output automatically)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 05:49:10 +10:00
7e16b825c5 chore: bump version to 1.3.0+4 for Phase 3 deploy
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 05:32:49 +10:00
b95471b0b4 feat: Phase 3 — E2EE calls, Jitsi conferencing, help tab, web security model
- 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>
2026-04-27 05:28:14 +10:00
255e7a29f6 claude-notes: update remaining-work.md (2026-04-11 03:55) 2026-04-11 03:55:01 +10:00
93442790f3 claude-notes: update current-state.md (2026-04-11 03:54) 2026-04-11 03:54:26 +10:00
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