Files
cinny/LOTUS_TODO_REFERENCE.md
T
jared 6db07f1371 feat: seasonal theme overlays + improved animated chat backgrounds
Adds 11 CSS-only seasonal overlays (Halloween, Christmas, New Year, Autumn,
April Fool's, Lunar New Year, Valentine's Day, St. Patrick's Day, Earth Day,
Deep Space, Retro Arcade) with date-based auto-detection and a manual override
dropdown in Settings → Appearance → Seasonal Theme. All themes respect
prefers-reduced-motion. SeasonalEffect mounts at z-index 9997 in App.tsx.

Also rewrites all 5 animated chat background keyframes for smoother, more
organic motion: Digital Rain gains a phosphor glow flicker; Star Drift now
loops each layer by exactly its own tile size (no more seam); Grid Pulse adds
an independent brightness oscillation at a prime period; Aurora Flow drives
all four gradient layers through distinct paths; Fireflies adds glow-pulse and
opacity-blink animations at prime periods for unsynchronised bioluminescence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 00:33:04 -04:00

11 KiB

Lotus Chat — Technical Implementation Field Guide

Date: June 2026

This document provides exhaustive, low-level implementation details for the remaining items in LOTUS_TODO.md. Follow these patterns to ensure code is "Lotus-perfect" (idiomatic, performant, and TDS-compliant).


🧵 Priority 3 — Higher Complexity

P3-8 · Thread Panel (Full Side Drawer)

Architecture: Mirror the MembersDrawer pattern but with a specialized timeline.

  • 1. State (src/app/state/room/thread.ts):
    export const activeThreadIdAtom = atom<string | null>(null);
    
  • 2. Layout (src/app/features/room/Room.tsx): Insert the ThreadPanel conditionally alongside the RoomTimeline.
    {activeThreadId && (
      <>
        <Line variant="Background" direction="Vertical" size="300" />
        <ThreadPanel roomId={roomId} threadId={activeThreadId} />
      </>
    )}
    
  • 3. Component (src/app/features/room/thread/ThreadPanel.tsx):
    • Use room.getThread(threadId) from the SDK.
    • Render a Header with a "Close" button that sets activeThreadIdAtom to null.
    • Reuse RoomTimeline but pass a filtered EventTimelineSet.
    • Pro Tip: Use thread.timelineSet directly for the most accurate thread view.

🎨 Priority 5 — Gamer / Aesthetic / Customization

P5-1 · Custom Accent Color Picker (Non-TDS only)

Mechanism: Dynamic CSS variable injection.

  • 1. Setting (src/app/state/settings.ts): Add customAccentColor: string (hex).
  • 2. Manager (src/app/pages/ThemeManager.tsx): Inside the useEffect that monitors theme changes:
    if (!lotusTerminal && customAccentColor) {
      document.documentElement.style.setProperty('--lt-accent-orange', customAccentColor);
      // Also derive a 'glow' version (e.g. 50% opacity)
      document.documentElement.style.setProperty('--lt-accent-orange-glow', `${customAccentColor}80`);
    }
    
  • 3. UI (src/app/features/settings/general/General.tsx): Use a <Input type="color"> component. Hide this section if lotusTerminal is true.

P5-14 · Animated Avatar Overlay

Mechanism: CSS Pseudo-element wrapping.

  • 1. Wrapper (src/app/components/user-avatar/UserAvatar.tsx):
    const avatar = (
      <AvatarImage className={classNames(css.UserAvatar, className)} ... />
    );
    if (!frame) return avatar;
    return (
      <div className={css.AvatarFrameContainer} data-frame={frame}>
        {avatar}
        <div className={css.AvatarFrameEffect} />
      </div>
    );
    
  • 2. CSS (src/app/components/user-avatar/UserAvatar.css.ts): Define animations like rotate or pulse. Use position: absolute; inset: -4px on the effect div to create a glowing ring around the avatar.

🛠️ Priority 4 — Specialized Features

P4-4 · Math / LaTeX Rendering

Mechanism: KaTeX injection into the HTML parser.

  • 1. Sanitizer (src/app/utils/sanitize.ts): You must allow KaTeX-specific tags and classes (e.g., span, annotation, math). Use a specialized allowed list for math blocks.
  • 2. 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;
      });
    }
    
  • 3. CSS (src/app/styles/CustomHtml.css.ts): Import katex/dist/katex.min.css only 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 /login to OAuth2 authorization_code flow.
  • Key Files: src/app/pages/auth/Login.tsx and src/app/hooks/useAuth.ts.
  • Implementation:
    1. Use oidc-client-ts or a similar lightweight OIDC library.
    2. Check for m.authentication in /.well-known/matrix/client.
    3. Redirect to the MAS authorization endpoint.
    4. Handle the callback in a new OidcCallback route and store the OIDC refresh_token.

🎨 Priority 5 — Gamer / Aesthetic / Customization

P5-12 · Seasonal / Event Themes

Mechanism: Date-aware global overlays.

  • 1. Component (src/app/components/seasonal/SeasonalEffect.tsx):
    const isHalloween = now.month === 9 && now.day >= 15; // etc.
    if (!isHalloween) return null;
    return <div className={css.HalloweenSpiderWeb} />;
    
  • 2. Styles (src/app/styles/Animations.css.ts): Use repeating-linear-gradient for spider webs or keyframes for falling snow.
  • 3. Mounting: Place at the very end of App.tsx (above the LotusToastContainer) with pointer-events: none.

