# Lotus Chat — Bug Report & Technical Audit
**Date:** June 2026

This document tracks identified bugs, edge cases, and architectural discrepancies found during the audit of the Lotus Chat codebase. Recommended fixes are provided for each item.

---

## 🛡️ Critical Security & Privacy Regressions

### 1. E2EE Bypass in Media Gallery Downloads
**File:** `src/app/features/room/MediaGallery.tsx` (Line 855)
**Status:** **CRITICAL**

*   **Issue:** The "Download" button in the Files tab uses `mxcUrlToHttp` directly and clicks an `<a>` link.
*   **Impact:** In encrypted rooms, this downloads the encrypted ciphertext rather than the decrypted file. Users cannot open the downloaded files.
*   **Recommended Fix:** 
    1. Check if the event is encrypted.
    2. If encrypted, use the `decryptAttachment` logic (similar to `useDecryptedMediaUrl`) to decrypt the file in memory.
    3. Use `file-saver` or a Blob URL to trigger the download of the decrypted plaintext.

### 2. Privacy Leak in URL Previews
**File:** `src/app/components/url-preview/UrlPreviewCard.tsx` (Line 1655)
**Status:** **PRIVACY RISK**

*   **Issue:** Generic URL preview cards fetch favicons directly from `https://www.google.com/s2/favicons?domain=...`.
*   **Impact:** This leaks the user's browsing/chat activity (domains of links they see) to Google. It bypasses the "proxied through Matrix" privacy standard.
*   **Recommended Fix:** Use the proxy URL returned by the Matrix `/_matrix/media/v3/preview_url` endpoint instead of contacting Google directly.

---

## 🚩 Functional & Logic Bugs

### 1. Presence Updater Reverts Status Updates
**File:** `src/app/hooks/usePresenceUpdater.ts` (Line 20)
**Status:** ✅ RESOLVED (June 2026)

*   **Issue:** The `storedStatus` variable was captured once when the `useEffect` started.
*   **Impact:** If a user updated their status message in Profile Settings, the hook would continue broadcasting the old message on every activity event, silently reverting the change.
*   **Fix Applied:** Replaced the single `localStorage.getItem` read with a `readStatus()` function called inside every `setOnline` and `setUnavailable` invocation, ensuring the current value is always used.

### 2. Audio Playback Rate Reset
**File:** `src/app/components/message/content/AudioContent.tsx` (Line 97)
**Status:** UX Bug

*   **Issue:** The `playbackRate` is set in a `useEffect` that only depends on `[playbackSpeed]`.
*   **Impact:** If a user selects a playback speed *before* the audio blob has finished loading, the `<audio>` element may reset its rate to 1.0 once the `<source>` is added.
*   **Recommended Fix:** Add `srcState.data` to the `useEffect` dependencies or set the `playbackRate` in an `onCanPlay` handler on the audio element.

### 3. Room Insights are Static (No Live Updates)
**File:** `src/app/features/room-settings/RoomInsights.tsx` (Line 60)
**Status:** Medium Priority

*   **Issue:** Stats are calculated in a `useMemo` that only depends on `[room]`.
*   **Impact:** If new messages arrive while the Insights page is open, the statistics (message counts, top participants, etc.) do not update.
*   **Recommended Fix:** Add a listener for `RoomEvent.Timeline` inside the component to trigger a recalculation when new events are added to the room.

### 4. Incorrect Ringing in Voice Rooms
**File:** `src/app/components/CallEmbedProvider.tsx`
**Status:** ✅ RESOLVED (June 2026)

*   **Issue:** Joining a static voice room (Public Space channel) triggered the "Incoming Call" ringing animation and sound.
*   **Root Cause:** `getStateEvent(room, StateEvent.SpaceParent)` always returned `undefined` because `m.space.parent` events use the parent space's room ID as the state key, not an empty string. The ringing suppression check silently failed.
*   **Fix Applied:** Replaced `getStateEvent` with `getStateEvents` (plural), which returns all events of a given type regardless of state key. A room with any `m.space.parent` event is correctly identified as a space channel and suppresses ringing.

---

## 🎨 UI/UX & Visual Consistency

### 1. Hardcoded Primary Color in Polls
**File:** `src/app/components/message/content/PollContent.tsx` (Line 245)
**Status:** TDS Violation

