ESLint errors: - usePresenceUpdater: remove redundant `const userId` inside handlePageHide that shadowed the outer declaration (no-shadow) - RoomViewHeader: prefix unused encryptedRoom with _ (no-unused-vars) Prettier: reformat 14 files to match project style Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
29 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)
⚠️ 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.153.0(2026-05-19) — fully up to date - 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 |
All safe to use now |
MSC flags OFF: msc4306 (thread subscriptions) · msc3882 · msc3912 · msc4155 |
These features are BLOCKED |
| MSC3266 room summary: returns 404 | Room Preview feature BLOCKED |
| MSC3892 relation redaction: not in flags | Reaction Redaction feature BLOCKED |
| MSC4260 report user: server at v1.12, endpoint may not exist | Report User feature BLOCKED |
| 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 |
Known Bugs
[!] BUG · Drag-and-drop file overlay doesn't dismiss on hover-away
Confirmed bug — drag a file over the window without dropping: the drop overlay persists.
Fix: Ensure dragleave fires correctly at the window/document level. Child element boundaries can cause spurious dragleave — use a counter or relatedTarget check.
[AUDIT REQUIRED] Find the drag-and-drop overlay component in RoomInput.tsx or the room view. Confirm the exact event listener structure.
Complexity: Low (bug fix).
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-coreor browser DevTools accessibility tree. Document every violation before writing a single line of code. Prioritize by severity (critical > serious > moderate).
Complexity: Medium-High (audit is the main work).
[x] P3-6 · Configurable Composer Toolbar
What: Let users rearrange or hide individual composer toolbar buttons (GIF, Sticker, Emoji, File, Voice, Location). Changes stored in settingsAtom. Access via a small "⚙ Customize toolbar" option in toolbar overflow.
[AUDIT REQUIRED] — Audit the current toolbar button rendering in RoomInput.tsx. Understand the layout system (is it a fixed array or already mapped from config?). Drag-to-reorder may require a DnD library; consider whether reorder is worth the complexity vs just toggle-visibility.
Complexity: Medium-High (drag reorder adds significant complexity).
[ ] 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 howm.threadrelation events are currently stored and retrieved in the matrix-js-sdk. Understand the thread aggregation API:GET /rooms/{roomId}/relations/{eventId}/m.thread. Check ifRoomTimeline.tsxcurrently filters out thread replies from the main timeline (it should — confirm).
Complexity: High.
Priority 4 — Specialized, high complexity, or low priority
[ ] 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-3 · Knock-to-join Notifications for Admins
Note: The basic knock-to-join UX is covered in P1-11 (completed). This task adds the admin notification side.
What: Space/room admins see a notification badge when there are pending knock requests. A "Pending Join Requests" section in the members drawer or room settings. Approve (invite) or deny (kick) each knock.
Complexity: Medium.
[ ] 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.
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.
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] Studysrc/lotus-terminal.css.tsfor the full token list before designing themes. All tokens must be covered.
Complexity: Medium (design effort is the main cost).
[ ] P5-9 · LFG (Looking for Group) Slash Command
What: /lfg generates a formatted LFG post visible on ALL Matrix clients using standard m.room.message HTML. Fields: Game, Players Needed, Platform, Skill Level, Description, DM link. Other clients see clean formatted HTML; Lotus Chat renders an enhanced styled card.
[AUDIT REQUIRED] Test which HTML tags survive Matrix HTML sanitization on Element/FluffyChat before designing the card structure. Test with minimal HTML.
Complexity: Medium.
[ ] P5-10 · Voice Channel User Limit
What: Admins set max participants via custom state event io.lotus.voice_limit: { max_users: N }. Show "Channel Full (5/5)" to users over the limit. Local enforcement only.
[AUDIT REQUIRED] Check if Element Call has its own participant limit that should be integrated with rather than duplicated.
Complexity: Medium.
[ ] P5-11 · AFK / Idle Auto-Mute in Voice
What: Auto-mute mic after X minutes of silence (detected via Web Audio AnalyserNode). Show "You were auto-muted due to inactivity" toast with click-to-unmute. Admin-configurable via io.lotus.afk_timeout state event. Disableable in Settings → Calls.
[AUDIT REQUIRED] Verify auto-mute must go through the same CallControl bridge as manual mute.
Complexity: Medium.
[ ] P5-12 · Seasonal / Event Themes
What: Automatic + manually toggleable seasonal overlays with CSS particle effects and accent color variants:
- Halloween (Oct 15–Nov 1): purple particles, orange accents, spider web pattern
- Christmas (Dec 10–Jan 2): snow fall, red/green accents, snowflake pattern
- New Year (Dec 31–Jan 1): firework burst animation, gold accents
- Pride (June): rainbow gradient accent cycle
All toggleable manually in Settings → Appearance regardless of date. Respectsprefers-reduced-motion.
[AUDIT REQUIRED] Design against existing CSS animation system inlotus-terminal.css.ts.
Complexity: Medium.
[ ] P5-13 · Avatar Frame / Border Decorations
What: Decorative CSS rings/frames rendered around user avatars. Built-in options: TDS Glow (animated orange pulsing), Cyberpunk (rotating gradient), Minimal (thin ring), Gold (supporter cosmetic). Stored in Matrix account data io.lotus.avatar_frame. Only visible in Lotus Chat.
[AUDIT REQUIRED] Verify folds Avatar component allows overlay decoration without breaking child-type constraints (see previous white-circle avatar bug).
Complexity: Medium.
[ ] P5-14 · Animated Avatar Overlay Decorations (Discord-style)
What: Animated WebM/GIF overlays that float around avatars (transparent center showing avatar). Curated built-in set OR user-uploaded mxc:// overlay. Stored in account data. Only Lotus Chat users see them.
[AUDIT REQUIRED] See #P5-13 audit. Also decide: curated set only vs user-uploadable.
Complexity: Medium.
[ ] 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-16 · Custom Join / Leave Sound Effects
What: Local-only sounds when participants join/leave a call you're in. Built-in options + per-user settable. Detect via Element Call participant list change events.
[AUDIT REQUIRED] Find how Element Call exposes join/leave participant events to the parent window via postMessage bridge.
Complexity: Medium.
[ ] 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) This requires a Service Worker to handle the reply event — confirm if Lotus Chat has one or needs one.
Complexity: Medium-High.
[x] P5-21 · Custom @Mention Highlight Color
What: Each user sets their own mention highlight color in Settings → Appearance. Applied as --user-mention-color CSS property override on mention-highlighted message rows.
Complexity: Low.
[x] P5-22 · Font Selector for the UI
What: Font picker in Settings → Appearance. Options: JetBrains Mono, Inter, Geist, Fira Code, OpenDyslexic, System Default. Applied via CSS custom property overrides.
[AUDIT REQUIRED] Check if any fonts are already globally loaded to avoid double-loading.
Complexity: Low-Medium.
[x] P5-27 · Notification Profile Presets (Gaming / Work / Sleep)
What: Saved presets that change all notification settings atomically. Gaming (mentions only), Work (DMs + mentions), Sleep (all off). Quick-switch from sidebar or settings.
Complexity: Medium.
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 /v1/rooms/{id}/summary returns 404 — endpoint not available on this server
What it would do: Show room name, topic, avatar, member count before joining.
Action when unblocked: Build pre-join preview card; trigger on unjoined room navigation.
[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).
[BLOCKED] · Report User (MSC4260)
Blocked by: Server declares only spec v1.12; MSC4260 merged in v1.14 — endpoint may not exist
What it would do: Report a specific user to homeserver admins (separate from reporting a message).
Note: Report Message already exists in upstream Cinny. This would add Report User to the profile panel.
Action when unblocked: Test POST /_matrix/client/v3/users/{userId}/report; if 200, add button to user profile.
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 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