P5-13 · Avatar Frame / Border Decorations

Mechanism: Static variant of P5-14.

  • Implementation: Use a simple border or box-shadow in UserAvatar.tsx based on the io.lotus.avatar_frame account data. Unlike the animated version, this should avoid keyframes to save CPU on long member lists.

P5-2 · Additional Color Theme Presets

Mechanism: Standard vanilla-extract theme multiplication.

  • Implementation: Replicate the lotusTerminalTheme object in src/colors.css.ts for each new theme (Cyberpunk, Ocean, etc.). Ensure each theme defines all 50+ tokens required by the folds library and TDS.

P5-15 · In-Call Soundboard

Mechanism: Local-to-Global Audio Bridge.

  • Architecture: Use the Web Audio API to mix sounds into the MediaStream before it enters the Element Call widget.
  • Implementation:
    1. Create an AudioContext.
    2. Create a MediaStreamDestinationNode.
    3. Create an AudioBufferSourceNode for the clip.
    4. Route the mic MediaStream and the clip source to the destination.
    5. Pass the destination's .stream to the call bridge.

P5-20 · Quick Reply from Browser Notification

Mechanism: Service Worker notificationclick Action.

  • 1. Registration (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); // Uses existing session mapping
        // Send via direct fetch to bypass SDK loading
        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
          })
        });
      }
    });
    

🔬 Extreme Complexity Projects

P5-30 · Advanced ML Noise Suppression (Krisp-style)

Mechanism: RNNoise WASM + Web Audio Worklet Pipeline.

  • Objective: Filter non-vocal noise from the microphone stream in real-time.
  • Architecture:
    1. Engine: Use RNNoise (Recurrent Neural Network for noise suppression). It is lightweight and highly effective for speech.
    2. Pipeline: Mic Stream -> AudioWorkletNode (Processing) -> MediaStreamDestination -> Element Call.
  • Implementation Steps:
    1. WASM Wrapper: Compile the RNNoise C library to WebAssembly. Use a library like rnnoise-wasm or noise-suppression-js.
    2. Audio Worklet: Create src/app/utils/audio/RnnoiseWorklet.ts. This must handle 480-sample chunks (10ms of audio at 48kHz), which is the standard frame size for RNNoise.
    3. Client Integration:
      • In CallControl.ts, intercept the localStream.
      • Pass the stream through the Worklet.
      • Crucially, you must ensure that the processed stream is used by the RTCPeerConnection within the Element Call iframe.

P5-31 · Granular Voice & Screenshare Quality Controls

Mechanism: WebRTC Encoding Parameters + Backend Quality Guard.

  • Objective: Per-room and per-user control over audio fidelity and screenshare smoothness.
  • Architecture:
    1. State Event: io.lotus.room_quality (state key "") containing:
      {
        "audio_bitrate": 128000,
        "screen_max_res": "1080p",
        "screen_max_fps": 60
      }
      
    2. Client-Side (RoomInput / CallControl):
      • Screenshare: In src/app/plugins/call/CallControl.ts, when initiating screenshare, map the "Quality" setting to getDisplayMedia constraints:
      const constraints = {
        video: {
          width: { ideal: 1920 }, // 1080p
          frameRate: { ideal: 60 }
        }
      };
      
      • Audio Bitrate: After the call joins, find the RTCRtpSender for the audio track and update parameters:
      const sender = peerConnection.getSenders().find(s => s.track?.kind === 'audio');
      const params = sender.getParameters();
      params.encodings[0].maxBitrate = roomBitrate || 128000;
      await sender.setParameters(params);
      
    3. Backend Sidecar (The "Quality Guard"):
      • Pattern: Extend the voice-limit-guard.py (on LXC 151) to handle quality metadata.
      • Mechanism: When a user requests a LiveKit JWT to join a room, the Guard fetches the io.lotus.room_quality event for that room via the Synapse Admin API.
      • Enforcement: The Guard injects these limits into the LiveKit token claims (if supported) or simply returns them to the client as an authorized "config" packet that the client must respect.
  • Challenges:
    • LiveKit Compatibility: Ensuring the SFU doesn't over-compress a high-bitrate stream from a "Pro" user.
    • Network Stability: High bitrates (512kbps audio + 60fps 1080p video) require significant upstream bandwidth. Implement a "Network Warning" UI if packets are dropped.

P5-34 · User-to-User Private Notes

Mechanism: Encrypted account data map.

  • Objective: Private, cross-session notes about other users.
  • Key Files:
    • src/app/hooks/useUserNotes.ts: New hook for CRUD operations.
    • src/app/components/user-profile/UserRoomProfile.tsx: UI site.
  • Implementation:
    1. Store as a single map in io.lotus.user_notes account data: { "@alice:server": "Cool dev", "@bob:server": "Needs moderation" }.
    2. Wrap the entire map in E2EE if the client supports local account data encryption, or rely on standard Matrix account data privacy.
    3. Add a "Notes" tab or textarea to the user profile popout.