*   **Issue:** Uses `rgba(var(--mx-primary-rgb, 0,132,255), ...)` for selected options and borders.
*   **Impact:** Polls look like standard Cinny and ignore the Lotus Terminal Design System (TDS) colors.
*   **Recommended Fix:** Use `var(--lt-accent-cyan)` or the primary theme accent color.

### 2. Inaccessible Room Menu on Mobile
**File:** `src/app/features/room-nav/RoomNavItem.tsx` (Line 643)
**Status:** **MOBILE BUG**

*   **Issue:** The room menu icon (`VerticalDots`) is hidden on mobile because `hover` is never active.
*   **Impact:** Mobile users cannot access room settings, mark as read, or leave rooms from the sidebar.
*   **Recommended Fix:** Ensure the menu button is visible on mobile for the active room or provide an alternative trigger.

### 3. Inconsistent Settings Dropdown Styling
**File:** `src/app/features/settings/general/General.tsx`
**Status:** ✅ RESOLVED (June 2026)

*   **Issue:** The dropdowns for "Join & Leave Sounds", "UI Font", and "Seasonal Theme" used raw HTML `<select>` elements, which render differently from the custom-styled `Menu`+`PopOut` used for "Message Layout" and other settings.
*   **Fix Applied:** All three raw `<select>` elements replaced with a reusable `SettingsSelect` component using the Menu+PopOut+FocusTrap pattern consistent with the rest of the settings UI.

### 4. No Camera Focus During Screenshare
**File:** `src/app/features/call/CallControls.tsx`
**Status:** UX Bug — blocked by Element Call internals

*   **Issue:** When someone is screensharing and another participant turns on their camera, there is no way to switch the primary display to the camera or go fullscreen on it.
*   **Root Cause:** Element Call's spotlight/layout is controlled internally by EC. Lotus Chat injects the call iframe and cannot easily override EC's participant tile behavior without forking the EC widget.
*   **Recommended Fix:** Implement a "Pin/Focus" toggle on participant tiles that overrides the automatic screenshare spotlight — requires EC upstream changes or a custom message bridge.

### 5. Ringing Modal Fires in Persistent Voice Rooms
**File:** `src/app/components/CallEmbedProvider.tsx` (line 337–342)
**Status:** ✅ RESOLVED (June 2026)

*   **Issue:** Joining a persistent voice room (not a DM or transient group call) showed the incoming call ringing modal and animation.
*   **Root Cause:** The `isPrivateGroup` condition included `JoinRule.Restricted` and `JoinRule.Knock` rooms. Lotus Guild voice rooms are `Restricted` join-rule rooms. Their `m.space.parent` state event was being checked but some rooms were set up with only the space-side `m.space.child` relationship, leaving no `m.space.parent` on the room itself — so they passed as `isPrivateGroup` and triggered ringing.
*   **Fix Applied:** Narrowed `isPrivateGroup` to only `JoinRule.Invite` to match the exact set of rooms where the call button is shown. Also added `room.isCallRoom()` early-exit so rooms with `m.join_rule.call` type never ring.

### 6. Animated Chat Backgrounds Affect Message Content
**File:** `src/app/features/lotus/chatBackground.ts`
**Status:** ✅ RESOLVED (June 2026)

*   **Issue:** Animated backgrounds that use `filter: brightness()` or `opacity` animations (Digital Rain glow, Grid Pulse brightness, Fireflies glow/blink) applied those effects to the entire `<Page>` element, causing all message content and the composer to flash/flicker in sync with the animation.
*   **Root Cause:** `filter` and `opacity` CSS properties affect an element AND all its descendants. Applying these as part of the `animation` shorthand on the `Page` container made them "inherited" visually by everything inside the room view.
*   **Side Effect:** `filter` animation also created a CSS stacking context on Page, which pushed Seasonal Theme overlays (position:fixed; z-index:9997) behind the Page compositor layer.
*   **Fix Applied:** Removed `animRainGlowKeyframe`, `animGridBrightnessKeyframe`, `animFirefliesGlowKeyframe`, and `animFirefliesBlinkKeyframe` from `chatBackground.ts`. Only `backgroundPosition` / `backgroundSize` animations remain — these are safe and do not affect descendants or create stacking contexts.
*   **Follow-up (June 2026):** The initial fix only removed glow/brightness keyframes from the DARK variant definitions. The LIGHT variant `anim-rain` and `anim-pulse` entries still referenced `animRainGlowKeyframe` and `animGridBrightnessKeyframe` (which were no longer imported, causing a build error). Both references removed from LIGHT variants to complete the fix.

