Files
cinny/LOTUS_TODO.md
T
jared 17bd50cc4e
CI / Build & Quality Checks (push) Successful in 11m7s
CI / Trigger Desktop Build (push) Successful in 7s
feat(crypto): QR-code device verification (alongside emoji SAS)
B2 of the Matrix protocol-gaps roadmap, gate-green (688 tests):
- Enable QR verification methods (show/scan/reciprocate) in initMatrix.
- Extend DeviceVerification: the Ready step offers your own QR (byte-mode encode
  via qrcode), a camera 'Scan their QR code' flow, and an emoji fallback; the
  Started step routes reciprocate → a confirm step (useVerifierShowReciprocateQr)
  or SAS as before.
- New QrScanner component: getUserMedia + jsQR, handing the raw binaryData bytes
  to request.scanQRCode (BarcodeDetector is string-only, so can't be used).
- Adds qrcode + jsqr (small, pure-JS, client-only); build-verified under rolldown.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 11:30:23 -04:00

22 KiB
Raw Blame History

Lotus Chat — Work Backlog

Repo: lotus branch at https://code.lotusguild.org/LotusGuild/cinny Deploy: push to lotus → CI → auto-deploy to chat.lotusguild.org (~11 min)

Completed features are documented in LOTUS_FEATURES.md. Manual test steps live in LOTUS_TESTING.md. This file is open work only — resolved audit findings and shipped-feature write-ups were removed 2026-07 (full history in git).

Status legend: [ ] pending · [~] in progress / shipped-awaiting-QA · [x] done · [BLOCKED] server/upstream-gated · [DEFERRED]/[DROPPED]/[WON'T FIX] decided.


⚠️ TDS DESIGN LAW — READ BEFORE TOUCHING ANY UI

ALL Lotus Terminal Design System (TDS) styling — colors, animations, glows, borders, fonts, spacing — MUST come exclusively from /root/code/web_template/base.css CSS variables. Do NOT hardcode hex values. Do NOT invent new variable names. Canonical tokens: --lt-accent-orange, --lt-accent-cyan, --lt-accent-green, --lt-glow-*, --lt-box-glow-*, --lt-border-color, --lt-font-mono. Syntax-highlight token classes: .tok-kw .tok-str .tok-num .tok-cmt .tok-fn. Reference patterns: /root/code/tinker_tickets/ (markdown.js, base.js, ticket.css). Applies to every task without exception. New components must respect both TDS dark (LotusTerminalTheme) and TDS light (LotusTerminalLightTheme); non-TDS theme work uses vanilla-extract (match src/lotus-terminal.css.ts).

🧩 NATIVE-CINNY LAW — EVERY FEATURE MUST FEEL LIKE STOCK CINNY

Every feature must feel native to upstream Cinny — indistinguishable from what the Cinny team would ship. Reference: https://github.com/cinnyapp/cinny.

  • Use the folds design system, not bespoke UI (Button, Chip, IconButton, Menu, MenuItem, Dialog, Modal, Input, Switch, Badge, SettingTile, SequenceCard, …) and folds tokens (color.*, config.space.*, config.radii.*). Use folds Icon/Icons, never literal emoji, in UI chrome. No hardcoded hex/rgba(), no invented CSS variables.
  • Match Cinny's existing patterns — find the closest existing component/flow and mirror it before adding UI.
  • The ONE exception: explicit TDS features, which follow the TDS Design Law above (opt-in, only in Lotus Terminal mode).

Audit (2026-07) — closed out

A three-wave feature bug-hunt (~15 parallel agents, each batch independently reviewed) plus a low-tail cleanup. All confirmed 🔴/🟠 and the clean 🟡 tail are fixed, reviewed, and gate-green; details in git history + LOTUS_FEATURES. Only the minor items below remain open.

Still open (low tail — all 🟡 minor):

  • Calls host: C-M1 deafen DOM-fallback leaks late-added <audio> tracks; C-M2 .click()-by-testid toggles no-op if EC renames — both retire via EC-fork P6-2. C-L1 AFK mic not released if EC elides the echo; C-L2 ringtone-preview global cross-cancel; C-L3 first ring after cold load can be silent (ctx not unlocked); C-L5 speaker-observer churn on membership change; C-L7 all-muted DOM miscount if EC label format differs; C-L8 PiP sw/nw resize anchor jitter at min size.
  • Threads: T5 participating detection is server-bundle-only (thread.hasCurrentUserParticipated) → can under-notify a thread you just replied to; T6 room "Mentions & Keywords" not honored for participated/Default thread replies (over-notify); T7 account-data thread-mute write is a lost-update race.
  • Crypto/session: F5 OIDC refresh drops expiresAt on persist (persistTokens can't reach the expiry without SDK-internal plumbing; refresh is reactive on 401).
  • Native/desktop: D7 Unity badge application://cinny.desktop id may not match the installed .desktop basename — runtime-verify on the .deb/AppImage. H10 room-name setter fire-and-forget/silent length reject (trivial). N6 per-message read-receipt avatars may not refresh on membership change (emitter uncertain, low impact).
  • EC fork (EC1EC6 fixed on element-call:lotus, needs a republish): re-apply setTimeout cleanup, remote-gated subscription → allConnections$, per-call decoration state leak, re-subscribe-every-render, focus-clear on missing userId. Rides with P6-2 phase 2.

Shipped — Awaiting Live Verification

Built and gate-green; verify per LOTUS_TESTING.md, then graduate to LOTUS_FEATURES.md. Includes the desktop/native Tier A/B stack (P5-35/36/41/42/43/44/46/47/48/49/55/56/57, P6-1 Linux parity) — all CI-compile-verified, runtime-verify on Windows/Linux — plus:

Area Test guide
Full-Screen Camera Broadcasts (per-participant focus) A5 / G2
Advanced search filters + virtualized infinite scroll K2 / M1 / M2 / M4
Custom Accent Color Picker (non-TDS) · 5 Color Theme Presets M3 / M5
Intersection lazy media loading · context-aware thumbnails H1 / H2
Thread Panel (side drawer) + per-thread notification modes (P4-1) (thread QA)
Encrypted message search indexing/caching (opt-in, default OFF) search backlog
Remind Me Later · Mobile Bookmarks access K1 / E5
In-Call Soundboard (P5-15) · Quality Controls (P5-31) · Permissions (P5-31) D2-7 / D2-8 / D2-9
Desktop proactive update notifications (P5-40) J1
OIDC/SSO login (P4-6, needs an MSC3861 server — pick mozilla.org on login) OIDC
Windows native WinRT toast quick-reply / click-to-open (D6, AUMID) rich-toast (§backlog)

🔴 Open — Actionable

🧨 Encryption / E2EE — ⚠️ EXTREME COMPLEXITY · 🧠 PLANNING SESSION REQUIRED

Observed live in prod 2026-06-30 during a 2-person Element Call (E2EE). These span client rust-crypto (matrix-js-sdk@41.7.0) ↔ Synapse ↔ EC MatrixRTC E2EE and are interrelated — do NOT spot-fix. Capture first: run Settings → Developer Tools → Crypto Diagnostics during the next affected call + a synapse-side trace before any fix. (Full runbook was in LOTUS_E2EE_INVESTIGATION.md, now in git history.) None are caused by the EC fork work.

  • KE-1 — OTK upload conflict storm (CRITICAL, root-cause candidate). POST /keys/upload returns 400 M_UNKNOWN: One time key … already exists continuously — the rust-crypto store and Synapse have diverged OTK state (upstream matrix-rust-sdk#5200, OPEN: on the 400 the SDK never marks the request sent → re-uploads forever; not fixed in 41.7.0). Leading web trigger: cinny never calls navigator.storage.persist(), so the IndexedDB crypto store is evictable while the localStorage session survives → device resurrects with a blank store. Buildable preventive fix (no call needed): request persistent storage on login (+ optional multi-tab guard + a 400-loop→recovery prompt). Healing an already-diverged device still needs a clean logout+login.
  • KE-2 — EC media keys not arriving/decrypting → audio/video cut out (CRITICAL). MissingKey … for participant, unexpected encrypted to-device io.element.call.encryption_keys. Almost certainly downstream of KE-1 (broken Olm sessions). This is the "friend's audio cuts out" symptom.
  • KE-3 — Timeline decrypt error: missing algorithm field (HIGH). rust-crypto can't parse a malformed/legacy encrypted event — capture the offending event id + raw content.
  • KE-4 — MatrixRTC delayed-event / membership timeouts (MEDIUM-HIGH). Restart delayed event timed out, repeated msc4157.update_delayed_event — may be partly HS responsiveness; correlate with synapse latency. Same planning session (shares the call-reliability surface).

Security & Privacy

  • N97 — Access token + device id in plaintext localStorage (state/sessions.ts), XSS-exposed. Architectural — needs a token-protection / session-storage redesign.
  • Persisted PII without encryption: user status message + expiry (Profile.tsx), unsent composer drafts (RoomInput.tsx). Leak risk on shared devices.

PWA / Offline / Web Push

  • N107 — Web Push is non-functional: src/sw.ts has no push handler. Needs a push listener + Matrix push-gateway integration. The one substantive remaining feature (session/crypto groundwork it waited on has landed).
  • No app-asset caching strategy in src/sw.ts — no offline capability.

Dependencies / Build / Hygiene

  • Build-time: lotusDenoise does heavy sequential fs in closeBundle; viteStaticCopy has redundant renames — could be streamlined.
  • patch-folds.mjs edits node_modules directly (robust today; patch-package considered but more brittle to folds restructuring — WON'T-DO unless it breaks).
  • types/matrix/ mirrors SDK types instead of importing them — drift risk; spot-fix highest-risk only.
  • contrib/nginx/contrib/caddy examples: headers + try_files already synced with prod; the prod nginx add_header isn't inherited by cache location blocks (pre-existing; SPA entry / still gets all headers).
  • as any casts across src/ — gradual typing cleanup. Keep commits scoped (bisect-friendly). Keep README fork-sync version/logo current.

🌐 Matrix Protocol Gaps

Genuine Matrix client-spec / MSC features Lotus does not yet implement (audited 2026-07 against the codebase — almost everything else is built: pinning, stickers+picker, room directory, mutual rooms MSC2666, blurhash, key backup/recovery/SSSS, SAS verification, ignore list, invite spam-filter, voice messages, polls, threads, spaces, OIDC, extended profiles, delayed events, authed media). Build each fully — spec-correct events, native-Cinny folds UI, tests. Order = clean wins first.

Phase A (2026-07, gate-green 683 tests):

  • Mark as Unread — MSC2867 m.marked_unread. Room account data { unread: true } (+ unstable com.famedly.marked_unread) via mx.setRoomAccountData; clear on read. Context-menu item in RoomNavItem + light the existing unread dot; integrate state/room/roomToUnread.ts.
  • Low Priority rooms — m.lowpriority tag. Mirror the favourite impl (RoomNavItem.tsx:331-337 setRoomTag/deleteRoomTag + the favourites category in home/Home.tsx): context-menu toggle + a collapsed "Low Priority" category sorted to the bottom, excluded from normal unread nudging.

Phase B (2026-07, gate-green 688 tests):

  • Disappearing Messages — MSC1763 m.room.retention. PL-gated room-settings SettingTile to set { max_lifetime }; retention badge; a client-side sweep hides/self-redacts own expired events (pattern like the mute-timer restore in ClientNonUIFeatures.tsx). True server deletion also wants Synapse retention: (LXC 151).
  • QR Device Verification — reciprocate QR. Add the QR path beside emoji-SAS in components/DeviceVerification.tsx: render with qrcode.react (already a dep), scan via BarcodeDetector (fallback jsQR); uses the SDK VerificationRequest QR/reciprocate support.

Phase C (large — each its own planning session):

  • Room Widgets — MSC1236 + widget API. No general widget UI exists (only the PL entry im.vector.modular.widgets; the EC call widget is hardcoded). Read im.vector.modular.widgets/m.widget state, add an Add/Manage panel + sandboxed iframe renderer via matrix-widget-apiextend the existing EC widget plumbing (plugins/call/CallEmbed.ts). Enables Etherpad/notes/dashboards/integrations.
  • Sliding Sync — MSC3575 / simplified MSC4186. Lotus is on legacy full /sync though the server advertises simplified_msc3575. matrix-js-sdk ships SlidingSync; migration → near-instant cold start + low memory + huge-account scale. Touches the sync/room-list/spaces/unread core — behind a feature flag with a legacy fallback. Plan separately before touching.

Server-gated / advanced (capture, don't build yet): QR sign-in for a new device (MSC4108 rendezvous — needs an HS-side endpoint); dehydrated devices (MSC3814 — offline key delivery, also helps the E2EE KE cluster); E2EE history key sharing on invite (MSC3061 shared_history, niche); voice broadcast (Element MSC3888, low value — skip).


📋 Open Feature Backlog

[ ] P4-4 · Math / LaTeX Rendering (LOW PRIORITY)

Render $…$ / $$…$$ via KaTeX; graceful fallback to raw text. Sanitizer must be patchedsrc/app/utils/sanitize.ts (sanitize-html, disallowedTagsMode:'discard') strips all MathML: add <math><mi><mo><mn><mrow><mfrac><msqrt><mroot><msub><msup><msubsup><munder><mover><mtable><mtr><mtd>… + annotation to permittedHtmlTags, and xmlns/display/mathvariant to permittedTagToAttributes. Parser: split text nodes on /(\$\$.*?\$\$|\$.*?\$)/g in react-custom-html-parser.tsx<KaTeX>. Lazy-import katex/dist/katex.min.css only when a math block renders. Verify KaTeX bundle-size impact.

[~] P5-20 · Quick Reply from Browser Notification (partial)

Done: notifications show the real body, click navigates to the specific event + focuses the tab. Remaining: inline reply via Notification Actions API needs the SW push+notificationclick pipeline (switch new Notification()serviceWorkerRegistration.showNotification() so the SW receives notificationclick; on event.action==='reply' POST m.room.message with the stored {roomId, threadId}). Ties into N107.

[~] P5-30 · Advanced ML Noise Suppression — open verification

Shipped in the EC fork (DeepFilterNet3 default-capable / DTLN / RNNoise / Speex; AEC on, AGC off for ML tier; never-silent watchdog). Open: real-call by-ear A/B — model choice, lotusDenoiseFloor, AGC on/off (LOTUS_TESTING §D2-1 / J2). GTCRN (deferred): tiny MIT 16 kHz model beating RNNoise, but no drop-in browser package — needs onnxruntime-web in a Web Worker behind a custom AudioWorklet ring-buffer (ORT can't run in an AudioWorklet, issue #13072); ~1-week build. Revisit only if low-power quality proves insufficient. HW-gated (FRCRN/Maxine) = desktop-Rust-only future.

[~] P6-2 · Element Call fork — retire remaining DOM hacks (Phase 2 needs publish)

Phase 1 shipped: io.lotus.set_deafen (LiveKit-source deafen/screenshare-audio-mute) replaces the brittle <audio>.muted iframe hack; cinny sends it join-gated alongside the transitional DOM fallback. Phase 2 (blocked on user npm publish): publish fork 0.20.1-lotus.2 → bump cinny pin lotus.1lotus.2 → delete the CallControl.ts .muted fallback + the EC1EC6 fixes ship. Deferred pieces (P6-2b): the useCallSpeakers DOM-scrape is a dormant fallback behind io.lotus.call_state; .click()-by-data-testid UI toggles are low-value fork surface. Divergence to confirm: deafen doesn't silence soundboard/Unknown-source audio (setVolume type limit).

[ ] Mobile audit

Comprehensive audit of all LOTUS_FEATURES.md features for mobile PWA usability + responsiveness. Method: 44px touch targets, no horizontal overflow, full-screen modals/drawers on mobile, composer not obscured by keyboard.

Deferred / dropped (decided — kept for context)

  • [DEFERRED] P5-51 Federated "Identity Contexts" (session isolation) — multi-sprint, touches auth/crypto/storage core; smaller intermediate step = plain multi-account switch. [DROPPED] P5-52 per-room sync governor — js-sdk can't truly per-room filter /sync; only a cosmetic hide. [DEFERRED] P5-53 local scripting plugin — prefer a declarative automation-rules feature (no arbitrary code). [DEFERRED] Audit-3 profile banner — MSC4427 open/unmerged; revisit on merge. [WON'T FIX] P5-50 Windows HW media pipeline (WebRTC decode lives in WebView2; not injectable). [MOVED] P5-9 LFG → LotusBot !lfg.

🚫 Blocked Features (server / upstream gated)

Re-run /_matrix/client/versions + unstable_features after each Synapse upgrade.

  • [BLOCKED] Live Location Sharing (MSC3489 + MSC3672 both false) — real-time GPS beacons over the existing static share.
  • [BLOCKED] Reaction/Relation Redaction (MSC3892 false) — remove a reaction without redacting the parent; current full-redaction fallback is acceptable.
  • [BLOCKED] Room Preview before joining (MSC3266) — GET /v1/rooms/{id}/summary returns 404 M_UNRECOGNIZED on Synapse 1.155 despite msc3266_enabled:true.
  • [BLOCKED] Thread Subscriptions (MSC4306 false) — "Follow thread" button (depends on the shipped Thread Panel).

📖 Reference

Server Capabilities (as of 2026-06)

  • Homeserver matrix.lotusguild.org · Synapse 1.155.0 · Matrix spec up to v1.12 (+ MSC unstable_features).
  • MSC ON: msc4140 · msc3771 · msc3440.stable · msc4133.stable · simplified_msc3575 · msc4222 · msc3266 (flag on but v1 summary 404s) · msc3401_matrix_rtc. OFF/blocked: msc4306 · msc3882 · msc3912 · msc4155 · msc3489/msc3672 · msc3892.
  • Live endpoints: Report User (MSC4260) 200 · Report Room (MSC4151) .
  • Homeserver access (audits): Synapse = LXC 151 (pct exec 151 -- bash), config /etc/matrix-synapse/homeserver.yaml. Web deploy = LXC 106. Voice guard = voice-limit-guard.py on LXC 151.
  • SDK notes: no arbitrary profile-field methods (use mx.http.authedRequest() for MSC4133); js-sdk can't per-room filter /sync; sanitizer strips <math>/MathML; SW exists at src/sw.ts; getMatrixToRoom() builds invite URLs; EC audio-inject unblocked via the fork's io.lotus.inject_audio.

Key File Reference

What File Lines
Global keydown / room nav hooks/useKeyDown.ts · hooks/useRoomNavigate.ts whole / 19-72
Room unread counts atom state/room/roomToUnread.ts roomToUnreadAtom
Overlay portal provider pages/App.tsx · index.html 65 / 101
Room settings tabs features/room-settings/RoomSettings.tsx 27-56
State event read/write pattern features/common-settings/general/RoomEncryption.tsx 42-52
Power levels hooks/usePowerLevels.ts whole
Slash commands hooks/useCommands.ts 140-537
Chat background picker/defs features/settings/general/General.tsx · lotus/chatBackground.ts 945-981 / whole
Matrix.to URL builder plugins/matrix-to.ts getMatrixToRoom()
Media URL conversion utils/matrix.ts mxcUrlToHttp()
Search pagination / virtual features/message-search/{useMessageSearch,MessageSearch}.tsx 74-121 / 234-365
Call mic control plugins/call/CallControl.ts 206-212
Knock support check utils/matrix.ts 376-391
Notification mute push rules hooks/useRoomsNotificationPreferences.ts 110-150

Element Call fork — operational reference

Fork = LotusGuild/element-call (branch lotus, from upstream tag v0.20.1); cinny consumes the npm package @lotusguild/element-call-embedded (built bundle copied into public/element-call/).

Publish a new version (manual; needs the Gitea npm token): bump embedded/web/package.json (current unpublished 0.20.1-lotus.2) → pnpm run build:embedded (Node 24, pnpm 10.33) → cd embedded/web && npm version <tag> --no-git-tag-version && npm publish (Gitea registry) → in cinny bump the @lotusguild/element-call-embedded pin (currently 0.20.1-lotus.1) → npm install → build.

io.lotus.* widget actions (add new toWidget actions to the enum + LOTUS_TO_WIDGET_ACTIONS in src/lotus/lotusActions.ts; only send AFTER call-join or a 10s timeout fires):

Action Dir Purpose Module
io.lotus.call_state EC→host speaker/mute/camera stream (lotusCallState=1) lotusCallState.ts
io.lotus.focus_participant host→EC spotlight (works during screenshare) lotusFocus.ts
io.lotus.inject_audio host→EC soundboard clip mixed into call (lotusAudioInject=1) lotusAudioInject.ts
io.lotus.set_quality host→EC audio/screenshare bitrate/fps caps lotusQuality.ts
io.lotus.decorations host→EC in-call avatar decorations lotusDecorations.ts
io.lotus.set_deafen host→EC LiveKit-source deafen (P6-2) lotusDeafen.ts

Also flag-gated: lotusTransparent/lotusTheme, lotusDenoiseSource=1 (in-source ML denoise).

CI/CD + per-feature checklist

edit → commit → git push origin lotus
→ Gitea Actions: tsc --noEmit, eslint, prettier (~3 min)
→ lotus_deploy.sh on LXC 106 polls CI → npm ci && npm run build → rsync → live (~11 min)

Before marking a feature complete: npx tsc --noEmit (0 errors) · npx eslint src/ (0 new) · npx prettier --check src/ · npm test (Node runner via tsx, hard CI gate — colocated *.test.ts) · update README.md/landing/index.html for Lotus-custom features · visually verify on chat.lotusguild.org.