Pure formatting reflows (multi-line wrapping of long lines/imports/tables); no behavior change. Clears the working tree of pending prettier diffs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
50 KiB
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)
🏗️ Infrastructure & Maintenance
- Upgrade Synapse to v1.155.0 ✅ Done 2026-06-18
- Context: 1.155.0 is the last version supporting Debian 12 Bookworm. LXC 151 is already on Debian 13 Trixie — OS migration was completed prior to this upgrade.
- What changed (1.154→1.155): No breaking changes, no config changes, no DB migrations. Bugfixes: to-device EDU size limiting, restricted room joins, sliding sync subscription response timing. Rust port of more internal classes (perf only).
- MSC4452 (Preview URL capabilities) shipped in 1.154 — opt-in via
msc4452_enabled, not enabled.
📱 Quick Feature Additions
- Full-Screen Camera Broadcasts ⚠️ UNTESTED — verify in a real call
- Context: Element Call currently supports full-screening screenshares. We need to parity this functionality for camera broadcasts.
- Goal: Users should be able to toggle any camera feed to full-screen mode, similar to the existing screenshare full-screen implementation.
- Implemented 2026-06-18:
- Fullscreen button always shows — removed
screenshare &&gate inCallControls.tsx. The fullscreen button is now available in camera-only calls, not just during screenshares. - Per-participant camera focus —
CallControl.focusCameraParticipant(userId)added. Finds the participant's video tile via[data-testid="videoTile"]/[data-video-fit]+[aria-label="${userId}"], enables spotlight mode, then clicks the tile to focus them. - MemberGlance "Focus camera" action — clicking a participant avatar in the call status bar now opens a mini popup with "Focus camera" (triggers focusCameraParticipant) and "View profile" options, rather than immediately opening the profile.
- PiP fullscreen button — a small fullscreen toggle button (⛶/⊡) is shown in the PiP overlay top-right, allowing users to go fullscreen directly from PiP mode without navigating back to the call room.
- Fullscreen button always shows — removed
⚠️ 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.cssCSS variables. Do NOT hardcode hex values. Do NOT invent new variable names. Do NOT deviate from the design tokens defined in that file. The canonical variable reference:--lt-accent-orange,--lt-accent-cyan,--lt-accent-green,--lt-glow-orange,--lt-box-glow-*,--lt-border-color, etc. Reference implementation for code patterns:/root/code/tinker_tickets/(markdown.js, base.js, ticket.css) This rule applies to EVERY task in this file without exception.
Completed features are documented in LOTUS_FEATURES.md.
Legend:
[AUDIT REQUIRED]— at least one assumption needs code/server verification before implementing[SERVER CHECK]— depends on a Synapse feature or MSC; verify onmatrix.lotusguild.org[LOW PRIORITY]— implement after all higher-priority items[EXTREME COMPLEXITY]— multi-sprint, plan separately before touching[BLOCKED]— cannot build until a server upgrade, upstream MSC, or dependency resolves[IMPROVE]— feature exists in upstream Cinny; this task enhances it for Lotus Chat
Status: [ ] pending · [~] in progress · [x] completed
Server Capabilities (as of June 2026)
- Homeserver:
matrix.lotusguild.org - Synapse version:
1.155.0(2026-06-18) — fully up to date; last version for Debian 12 (LXC 151 already on Debian 13 Trixie) - Matrix spec: up to
v1.12formally; newer MSC features viaunstable_features
Confirmed facts
| Finding | Impact |
|---|---|
MSC flags ON: msc4140 · msc3771 · msc3440.stable · msc4133.stable · simplified_msc3575 · msc4222 · msc3266 · msc3401_matrix_rtc |
All safe to use now |
MSC flags OFF: msc4306 (thread subscriptions) · msc3882 · msc3912 · msc4155 |
These features are BLOCKED |
MSC3266 room summary: flag msc3266_enabled: true set but GET /v1/rooms/{id}/summary still returns 404 (M_UNRECOGNIZED) |
Room Preview BLOCKED — endpoint not implemented in Synapse 1.155 |
| MSC3892 relation redaction: not in flags | Reaction Redaction feature BLOCKED |
MSC4260 report user: POST /_matrix/client/v3/users/{userId}/report returns 200 ✅ |
Report User UNBLOCKED — endpoint live since Synapse 1.133; ready to build |
| MSC4151 report room: HTTP 405 on GET = endpoint exists (POST only) | Report Room live ✅ |
folds AvatarImage does NOT accept children |
Add frame/overlay inside UserAvatar.tsx itself — optional frameName prop |
| No in-app toast system exists (was) | Built ToastProvider + Jotai queue; at App.tsx:65 |
useUnverifiedDeviceCount() hook exists |
src/app/hooks/useDeviceVerificationStatus.ts:65-106 |
Voice player: AudioContent.tsx:44-223 |
Playback rate on hidden <audio> at line 217 |
CallControl.setMicrophone(bool) at CallControl.ts:206-212 |
For AFK auto-mute |
CallControl.toggleSound() at CallControl.ts:230-251 |
Push-to-deafen — just wire a hotkey to this |
| matrix-js-sdk has NO arbitrary profile field methods | Use mx.http.authedRequest() for MSC4133 |
Sanitizer (sanitize.ts) allows table, div, span, a, code, hr |
LFG HTML card is safe locally; test on Element/FluffyChat |
Sanitizer STRIPS <math>/MathML tags |
Math/LaTeX task must also modify sanitizer |
Service worker EXISTS at src/sw.ts |
Quick-reply task: add notificationclick handler |
knockSupported() utility exists at matrix.ts:376-391 |
Knock UX: only need "Request to Join" in RoomIntro.tsx |
KeywordMessages.tsx already has custom keyword push rules |
Full push rule editor: only non-keyword rule types need new UI |
getMatrixToRoom() in matrix-to.ts generates invite URLs |
Invite link: just add QR code to room settings |
| Cindy CANNOT inject audio into EC call stream | In-call soundboard must be redesigned as local-only |
| Folds uses vanilla-extract in non-TDS, NOT CSS custom properties | Custom accent color: must create new vanilla-extract theme variant dynamically |
| Theme presets need ~50 CSS custom properties each | Significant design work before coding |
useCallSpeakers.ts CSS MutationObserver polling |
Visual speaking indicator: TDS ring animation on top of existing data |
| MSC3489/3672 live location: BOTH false on server | Live Location BLOCKED |
Key File Reference
| What you need | File | Lines |
|---|---|---|
| Global keydown hook | src/app/hooks/useKeyDown.ts |
whole file |
| Room navigation | src/app/hooks/useRoomNavigate.ts |
19-72 |
| All room IDs atom | src/app/state/room-list/roomList.ts |
allRoomsAtom |
| Room unread counts | src/app/state/room/roomToUnread.ts |
roomToUnreadAtom |
| Overlay portal provider | src/app/pages/App.tsx |
65 |
| Portal container div | index.html |
101 |
| Room settings tabs | src/app/features/room-settings/RoomSettings.tsx |
27-56 |
| State event read/write pattern | src/app/features/common-settings/general/RoomEncryption.tsx |
42-52 |
| Power level checker | src/app/hooks/usePowerLevels.ts |
whole file |
| Slash command registration | src/app/hooks/useCommands.ts |
140-537 |
| Chat background picker | src/app/features/settings/general/General.tsx |
945-981 |
| Chat backgrounds definition | src/app/features/lotus/chatBackground.ts |
whole file |
| Matrix.to URL builder | src/app/plugins/matrix-to.ts |
getMatrixToRoom() |
| Media event content types | src/app/types/matrix/common.ts |
46-91 |
| Media URL conversion | src/app/utils/matrix.ts |
mxcUrlToHttp() |
| Message pagination (search) | src/app/features/message-search/useMessageSearch.ts |
74-121 |
| Infinite pagination pattern | src/app/features/message-search/MessageSearch.tsx |
234-365 |
| Poll event format | src/app/components/message/content/PollContent.tsx |
1-320 |
| Theme class application | src/app/hooks/useTheme.ts |
25-60 |
| Animations file | src/app/styles/Animations.css.ts |
whole file |
| Message status (EventStatus) | src/app/features/room/message/Message.tsx |
84-142 |
| Call member change events | src/app/hooks/useCall.ts |
37-52 |
| Mic control in calls | src/app/plugins/call/CallControl.ts |
206-212 |
| Device verification hook | src/app/hooks/useDeviceVerificationStatus.ts |
65-106 |
| Knock room support check | src/app/utils/matrix.ts |
376-391 |
| Room join button location | src/app/components/room-intro/RoomIntro.tsx |
25-119 |
| Notification mute via push rules | src/app/hooks/useRoomsNotificationPreferences.ts |
110-150 |
| Message text body CSS | src/app/components/message/layout/layout.css.ts |
182-205 |
Priority 3 — Higher complexity / lower daily frequency
[ ] P3-4 · Accessibility Improvements (WCAG 2.1 AA)
What: Comprehensive audit and fix pass targeting the critical user paths:
- Room list navigation (keyboard-only)
- Reading messages in the timeline (screen reader announces new messages)
- Composing and sending a reply
- Opening and closing modals (focus trap, return focus)
- ARIA labels on all icon-only buttons
Scope: Do NOT attempt to make every corner of the app AA-compliant in one pass — focus on the golden path (open app → find room → read → reply → send).
[AUDIT REQUIRED] — Run an automated audit first: npx axe-core or browser DevTools accessibility tree. Document every violation before writing a single line of code. Prioritize by severity (critical > serious > moderate).
Investigation Findings:
- Root Cause: Inconsistent focus management, missing
aria-liveregions for dynamic timeline updates, and sparse global keyboard shortcuts. - Approach: Standardize
focus-trap-reactusage (referenceRoomNavItem.tsx). Addaria-liveregions to the timeline. ExpanduseKeyDown.tsfor section navigation shortcuts. - Complexity: Medium-High (audit is the main work).
[ ] P3-8 · Thread Panel (full side drawer)
⚠️ LARGEST FEATURE — requires its own planning session before implementation.
What: A right-side drawer for threaded conversations. Currently "Reply in Thread" exists but there is no panel to read or write thread replies.
Features:
- Click "Reply in Thread" → opens thread drawer on the right
- Thread root event shown at the top of the panel
- Full message rendering for all in-thread replies (reuse timeline components)
- Reply input at the bottom (full composer with formatting, emoji, etc.)
- Unread count badge on the thread button in the main timeline
- Keyboard shortcut to close thread panel
Architecture:
- New Jotai atom:
activeThreadEventId: string | null - New component:
src/app/features/room/thread/ThreadPanel.tsx - Rendered alongside
RoomViewas a conditional right panel (mirror the members drawer pattern) - Filter events in timeline to
m.threadrelation for the active root event ID - Shares the same
mxclient and room reference as the main timeline
[AUDIT REQUIRED] — Deeply audit how m.thread relation events are currently stored and retrieved in the matrix-js-sdk. Understand the thread aggregation API: GET /rooms/{roomId}/relations/{eventId}/m.thread. Check if RoomTimeline.tsx currently filters out thread replies from the main timeline (it should — confirm).
Investigation Findings:
- Root Cause: Current
m.threadevents are treated as standardm.room.messageevents and rendered in the main timeline. - Approach: Introduce new Jotai atom
activeThreadEventId. CreateThreadPanel.tsx. UpdateRoomTimeline.tsxto filter out thread relations (m.relates_to). Implement aggregation fetch usingGET /rooms/{roomId}/relations/{eventId}/m.thread. Usethread.timelineSetdirectly for the most accurate thread view. - Complexity: High.
Priority 4 — Specialized, high complexity, or low priority
[ ] P4-7 · Virtualized Infinite Scroll for Search Results
What: Replace the manual "load more" button with an automated, virtualized infinite scroll for search results.
Approach: Utilize @tanstack/react-virtual in MessageSearch.tsx to handle the nextToken automatically as the user scrolls.
[ ] P4-8 · Encrypted Message Search Indexing & Caching
What: Implement a persistent local cache for search results, optimized for encrypted rooms.
Approach: Use IndexedDB to store search metadata (event IDs, timestamps) to prevent redundant server-side decryption/fetching.
[x] P4-9 · Advanced Search Filter UI — PARTIALLY DONE (UNTESTED)
What: Improve search filter UX in SearchFilters.tsx.
Completed 2026-06-18:
- ✅
SelectSenderButton— picker UI for sender filter (previously required typingfrom:@userby hand) - ✅
DateRangeButton— quick-pick presets: Today / Last week / Last month / Last year - ✅
Has linkchip —contains_url: truefilter, wired to Matrix API and URL param UNTESTED — needs verification at chat.lotusguild.org.
Remaining for parity with Discord/Slack:
has:image/has:file/has:video— msgtype filters (require client-side post-filtering, no server API)- Pinned messages filter
- Saved searches / search history
[ ] P4-1 · Thread Notification Mode Per-Thread (MSC3771)
Spec: MSC3771 (stable). Depends on Thread Panel (#P3-8).
What: Per-thread notification toggle: "All messages" vs "Mentions only". Accessible from the thread panel header. Tracks unread counts separately per thread.
[AUDIT REQUIRED] — Implement after Thread Panel. Requires understanding how the SDK tracks per-thread unread counts.
Complexity: Medium (after thread panel exists).
[ ] P4-2 · Thread Subscriptions (MSC4306) [BLOCKED]
Spec: MSC4306 (Synapse experimental). Depends on Thread Panel (#P3-8).
What: "Follow thread" button to receive notifications for a thread you haven't posted in. Uses MSC4306 subscription endpoint.
[SERVER CHECK] — org.matrix.msc4306 = false on matrix.lotusguild.org — BLOCKED until server enables it.
Complexity: Medium (after thread panel exists).
[ ] P4-4 · Math / LaTeX Rendering in Messages (LOW PRIORITY)
Spec: CS-API §11.5 (stable) — formatted_body can contain LaTeX.
What: Render $...$ or $$...$$ LaTeX expressions in message bodies. Use KaTeX (lightweight, ~100KB, renders server-side-compatible CSS). Must gracefully fall back to raw LaTeX text if KaTeX fails.
Note: This is LOW PRIORITY — only useful for academic/technical communities. Implement last.
[AUDIT REQUIRED] — Confirm KaTeX bundle size impact on the Vite bundle. Check if matrix-js-sdk's HTML sanitizer strips LaTeX before it reaches the renderer. The formatted_body sanitization pipeline is the main risk here. (Confirmed: sanitizer STRIPS <math> tags — must be patched alongside the renderer.)
Complexity: Low-Medium.
[ ] P4-5 · Live Location Sharing (MSC3489 + MSC3672) (LOW PRIORITY, HIGH COMPLEXITY) [BLOCKED]
Spec: MSC3489 + MSC3672. Implemented in Element Web.
Note: Static location sharing is already implemented. This adds live/real-time GPS beacons. Very low priority per user preference.
What: Start sharing live location → creates m.beacon_info state event → client posts m.beacon events on a timer → other users see your position update live on a map.
[SERVER CHECK] — org.matrix.msc3489 = false AND org.matrix.msc3672 = false on matrix.lotusguild.org — BLOCKED.
Complexity: High. Requires background geolocation API + live map rendering.
[ ] P4-6 · OIDC / SSO Next-Gen Auth (MSC3861) (EXTREME COMPLEXITY, LOW PRIORITY)
Spec: MSC3861, merged Matrix spec v1.15. Uses Matrix Authentication Service (MAS).
Context: ~80% of homeserver users have LLDAP/Authelia/SSO accounts. SSO is currently enabled on matrix.lotusguild.org but accounts are not yet linked. This would allow users to log in via their SSO credentials.
What: OAuth 2.0 / OIDC login flow, token refresh, account management page linking Matrix identity to SSO identity.
EXTREME COMPLEXITY — requires: MAS deployment/configuration on the homeserver, significant auth flow changes in the client, token refresh handling, session management overhaul.
[SERVER CHECK] — Before any client work, audit whether MAS is already deployed on compute-storage-01. Check: pct exec 151 -- systemctl status matrix-authentication-service or similar.
Complexity: Extreme. Multi-sprint project. Plan separately.
Priority 5 — Gamer / Aesthetic / Customization
[ ] P5-1 · Custom Accent Color Picker (non-TDS mode only)
What: A hex/HSL color picker in Settings → Appearance. Chosen color replaces the primary accent throughout the UI: buttons, badges, active states, highlights, presence dot, links. Applied via a CSS custom property override injected into <head>.
IMPORTANT: This feature is completely inactive when TDS is enabled — TDS has its own fixed palette. Add this setting under a "Non-TDS Themes" section that is hidden when TDS is active.
[AUDIT REQUIRED] Identify all CSS custom properties that constitute the "accent color" in non-TDS mode. Map them to the folds/vanilla-extract token names. (Confirmed: folds uses vanilla-extract, NOT CSS custom properties — must create a new vanilla-extract theme variant dynamically.)
Complexity: Medium.
[ ] P5-2 · Additional Color Theme Presets
What: 5 new one-click theme presets alongside TDS. Each must be a complete, polished system with proper contrast ratios (WCAG AA). All implemented as vanilla-extract themes matching the existing TDS pattern.
Themes:
- Cyberpunk — deep navy bg (
#0a0015), electric purple (#bf5fff) + hot pink (#ff2d9b) accents, neon glow - Ocean — deep sea blue bg (
#020b18), teal (#00c9b1) + aqua (#0096d6) accents, soft feel - Blood Red — near-black bg (
#0d0203), deep crimson (#7a0010) + bright red (#ff2233) accents - Classic Matrix — pure black bg (
#000000), phosphor green (#00ff41) text + accents - Midnight — dark charcoal (
#111827), cool blue-grey (#6b7ca8) accents, clean minimal
[AUDIT REQUIRED] Study src/lotus-terminal.css.ts for the full token list before designing themes. All tokens must be covered (~50 CSS custom properties each).
Complexity: Medium (design effort is the main cost).
[MOVED] P5-9 · LFG (Looking for Group) Command → LotusBot
Decision: Implemented as !lfg in LotusBot rather than a client slash command. Bot-side rendering works consistently across all Matrix clients; client-side enhanced cards would only be visible to Lotus Chat users and require sanitizer auditing. The bot can also support richer flows (list active LFGs, DM interested players, auto-expire posts).
[x] P5-5 · Intersection-Based Lazy Loading ⚠️ UNTESTED — needs verification in timeline with many images
What: Use IntersectionObserver to trigger media decryption and loading only when components approach the viewport.
Approach: Reduce initial memory footprint and improve timeline load times by deferring decryption of images/videos until they are visible.
[x] P5-6 · Context-Aware Thumbnail Previews ⚠️ UNTESTED
What: Enhance thumbnail rendering in the timeline for consistent, polished aesthetics.
Approach: Use CSS object-fit: cover with improved focal-point centering within ThumbnailContent to prevent media stretching or awkward aspect-ratio cropping.
Fix Applied: Added objectPosition: 'center top' to: (1) media.css.ts → Image component (timeline images), (2) video thumbnail inline style in RenderMessageContent.tsx, (3) GalleryTile <img> in MediaGallery.tsx. Full-size viewers retain objectFit: 'contain' — no change. objectPosition: 'center top' prevents face/subject cropping on tall portrait images capped at 600px by AttachmentBox.
[ ] P5-15 · In-Call Soundboard
What: Grid of short audio clips playable into the call audio stream via Web Audio API (AudioBufferSourceNode → MediaStreamDestinationNode → mixed with mic). Built-in clips + user-uploadable custom clips (stored as mxc://). Accessible from call controls bar.
[AUDIT REQUIRED] Verify the Element Call integration exposes the mic MediaStream for mixing. This is the highest-risk part of this feature.
Complexity: High.
[~] P5-20 · Quick Reply from Browser Notification
What: Inline reply field in browser notification toasts via Notification Actions API. Reply sends as threaded reply to the triggering message.
[AUDIT REQUIRED] (1) Verify browser Notification Actions API support in target browsers. (2) Confirmed: service worker EXISTS at src/sw.ts — add notificationclick handler there.
Complexity: Medium-High.
Partial Fix Applied ⚠️ UNTESTED: Notifications now (a) show the real message body (username: message instead of "New inbox notification from..."), (b) click navigates directly to the room at the specific event (not the inbox), (c) window.focus() called on click so the tab comes to front, (d) reminder toasts also link to the specific event. Full inline-reply via Notification Actions API still needs the SW push+notificationclick pipeline (requires switching from new Notification() to showNotification() through the SW).
[x] P5-30 · Advanced ML Noise Suppression (Krisp-style)
What: High-end background noise cancellation using a pre-trained ML model (RNNoise) running in the browser. Removes dogs, fans, and keyboard clicks from the mic stream.
Shipped: 3-tier setting (Off / Browser-native / ML) in Settings → General → Calls. ML tier injects a same-origin pre-init shim into the vendored Element Call index.html that monkeypatches getUserMedia and routes the captured mic through an RNNoise AudioWorklet before LiveKit publishes — no EC fork required. See LOTUS_FEATURES.md → "Noise Suppression (Advanced Multi-Tier)".
Key decision: LiveKit's Krisp filter is LiveKit-Cloud-only (we self-host the SFU); EC's own RNNoise PR #3892 is unmerged. The shim is the same post-capture pipeline #3892 uses, executed from the realm we control, so it survives EC version bumps.
AEC note (resolved-as-accepted): WebAudio capture routing can weaken browser AEC — same tradeoff as EC's upstream feature; mitigated by keeping echoCancellation/autoGainControl on the raw capture and labeling the tier "beta".
Model Roadmap (priority order):
- Verify DTLN (16 kHz narrowband fix) in a real call before investing further — wired but unverified.
- DeepFilterNet 3 — best self-hostable upgrade: Rust→WASM, CPU real-time, 48 kHz fullband. Effort: self-host
df_bg.wasm+ DFN3 ONNX model, wire a 48 kHz worklet. - Desktop-only / HW-gated: FRCRN or NVIDIA Maxine (RTX/Tensor only) — impossible in-browser; would run in Tauri Rust backend + bridge a virtual mic into the webview. Must detect capability and only offer on supported hardware; web falls back to RNNoise.
- Excluded: Krisp (LiveKit Cloud only); FRCRN/Maxine on web (GPU/server-bound).
[ ] P5-31 · Granular Voice & Screenshare Quality Controls (Discord-style)
What: Let users (or room admins via room settings) adjust audio bitrates (e.g., 64kbps to 512kbps) and screenshare quality (resolution: 720p/1080p/Source, framerate: 15/30/60fps).
Note: Requires tight integration with the LiveKit SFU and custom state events for per-room quality caps.
[AUDIT REQUIRED] Must verify if current lk-jwt-service can be extended with custom bitrate/resolution claims or if a new sidecar (similar to voice-limit-guard) is needed for server-side enforcement.
Complexity: Extreme.
[ ] P5-35 · Desktop — Notification Click Opens Room (DEFERRED)
What: Clicking a system tray notification navigates to the relevant room. Quick-reply from the notification toast would send the reply without opening the window.
Status: Deferred — tauri-plugin-notification has no Rust click/action callback API. Quick-reply would need a custom WinRT toast activator + COM registration, which can't be compile-tested without a Windows build environment.
Note: Tray icon and matrix: deep links already bring the window forward on most interactions. Revisit when tauri-plugin-notification gains click handler support upstream.
Complexity: High (platform-specific native code required).
[ ] P5-36 · Desktop — Windows Jump List (DEFERRED)
What: Right-clicking the taskbar icon shows a jump list with recent/favorite rooms for quick navigation.
Status: Deferred — implementing the Windows COM jump list API in Tauri requires iterating on C++/COM code that can only be compile-checked on Windows, making blind CI iteration impractical.
Action when unblocked: Revisit when a Tauri plugin abstracts the Windows Shell ICustomDestinationList interface, or when a Windows build environment is available for local iteration.
Complexity: High (Windows-only native COM).
[x] P5-40 · Desktop — Proactive Update Notifications (Tauri) ⚠️ UNTESTED (requires Tauri build)
What: Automatically check for app updates on launch and periodically during long sessions. If an update is available, show an in-app toast or badge (e.g., on the Settings icon) to alert the user without requiring a manual check in settings.
Mechanism: Use the useTauriUpdater hook in a global component like ClientNonUIFeatures.tsx.
Note: Ensure the check is throttled (e.g., once every 12 hours) to avoid redundant Tauri commands.
Complexity: Low-Medium.
[ ] P5-41 · Desktop — Native WinRT Toast Notifications
What: Replace emulated notifications with native WinRT Toast notifications.
Approach: Implement native WinRT Toast integration using windows-rs to enable full Action Center integration, including native Quick Reply functionality.
[ ] P5-42 · Desktop — Persistent Background Sync
What: Maintain light connection to homeserver when WebView2 is suspended.
Approach: Implement a headless Rust sidecar to fetch unread counts/notifications while the webview is suspended to ensure instant notification delivery.
[ ] P5-43 · Desktop — System Media Transport Controls (SMTC)
What: Integrate with Windows SMTC for volume flyout call/media control.
Approach: Use Windows SMTC API to expose call status, mic mute/unmute, and media controls to the Windows volume flyout/media overlay.
[ ] P5-44 · Desktop — Taskbar Thumbnail Toolbar
What: Add persistent call controls to the taskbar preview.
Approach: Implement a COM thumbnail toolbar in the application preview window, featuring Mute/Deafen/End Call buttons.
[ ] P5-46 · Desktop — System Power Management (Call Continuity)
What: Prevent system sleep/hibernate during active calls.
Approach: Use Tauri/Rust power-manager or platform-specific APIs to block system power saving states while a voice/video session is active.
[ ] P5-47 · Desktop — TDS-Styled Native Window Chrome
What: Replace system titlebar with custom Lotus TDS chrome.
Approach: Configure Tauri window (decorations: false) and implement custom, TDS-token compliant titlebar controls (Close/Max/Min) for a cohesive UI.
[ ] P5-48 · Desktop — Native File System Drag-and-Drop Improvements
What: Enhance drag-and-drop support for Windows.
Approach: Improve handling for Windows file shortcuts, recursive folder uploads, and shell-integrated "Send To" context menu actions.
[ ] P5-49 · Desktop — Network Awareness (NCSI Integration)
What: Proactively detect Windows network connectivity changes.
Approach: Integrate with the Windows Network Connectivity Status Indicator (NCSI) API to improve offline mode transition latency and network recovery.
[ ] P5-50 · Desktop — Windows Hardware-Accelerated Media Pipeline
What: Replace standard browser decoding with native Windows Media Foundation.
Approach: Leverage DirectShow/Media Foundation to offload video/audio decoding from the CPU to the GPU, significantly reducing power consumption and latency during calls.
[ ] P5-51 · Desktop — Federated "Identity Contexts" (Isolation Manager)
What: Compartmentalize sessions, local databases, and caches into isolated "Contexts."
Approach: Implement a zero-leak boundary for personas (e.g., Work vs. Personal) by isolating IndexedDB, filesystem caches, and session persistence per context.
Priority: Extreme Low (Multi-sprint/Architectural).
[~] P5-52 · Desktop — Room-Level Sync Governor (Performance Control) [STILL_CONSIDERING]
What: Granular sync tuning for individual rooms.
Approach: Allow per-room overrides for sync frequency and event type filtering (e.g., disable read receipts/typing in heavy rooms) to optimize performance. Implementation requires careful UX to prevent complexity fatigue.
[ ] P5-53 · Desktop — Local-Only "Scripting" Plugin System (Tampermonkey-like)
What: A sandboxed environment for local execution of user scripts on Matrix events.
Approach: Implement a WASM-based execution engine that allows users to write local-only, client-side scripts to interact with incoming Matrix events, trigger sounds/notifications, or inject custom UI elements based on event payload rules. Designed for privacy — all logic runs exclusively on the local machine.
[ ] P5-55 · Desktop — Composer Toolbar Drag-and-Drop Reordering
What: Allow users to reorder toolbar icons via drag-and-drop.
Approach: Extend the current settings-based toolbar toggle system to include a drag-and-drop UI mode in the composer settings, allowing users to personalize their icon order.
[ ] P5-56 · Desktop — Windows "Focus Assist" (DND) Sync
What: Automatically toggle notification state based on Windows Focus Assist.
Approach: Integrate with the Windows NotificationCenter / Focus state via Tauri/Rust to automatically enable/disable Lotus Chat's internal notification suppression mode when Windows Focus Assist is toggled.
[ ] P5-57 · Desktop — Visual Draft Persistence Indicator
🚀 Features to Add
- Mobile Audit: Comprehensive audit of all features in LOTUS_FEATURES.md for mobile PWA usability and layout responsiveness.
- Remind Me Later: Slack-style reminders for messages — fully implemented ⚠️ UNTESTED end-to-end
- Storage:
useReminders.ts— persists toio.lotus.remindersaccount data withaddReminder/removeReminder/getReminders - UI:
RemindMeDialog.tsx— 4 presets (20 min, 1 hr, 3 hr, tomorrow 9am); wired intoMessage.tsxcontext menu viaremindOpenstate;useModalStyle(320)for mobile fullscreen - Monitor:
ReminderMonitorinClientNonUIFeatures.tsx— polls every 30s + on tab visibility; fires Lotus toast when due and callsremoveReminder
- Storage:
- Mobile Bookmarks: Fixed ⚠️ UNTESTED — bookmarks now accessible from within any room on mobile
- Root Cause:
BookmarksPanelrenders correctly on mobile butBookmarksTablives inSidebarNav, which is hidden when inside a room on mobile (MobileFriendlyClientNavreturnsnull). No trigger existed. - Fix: Added "Saved Messages"
MenuItemto theRoomMenu(···More Options) inRoomViewHeader.tsx. TogglesbookmarksPanelAtomand closes the menu. Works on all screen sizes — desktop users see it as a duplicate of the sidebar star, mobile users now have their only in-room access point.
- Root Cause:
Blocked Features
These features are confirmed desirable but cannot be built until the listed dependency is resolved.
Check back after each Synapse upgrade — re-run /matrix/client/versions and unstable_features to see if they've become available.
[BLOCKED] · Live Location Sharing (MSC3489 + MSC3672)
Blocked by: org.matrix.msc3489 = false AND org.matrix.msc3672 = false on matrix.lotusguild.org (confirmed from unstable_features).
What it would do: Real-time GPS beacon streaming upgrading the existing static location share.
Action when unblocked: Both MSCs must be enabled on the homeserver before any client work.
[BLOCKED] · Reaction / Relation Redaction (MSC3892)
Blocked by: org.matrix.msc3892 = false on matrix.lotusguild.org
What it would do: Cleanly remove a reaction without redacting the parent message.
Current behavior: Full event redaction — acceptable fallback, no user-facing issue.
Action when unblocked: Find onReactionToggle redaction call site; swap in MSC3892 endpoint with fallback.
[BLOCKED] · Room Preview Before Joining (MSC3266)
Blocked by: GET /_matrix/client/v1/rooms/{roomId}/summary returns M_UNRECOGNIZED 404 — endpoint not implemented in Synapse 1.155. Config flag msc3266_enabled: true is set but has no effect; Synapse appears not to have shipped a stable implementation at the v1 path. Verified 2026-06-18.
What it would do: Show room name, topic, avatar, member count before joining.
Action when unblocked: Re-test after each future Synapse upgrade.
[BLOCKED] · Thread Subscriptions (MSC4306)
Blocked by: org.matrix.msc4306 = false on matrix.lotusguild.org
What it would do: Follow a thread without posting; get notifications for replies.
Action when unblocked: Add "Follow thread" button in the thread panel header (depends on #P3-8 Thread Panel).
[DONE] · Report User (MSC4260) ✅
Previously blocked by: Server spec v1.12, but POST /_matrix/client/v3/users/{userId}/report was confirmed 200 on 2026-06-18 (live since Synapse 1.133.0).
What it does: Reports a specific user to homeserver admins (separate from reporting a message).
Note: Report Message already exists in upstream Cinny. This adds Report User to the profile panel.
Implemented 2026-06-18: ReportUserModal.tsx added at src/app/features/room/ReportUserModal.tsx. Button wired into UserRoomProfile.tsx between UserModeration and UserDeviceSessions (hidden for own profile). Category dropdown + reason text, inline success/error feedback, auto-close 1500ms after success.
Pending Audits
[ ] Audit-3 · Profile banner image — Matrix protocol support
Research whether Matrix spec or MSC4133 (v1.16) defines a standard profile banner field. uk.tcpip.msc4133.stable = true on our server — check if a banner_url or similar field is defined. If no cross-client standard exists, do not implement.
📚 Implementation Reference
Exhaustive, low-level implementation details for backlog items. Follow these patterns to ensure code is "Lotus-perfect" (idiomatic, performant, and TDS-compliant).
P3-8 · Thread Panel (Full Side Drawer)
Architecture: Mirror the MembersDrawer pattern but with a specialized timeline.
- State (
src/app/state/room/thread.ts):export const activeThreadIdAtom = atom<string | null>(null); - Layout (
src/app/features/room/Room.tsx): InsertThreadPanelconditionally alongsideRoomTimeline:{ activeThreadId && ( <> <Line variant="Background" direction="Vertical" size="300" /> <ThreadPanel roomId={roomId} threadId={activeThreadId} /> </> ); } - Component (
src/app/features/room/thread/ThreadPanel.tsx): Useroom.getThread(threadId)from the SDK. Render aHeaderwith a "Close" button that setsactiveThreadIdAtomtonull. ReuseRoomTimelinebut pass a filteredEventTimelineSet. Usethread.timelineSetdirectly for the most accurate thread view.
P4-4 · Math / LaTeX Rendering
Mechanism: KaTeX injection into the HTML parser.
- Sanitizer (
src/app/utils/sanitize.ts): Allow KaTeX-specific tags and classes (e.g.,span,annotation,math). Use a specialized allowed list for math blocks. - Parser (
src/app/plugins/react-custom-html-parser.tsx): Detect$ ... $and$$ ... $$patterns in text nodes:if (node.type === 'text') { const parts = node.data.split(/(\$\$.*?\$\$|\$.*?\$)/g); return parts.map((p) => { if (p.startsWith('$')) return <KaTeX math={p.replace(/\$/g, '')} />; return p; }); } - CSS (
src/app/styles/CustomHtml.css.ts): Importkatex/dist/katex.min.cssonly when a math block is rendered to save initial bundle size.
P4-6 · OIDC / SSO Next-Gen Auth (MSC3861)
Mechanism: Matrix Authentication Service (MAS) Integration.
- Architecture: Shift from password-based
/loginto OAuth2authorization_codeflow. - Key Files:
src/app/pages/auth/Login.tsxandsrc/app/hooks/useAuth.ts. - Implementation: Use
oidc-client-tsor a similar lightweight OIDC library. Check form.authenticationin/.well-known/matrix/client. Redirect to the MAS authorization endpoint. Handle the callback in a newOidcCallbackroute and store the OIDCrefresh_token.
P5-1 · Custom Accent Color Picker (Non-TDS only)
Mechanism: Dynamic CSS variable injection.
- Setting (
src/app/state/settings.ts): AddcustomAccentColor: string(hex). - Manager (
src/app/pages/ThemeManager.tsx): Inside theuseEffectthat monitors theme changes:if (!lotusTerminal && customAccentColor) { document.documentElement.style.setProperty('--lt-accent-orange', customAccentColor); document.documentElement.style.setProperty('--lt-accent-orange-glow', `${customAccentColor}80`); } - UI (
src/app/features/settings/general/General.tsx): Use<Input type="color">. Hide this section iflotusTerminalistrue.
P5-15 · In-Call Soundboard
Mechanism: Local-to-Global Audio Bridge via Web Audio API.
- Create an
AudioContextand aMediaStreamDestinationNode. - Create an
AudioBufferSourceNodefor each clip. - Route the mic
MediaStreamand the clip source to the destination node. - Pass the destination's
.streamto the call bridge.
P5-20 · Quick Reply from Browser Notification
Mechanism: Service Worker notificationclick Action.
// src/sw.ts
self.addEventListener('notificationclick', (event) => {
if (event.action === 'reply' && event.reply) {
const { roomId, threadId } = event.notification.data;
const session = sessions.get(event.clientId);
fetch(`${session.baseUrl}/_matrix/client/v3/rooms/${roomId}/send/m.room.message`, {
method: 'POST',
headers: { Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify({
msgtype: 'm.text',
body: event.reply,
'm.relates_to': threadId ? { rel_type: 'm.thread', event_id: threadId } : undefined,
}),
});
}
});
P5-30 · Advanced ML Noise Suppression — Model Roadmap
See shipped implementation in LOTUS_FEATURES.md → "Noise Suppression (Advanced Multi-Tier)".
Models status:
- RNNoise (sapphi, 48 kHz) — ✅ working, default fallback. Keep — runs on any hardware.
- Speex (sapphi, 48 kHz) — ✅ working, low value; candidate to drop.
- DTLN (@workadventure, 16 kHz) — 🟡 wired; sample-rate fix applied (was robotic at 48 kHz). TODO: verify in a real call. Narrowband (16 kHz) = slightly telephone-y even when correct.
Constraints: client-side AudioWorklet, fully self-hosted, no GPU, self-hosted SFU (no LiveKit Cloud).
Roadmap:
- Verify DTLN 16 kHz fix in a real call.
- DeepFilterNet 3 — best self-hostable upgrade: Rust→WASM, CPU real-time, 48 kHz fullband. Self-host
df_bg.wasm+ DFN3 ONNX model; wire a 48 kHz worklet. Audio quality unverifiable without a real-call test. - Desktop-only / HW-gated: FRCRN (Alibaba) or NVIDIA Maxine (RTX/Tensor only). Runs in Tauri Rust backend + bridges a virtual mic into the webview. Must detect capability; web + weak HW falls back to RNNoise/DTLN.
P5-31 · Granular Voice & Screenshare Quality Controls
Mechanism: WebRTC Encoding Parameters + Backend Quality Guard.
- State Event:
io.lotus.room_quality(state key"") containing:{ "audio_bitrate": 128000, "screen_max_res": "1080p", "screen_max_fps": 60 } - Screenshare: In
src/app/plugins/call/CallControl.ts, map the "Quality" setting togetDisplayMediaconstraints. - Audio Bitrate: After the call joins, find the
RTCRtpSenderfor the audio track:const sender = peerConnection.getSenders().find((s) => s.track?.kind === 'audio'); const params = sender.getParameters(); params.encodings[0].maxBitrate = roomBitrate || 128000; await sender.setParameters(params); - Backend Sidecar: Extend
voice-limit-guard.py(LXC 151) to fetchio.lotus.room_qualityand inject limits into the LiveKit JWT or return them as an authorized config packet.
P5-40 · Desktop — Proactive Update Notifications (Tauri)
Key Files: src/app/hooks/useTauriUpdater.ts, src/app/pages/client/ClientNonUIFeatures.tsx, src/app/features/toast/LotusToastContainer.tsx.
- Create a
TauriUpdateFeaturecomponent. UseuseTauriUpdater()to get thecheckfunction andstatus. - In a
useEffect, callcheck()on mount and then on asetInterval(every 12 hours). - When status transitions to
{ state: 'available', version: '...' }, fire a Lotus Toast: "Lotus Chat v[version] is available!" with an "Update" button that callsinstall(). - Store
lastChecktimestamp inlocalStorageto prevent redundant checks on refresh.
Mobile Bookmarks Visibility Fix
Issue: ClientLayout.tsx explicitly restricts BookmarksPanel to ScreenSize.Desktop (lines 51-56).
// ClientLayout.tsx
{
bookmarksOpen && (
<BookmarksPanel
onClose={() => setBookmarksOpen(false)}
isMobile={screenSize !== ScreenSize.Desktop}
/>
);
}
BookmarksPanel.tsx already supports the isMobile prop (line 127) to enable full-screen absolute positioning. No other changes required.
Remind Me Later (Slack-style)
Mechanism: Account Data + Timer/Service Worker.
- Storage (
src/app/hooks/useReminders.ts): Store in account dataio.lotus.remindersasArray<{ id: string, roomId: string, eventId: string, timestamp: number }>. - Context Menu (
src/app/features/room/message/MessageContextMenu.tsx): Add "Remind me" option → opens date/time picker modal (reuseJumpToTime.tsxlogic). - Trigger (foreground):
setTimeoutin a hook insideReminderMonitorinClientNonUIFeatures.tsx→ pushes totoastQueueAtominstate/toast.tswhen due. - Trigger (background): Use Service Worker —
setTimeoutin the main thread will not fire when the PWA is suspended.
Mobile Usability Audit — Methodology
- Viewport & Touch: All interactive elements must have at least
44px × 44pxtouch targets. Audit for horizontal overflow (horizontal scrolling must be disabled). - Modal Responsiveness: All modals (Settings, Profile, etc.) MUST cover the full screen on mobile, not float as overlays.
- Sidebar / Panels: On mobile, sidebar panels (Members, Bookmarks, Media) must become full-screen overlays (using a
DrawerorModalpattern) rather than side-by-side flexbox panels. - Input & Composer: Ensure the composer doesn't get obscured by the mobile keyboard. Test focus trap and blur behaviors.
Implementation Notes
⚠️ TDS DESIGN LAW (repeated here for emphasis)
Every TDS color, animation, glow, border, shadow, and font value MUST come from
/root/code/web_template/base.css.
Never hardcode hex values. Never invent CSS variable names.
Key variables:--lt-accent-orange·--lt-accent-cyan·--lt-accent-green·--lt-glow-*·--lt-box-glow-*·--lt-border-color·--lt-font-mono
Reference implementation:/root/code/tinker_tickets/(markdown.js, base.js, ticket.css)
This applies without exception to every task marked[IMPROVE],[Build], or any UI change.
Design Rules
- All new components must respect both TDS dark (
LotusTerminalTheme) and TDS light (LotusTerminalLightTheme) modes - Non-TDS theme work (custom accent color, theme presets) uses vanilla-extract theme files — match the pattern in
src/lotus-terminal.css.ts - Code syntax highlighting token classes:
.tok-kw .tok-str .tok-num .tok-cmt .tok-fn(defined inweb_template/base.css) folds AvatarImagedoes NOT accept children — wrap Avatar components externally for overlays/frames/borders
CI/CD Pipeline
edit → commit → git push origin lotus
→ Gitea Actions: tsc --noEmit, eslint, prettier (~3 min)
→ Webhook: lotus_deploy.sh on LXC 106 polls CI, then npm ci && npm run build → rsync
→ Live at chat.lotusguild.org (~11 min total)
Per-Feature Checklist (before marking complete)
npx tsc --noEmit— zero TypeScript errorsnpx eslint src/— zero new errors (warnings OK if pre-existing)npx prettier --check src/— formatting passesREADME.mdupdated (Lotus-custom features only — not upstream Cinny features)landing/index.htmlupdated if the feature appears in the comparison table- Visually tested at
chat.lotusguild.orgafter CI deploys
Homeserver Access (for server audits)
- Synapse (Matrix): LXC 151 on
compute-storage-01—pct exec 151 -- bash - Config:
/etc/matrix-synapse/homeserver.yaml - Version check:
curl -s https://matrix.lotusguild.org/_matrix/client/versions