### 7. Seasonal Themes Display Behind Chat Background
**File:** `src/app/components/seasonal/SeasonalEffect.tsx`
**Status:** ✅ RESOLVED (June 2026) — root cause was Bug #6

*   **Issue:** Seasonal theme overlays (position:fixed; z-index:9997) appeared behind animated chat backgrounds.
*   **Root Cause:** The `filter` animation on `<Page>` created a CSS stacking context, causing Page's GPU compositing layer to render above the fixed-position seasonal overlay in some browsers. Removing the filter animations (Bug #6 fix) resolves the stacking context issue.
*   **Fix Applied:** See Bug #6. No additional changes to SeasonalEffect required.

### 8. Avatar Decoration Images Not Rendering in Settings
**File:** `src/app/features/settings/account/ProfileDecoration.tsx` / LXC 106 nginx
**Status:** ✅ RESOLVED (June 2026)

*   **Issue:** Under Settings → Account → Avatar Decoration, no decoration images were visible.
*   **Initial (incorrect) diagnosis:** Suspected `loading="lazy"` in a nested scroll container. Changed to `loading="eager"` — images still did not render.
*   **Actual Root Cause:** The server nginx Content Security Policy (`img-src` directive) on LXC 106 did not include `https://drive.lotusguild.org`. The browser silently blocked all 99 APNG requests with CSP violation errors (206 violations visible in DevTools console). The lazy-loading change was a red herring.
*   **Fix Applied:** Added `https://drive.lotusguild.org` to the `img-src` directive in `/etc/nginx/sites-available/cinny` on LXC 106 (cinny-web-server) and reloaded nginx. Updated config is tracked in `pve-infra/containers/106-cinny-web-server/etc/nginx/sites-available/cinny`.

### 9. Avatar Decoration Grid Spacing Too Tight
**File:** `src/app/features/settings/account/ProfileDecoration.tsx`
**Status:** ✅ RESOLVED (June 2026)

*   **Issue:** Decoration preview cells in the settings picker appeared vertically and horizontally squished together with almost no gap between images.
*   **Root Cause:** The flex container used `gap: 20px`, but each `52×52` button renders its decoration image with `position: absolute; top: -8px; left: -8px` (INSET=8), so each image bleeds 8px outside the button on all sides. The visual gap between images was `20 - 8 - 8 = 4px` — nearly nothing. Additionally `paddingBottom: 4` clipped the bottom overflow of the last row, and `paddingRight` was absent so the rightmost column clipped.
*   **Fix Applied:** Increased `gap` from `20` to `36` (visual gap = 36 - 8 - 8 = 20px), changed `paddingBottom` from `4` to `INSET` (8px), added `paddingRight: INSET`.

### 10. Windows Taskbar Badge Has Black Square Background and Small Number
**File:** `src-tauri/src/lib.rs` (cinny-desktop) — `set_badge_count` command
**Status:** ✅ RESOLVED (June 2026)

*   **Issue:** The notification badge overlaid on the Windows taskbar icon had a solid black square background instead of a transparent circle, and the number was too small to read at a glance.
*   **Root Cause (black square):** `CreateDIBSection` initialises the pixel buffer to zero (BGRA `0x00000000`). GDI drawing functions (`Ellipse`, `DrawTextW`) paint RGB values but never touch the alpha channel — all pixels retain `A=0`. When every pixel has `A=0`, Windows cannot use per-pixel alpha compositing and falls back to the monochrome mask (`hbm_mask`, all zeros = fully opaque), so the entire 16×16 bitmap is drawn opaque. The corner pixels outside the ellipse are `RGB(0,0,0)` = black, producing the black square.
*   **Root Cause (small number):** Bitmap was 16×16 with an 11px font, giving very little room, especially for two-digit counts.
*   **Fix Applied:**
    1. After all GDI drawing, iterate the pixel buffer and set `alpha = 0xFF` for every non-zero pixel (`*pixel |= 0xFF00_0000`). Corner pixels (zero) retain `A=0` and composite as transparent.
    2. Increased bitmap size from `16` to `20` and font height from `11` to `14`.
