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>
126 lines
14 KiB
Markdown
126 lines
14 KiB
Markdown
### 2026-04-10 18:00 — Enable Olm E2EE in web build
|
|
- **Decision:** Re-enabled Olm encryption in index.html with synchronous load + Olm.init() before Flutter boots, graceful fallback if Olm fails
|
|
- **Rationale:** 45 of 88 rooms have Megolm encryption. MatrixSdkDatabase persists Olm keys in IndexedDB resolving /keys/upload 400 errors
|
|
- **Files affected:** web/index.html, lib/main.dart, app_config.dart, auth_repository.dart, pubspec.yaml
|
|
- **Status:** Working — device keys uploaded, messages encrypt/decrypt correctly
|
|
|
|
### 2026-04-10 17:25 — Deploy target is app2.m8chat.au NOT app.m8chat.au
|
|
- **Decision:** app.m8chat.au = PRODUCTION (old FluffyChat). app2.m8chat.au = dev/test for m8chat-app2
|
|
- **Rationale:** User corrected after accidental production deploy. Production restored from backup.
|
|
- **Status:** All future deploys to app2.m8chat.au
|
|
|
|
### 2026-04-10 17:15 — Lazy-load Jitsi external_api.js
|
|
- **Decision:** Moved from index.html to dynamic load in jitsi_web_service.dart
|
|
- **Rationale:** Brave blocks cross-origin scripts at page load
|
|
- **Status:** Done
|
|
|
|
### 2026-04-10 17:10 — Add MessageType.encrypted with lock icon UX
|
|
- **Files:** message_model.dart, chat_repository.dart, message_bubble.dart, room_preview.dart
|
|
- **Status:** Done
|
|
|
|
### 2026-04-10 17:05 — Fix web metadata to M8Chat branding
|
|
- **Files:** web/index.html, web/manifest.json
|
|
- **Status:** Done
|
|
|
|
### 2026-04-11 02:00 — Add Jitsi Conference tab to bottom nav
|
|
- **Decision:** Added 4th tab (videocam icon) to authenticated bottom nav so logged-in users can join Jitsi conferences
|
|
- **Rationale:** Jitsi join was only accessible on the unauthenticated welcome screen. Logged-in users could never reach it.
|
|
- **Files affected:** rooms_screen.dart (v1.3.0), conference_tab.dart (new), jitsi_screen.dart (Back→/rooms)
|
|
- **Status:** Done and deployed
|
|
|
|
### 2026-04-11 03:00 — LiveKit E2EE frame encryption attempt
|
|
- **Decision:** Added CallE2EEManager for m.rtc.encryption_keys to-device key exchange + E2EEOptions on LiveKit Room
|
|
- **Rationale:** Element X encrypts video frames with AES-GCM. Without decryption, video is scrambled.
|
|
- **Files affected:** call_e2ee.dart (new), livekit_service.dart (v1.4.0)
|
|
- **Status:** PARTIALLY WORKING — E2EE worker requires SharedArrayBuffer which needs COOP/COEP headers. Headers added to .htaccess on 2026-04-11. Key exchange protocol (m.rtc.encryption_keys) implemented but interop with Element X NOT YET VERIFIED.
|
|
|
|
### 2026-04-11 04:00 — Web auth: fresh login every session (no session persistence)
|
|
- **Decision:** Removed session restore entirely for web. Every visit requires login. No access token stored.
|
|
- **Rationale:** Security — web app runs on untrusted/shared devices. Can't assume same user. Different from future APK which will persist sessions.
|
|
- **Files affected:** auth_notifier.dart (v2.0.0), secure_storage.dart (v2.0.0), auth_repository.dart (v2.0.0), app_config.dart (v2.0.0)
|
|
- **Status:** Done. Removed StoredCredentials, restoreSession(), storage key constants. Device ID persistence also removed (caused keys/upload 400 because Olm account not persisted).
|
|
|
|
### 2026-04-11 04:30 — SSSS + cross-signing + key backup bootstrap
|
|
- **Decision:** Implemented full security setup dialog using Matrix SDK Bootstrap class
|
|
- **Rationale:** Users need to create/restore SSSS to decrypt old messages. Replaces "Phase 2" placeholder.
|
|
- **Files affected:** security_setup_dialog.dart (new), key_restore_dialog.dart (new), profile_screen.dart (v1.4.0)
|
|
- **Status:** Done. User 'try' successfully ran security setup. Recovery key generated.
|
|
|
|
### 2026-04-11 05:00 — Fix authenticated media (Synapse 1.151.0)
|
|
- **Decision:** Switched from frozen /_matrix/media/v3/download/ to /_matrix/client/v1/media/download/ with access_token query param
|
|
- **Rationale:** Synapse 1.151.0 froze the old media endpoint. All room avatars returned 404.
|
|
- **Files affected:** mxc_url.dart (v2.0.0)
|
|
- **Status:** Done — avatars load correctly now
|
|
|
|
### 2026-04-11 06:00 — Add Help tab + Account management info
|
|
- **Decision:** Added 5th bottom nav tab (Help) with expandable help cards
|
|
- **Rationale:** Users need in-app guidance for encrypted messages, security setup, account management
|
|
- **Files affected:** help_tab.dart (new), rooms_screen.dart (v1.3.0)
|
|
- **Status:** Done. Contains: encrypted messages fix guide, getting started, conference guide, video call issues, account management (links to m8chat.au), privacy statement.
|
|
|
|
### 2026-04-11 07:00 — COOP/COEP headers for E2EE workers
|
|
- **Decision:** Added Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy to .htaccess
|
|
- **Rationale:** LiveKit E2EE frame encryption uses Web Workers that need SharedArrayBuffer
|
|
- **Files affected:** .htaccess on app2.m8chat.au (production server)
|
|
- **Status:** Headers verified active. E2EE re-enabled in code. NOT YET TESTED with a live call.
|
|
|
|
### 2026-04-11 07:00 — Fix hangup call termination
|
|
- **Decision:** Changed _clearCallMemberEvent to send {} instead of {'memberships': []}
|
|
- **Rationale:** Element X sends {} when ending calls (verified from DB). Our {'memberships': []} wasn't being recognised.
|
|
- **Files affected:** livekit_service.dart
|
|
- **Status:** Deployed. NOT YET TESTED.
|
|
### 2026-07-03 05:20 — Functionality audit + brand retheme (v1.6.0+8)
|
|
- **Decision:** Retheme app from purple (#5C35C9) to m8chat.au brand blues taken from the site's Elementor kit: brandBlue #1265ED, accentBlue #3B8BFF, navy darks derived from Ancop Blue #010B46.
|
|
- **Rationale:** Full audit (2026-07-03) found look and feel did not match m8chat.au. Logo rendered black because flutter_svg does not parse SVG <style> blocks; fills now inlined as attributes in m8logo.svg. Profile screen showed stale hardcoded appVersion 1.3.0. UI strings contained em-dashes (global style rule violation).
|
|
- **Audit results:** Backend all healthy (client API, livekit JWT, Jitsi external_api.js, push gateway, TURN 3478/5349). Full E2E pass in headless Chromium: login, rooms, spaces, profile, conference, help tabs, room open, message send verified on Synapse. Disposable audit users created and erased via admin API.
|
|
- **Files affected:** assets/images/m8logo.svg, lib/app/theme.dart (2.0.0), lib/core/config/app_config.dart (2.1.0), web/manifest.json, pubspec.yaml (1.6.0+8), em-dash fixes in 8 presentation/util files, chat_screen.dart lint fix (_leaveRoom context param).
|
|
- **Status:** Done — built, deployed to app2.m8chat.au (backup public_html.bak.20260703_pre160.tar.gz), visually verified light+dark.
|
|
|
|
### 2026-07-04 07:15 — Jitsi conference join fixed (v1.6.2+10)
|
|
- **Decision:** Root cause of "blank page on join" was the JS interop binding calling JitsiMeetExternalAPI (an ES6 class) as a plain function, i.e. without `new`. Browsers throw "Class constructor cannot be invoked without 'new'" and the meeting screen stayed blank. Feature had never worked in the web release build.
|
|
- **Fix:** `_JitsiApi` extension type with an external constructor (compiles to `new`), container-div wait loop in joinMeeting (post-frame callback could fire before the platform view div existed), JS options built via setProperty instead of Map.jsify (DOM node in a jsify map is unsupported), joinMeeting returns bool, jitsi_screen shows an error screen instead of blank on failure.
|
|
- **Verified:** headless Chromium on production build: iframe renders, prejoin page shows, devices pass, name entry works, reaches the Prosody JWT auth gate (expected for a tokenless room). COEP require-corp turned out NOT to block conf.m8chat.au in current Chromium.
|
|
- **Files affected:** lib/features/jitsi/data/jitsi_web_service.dart (1.2.0), lib/features/jitsi/presentation/jitsi_screen.dart (1.3.0), app_config 1.6.2, pubspec 1.6.2+10.
|
|
- **Status:** Done — deployed to app2.m8chat.au (backups pre160/pre161 tarballs on server).
|
|
|
|
### 2026-07-04 07:35 — Feature build round 1 (v1.7.0+11)
|
|
- **P1 Encrypted history:** new key_restore_prompt.dart offers recovery-key restore right after login when the account has secret storage and the fresh device can't read history (was buried in Profile). Fires once per session from rooms_screen initState.
|
|
- **P2 Incoming calls:** overlay existed but (a) was never mounted and (b) only listened for legacy m.call.invite. Fixed: IncomingCallOverlayHost mounted in app.dart MaterialApp.builder; matrixrtc_repository now also detects MSC3401 org.matrix.msc3401.call.member (what Element X sends), with stale-event + dedupe guards. Accept uses routerProvider (overlay is above the router navigator).
|
|
- **P3 Reactions/edit/delete:** reactions + delete were already wired (notes were stale). Added editMessage (sendTextEvent editEventId) + EditMessage notifier + Edit tile/dialog. Verified full context menu renders Reply/React/Copy/Edit/Delete.
|
|
- **P4 File/image send:** already implemented (sendFile + file_picker + UploadFile). Confirmed, no change needed.
|
|
- **P5 Panic button:** NEW feature/panic module. Config in account data au.m8chat.panic (room + message). Hold-to-fire button in rooms AppBar (only shows when armed); Profile tile to arm/disarm. Alert text sent FIRST, then best-effort m.location pin (geo: uri, msc3488) with 10s geolocation budget so a denied permission never blocks the alert. VERIFIED end-to-end: text + location with exact GPS landed in the target room via mock geolocation.
|
|
- **Status:** built, deployed app2.m8chat.au v1.7.0+11 (backup pre170 tarball on server). Analyze clean, tests pass.
|
|
- **Deferred:** ghost-device cleanup, rooms-provider rebuild perf, native session persistence — still open from original notes.
|
|
|
|
### 2026-07-04 07:50 — Mobile UI v2 design pass (v1.8.0+12)
|
|
- **Process:** ran a 7-agent design workflow (3 lens designers: information-density, reachability-flow, polish-delight; 3 judges; 1 synthesiser). All proposals scored 8-9/10. Output: prioritised spec (A high-impact/low-risk, B medium, C defer). Full spec archived in session.
|
|
- **Implemented the whole A tier + safe B items:**
|
|
- A1/A2 message bubble contrast: self #1265ED (selfBubble token, WCAG AA 5.11:1 vs failing 3.32:1), incoming #1C2452 + hairline border.
|
|
- A3 replaced failing low-alpha read text with subtleText token (rooms empty state, room tile).
|
|
- A4 deterministic per-user avatar colour (8-hue brand palette, name.hashCode) in matrix_avatar.dart — every disc now distinct.
|
|
- A5/A6 room tile: unread preview brightened+w500, compact density, timestamp aligned to title, unread dot spacing.
|
|
- A7 fixed stale "pencil icon" empty-state copy.
|
|
- A9 FAB now "New message" (compose) in thumb zone; explore moved to AppBar compass. Straight onPressed swap.
|
|
- A8 login + welcome brand gradient (#0A0F2E→#131A42), card hairline outlines.
|
|
- B7 message input capped minLines 1 / maxLines 6. B8 bubble maxWidth = viewport*0.78 clamped 560. B10 AppBar scrolledUnderElevation + hairline.
|
|
- **Deferred (documented in spec):** B1 message grouping, B2 day separators, B4 quick-reaction row, B5 panic hold-ring, B6 scroll-to-latest FAB, C1 swipe-to-reply (iOS PWA back-gesture risk), C2 group sender prefix (needs RoomModel field), C3 demote Help nav.
|
|
- **Verified:** headless Chromium at 390x844 on production build — gradient login, distinct avatar colours, compose FAB, AA bubble contrast all confirmed.
|
|
- **Status:** built + deployed app2.m8chat.au v1.8.0+12 (backup pre180 tarball). Analyze clean, tests pass.
|
|
|
|
### 2026-07-04 08:10 — Deferred UI items built (v1.9.0+13)
|
|
- **B1 message grouping:** MessageBubble gains optional isFirstInGroup/isLastInGroup (defaulted true so callers keep compiling). _Timeline computes them from reverse-index neighbours (older=index+1, newer=index-1) + 5-min gap. Sender name only on first-of-run, avatar only on last-of-run (32px gutter holds space otherwise), tail corner only on last-of-run, tight 1px padding mid-run.
|
|
- **B2 day separators:** _DayChip (Today/Yesterday/d MMM, toLocal AEST) rendered in a Column above the first message of each local day. Column wraps the gesture widget so long-press stays intact.
|
|
- **B4 quick-reaction row:** _MessageContextMenu gains a top row of six presets (👍❤️😂😮😢🙏, 44dp each) + "+" opening the existing EmojiPicker, above Reply. Uses existing sendReaction provider.
|
|
- **Verified in headless Chromium on production build:** grouping renders (Sam's 3-run collapsed to tight blue bubbles; Alex's 2-run shows name once + avatar on last bubble only, amber deterministic disc), "Today" chip renders, quick-react row renders with all six presets + picker button, tapping ❤️ sent a real m.reaction (confirmed via server /messages). Long-press context menu still fires (all of Reply/React/Copy/Edit/Delete intact).
|
|
- **Note:** cross-day separators are logic-only verified (can't backdate events without an appservice); the same-day "Today" chip confirms the render path.
|
|
- **Status:** built + deployed app2.m8chat.au v1.9.0+13 (backup pre190 tarball). Analyze clean, format clean, tests pass.
|
|
|
|
### 2026-07-04 08:30 — Scroll-to-latest + panic hold-ring (v1.10.0+14)
|
|
- **B6 scroll-to-latest:** _Timeline converted ConsumerWidget → ConsumerStatefulWidget with a ScrollController. When offset > 400 (reverse list, newest at 0), an AnimatedScale FloatingActionButton.small(arrow_downward) fades in at Positioned(right:12,bottom:12) inside a Stack around the ListView (NOT the Scaffold FAB slot). Tap → animateTo(0, 200ms, easeOut).
|
|
- **B5 panic hold-ring:** panic_button.dart → onLongPressDown starts a 700ms AnimationController; a red CircularProgressIndicator(value: controller.value) fills around the emergency_share icon; on completion HapticFeedback.heavyImpact + fire; early release resets silently. onTap still shows the hint; configured guard + _sending state preserved.
|
|
- **Verified:**
|
|
- Panic ring: headless Chromium screenshot mid-hold shows the red arc ~half-filled; completing the hold fired the alert (🚨 text landed in the SES Emergency room; location correctly skipped when geolocation not granted, proving the alert-before-location safeguard).
|
|
- Scroll-to-latest: the Flutter CanvasKit surface would NOT accept synthetic scroll input from Playwright (mouse wheel and drag both no-op on the canvas; input field steals focus). Verified instead with a deterministic widget test (test/scroll_to_latest_test.dart): 40 messages, starts at offset 0, drag up → offset > 400 + button appears, tap → offset back to 0. Passes.
|
|
- **Note for future:** getTimeline() only yields the ~10-event sync window and there is no scroll-back history pagination, so on a normal phone the timeline rarely exceeds ~1 screen and the jump button seldom shows in practice. Wiring requestHistory() on near-top scroll would make both the button and long-scrollback genuinely useful. Flagged, not built.
|
|
- **Status:** built + deployed app2.m8chat.au v1.10.0+14 (backup pre1100 tarball). Analyze clean, 4 tests pass.
|