Compare commits
2
Commits
bafd9cbe75
...
170d22eebb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
170d22eebb | ||
|
|
c8ff7b0718 |
+69
-120
@@ -1,146 +1,95 @@
|
||||
# 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.
|
||||
This document tracks identified bugs, edge cases, and architectural discrepancies.
|
||||
|
||||
---
|
||||
|
||||
## 🚩 Critical & UI Bugs
|
||||
## ✅ Resolved Issues (Recently Fixed)
|
||||
|
||||
### 1. Drag-and-Drop Overlay Persistence
|
||||
**File:** `src/app/hooks/useFileDrop.ts`
|
||||
**Status:** Confirmed (Backlog item #!BUG)
|
||||
|
||||
* **Issue:** The file drop overlay remains visible if a user drags a file over the browser window and then moves the mouse out of the window without dropping.
|
||||
* **Root Cause:** The `dragleave` event does not reliably decrement the internal `dragCounter` when the mouse leaves the browser viewport entirely.
|
||||
* **Recommended Fix:** Update the `handleDragLeave` function to detect when the mouse has left the window by checking if `evt.relatedTarget` is null.
|
||||
|
||||
### 2. Stale Member List in Verification Banner
|
||||
**File:** `src/app/hooks/useDeviceVerificationStatus.ts`
|
||||
**Status:** High Priority
|
||||
|
||||
* **Issue:** The "Unverified Device Warning" banner may fail to appear for users who join a room after it has already been opened in the client.
|
||||
* **Root Cause:** The `memberIds` array is memoized only on `room.roomId`. New members joining the room do not trigger a recalculation of the list, meaning their devices are never checked.
|
||||
* **Recommended Fix:** Add the room's joined member count to the memoization dependencies.
|
||||
|
||||
### 3. Night Light Overlay Coverage (Portal Issue)
|
||||
**File:** `src/app/pages/App.tsx`
|
||||
**Status:** UI Consistency
|
||||
|
||||
* **Issue:** The Night Light (blue light filter) overlay is rendered inside the `#root` div. However, Cinny's modals, tooltips, and popouts are rendered inside a sibling `#portalContainer`.
|
||||
* **Impact:** Modals and tooltips will appear at full brightness, bypassing the filter.
|
||||
* **Recommended Fix:** Move the `NightLightOverlay` component to the end of `document.body` or utilize a Portal to render it inside `#portalContainer` with a high z-index (e.g., 9999).
|
||||
|
||||
### 4. Decrypted Media Memory Leak (Gallery & Lightbox)
|
||||
**File:** `src/app/features/room/MediaGallery.tsx`
|
||||
**Status:** Performance / Memory
|
||||
|
||||
* **Issue:** Every image in the gallery history is decrypted and converted to a Blob URL simultaneously.
|
||||
* **Impact:** Scrolling through long room histories can consume gigabytes of RAM, as Blob URLs are not revoked until the gallery is closed.
|
||||
* **Recommended Fix:** Implement virtualization for the gallery grid. Only decrypt and create URLs for items currently in or near the viewport.
|
||||
* **GIF Sending Bypasses E2EE**: Fixed in `RoomInput.tsx`.
|
||||
* **Scheduled Messages Bypass E2EE**: Fixed in `scheduledMessages.ts`.
|
||||
* **Drag-and-Drop Overlay Persistence**: Fixed in `useFileDrop.ts`.
|
||||
* **Stale Member List in Verification Banner**: Fixed in `useDeviceVerificationStatus.ts`.
|
||||
* **Incomplete Python Comment Highlighting**: Fixed in `syntaxHighlight.ts`.
|
||||
* **Search Button Hidden in E2EE Rooms**: Fixed in `RoomViewHeader.tsx`.
|
||||
* **TDS Design Law Violations (Hardcoded Hex)**: Fixed in `GifPicker.tsx` and `VoiceMessageRecorder.tsx`.
|
||||
* **Recent Emoji Sort Order**: Fixed in `recent-emoji.ts` (recency order, not frequency).
|
||||
* **Encrypted Search Misses Historic Events**: Fixed in `useLocalMessageSearch.ts`.
|
||||
* **Presence Updater Base URL Hack**: Fixed in `usePresenceUpdater.ts`.
|
||||
* **Presence Badge Accessibility**: Fixed in `Presence.tsx` (`aria-label` on badge).
|
||||
|
||||
---
|
||||
|
||||
## 🎨 UI/UX & Visual Consistency
|
||||
## 🛡️ Critical Security & Logic
|
||||
|
||||
### 1. Search Button Hidden in E2EE Rooms
|
||||
**File:** `src/app/features/room/RoomViewHeader.tsx`
|
||||
**Status:** Major UX Regression
|
||||
### 1. Edit History Broken for E2EE
|
||||
**File:** `src/app/features/room/message/EditHistoryModal.tsx`
|
||||
**Status:** **OPEN**
|
||||
|
||||
* **Issue:** The message search icon is hidden in encrypted rooms.
|
||||
* **Impact:** Users cannot access the "Encrypted Room Search" feature (local cache scan) from the room header, even though the feature is implemented and functional in the search page.
|
||||
* **Recommended Fix:** Remove the `!encryptedRoom` condition from the Search button renderer in the header.
|
||||
* **Issue:** The modal fetches edit history via raw `fetch`. The returned events are not decrypted.
|
||||
* **Impact:** In encrypted rooms, the edit history shows ciphertext or "(no text)" for all previous versions.
|
||||
* **Recommended Fix:** After fetching raw events, check if they are encrypted. Use `mx.decryptEventIfNeeded(event)` for each event in the chunk before rendering.
|
||||
|
||||
### 2. TDS Design Law Violations (Hardcoded Hex)
|
||||
**Files:** `src/app/components/GifPicker.tsx`, `src/app/components/VoiceMessageRecorder.tsx`
|
||||
**Status:** Styling Integrity
|
||||
|
||||
* **Issue:** Multiple custom components use hardcoded hex values (e.g., `#FF6B00`, `#060c14`) instead of the CSS variables defined in `lotus-terminal.css.ts`.
|
||||
* **Impact:** These components will not adapt if theme tokens are updated in the central TDS file, and they bypass the "Design Law" established in the backlog.
|
||||
* **Recommended Fix:** Replace all hardcoded hex values with `var(--lt-accent-orange)`, `var(--lt-bg-secondary)`, etc.
|
||||
|
||||
### 3. Missing CSS Variable `--bg-surface-variant`
|
||||
**Files:** `src/app/features/message-search/MessageSearch.tsx`, `src/app/components/VoiceMessageRecorder.tsx`
|
||||
**Status:** UI Bug
|
||||
|
||||
* **Issue:** Components use `var(--bg-surface-variant)`, which is not defined in either `index.css` or `lotus-terminal.css.ts`.
|
||||
* **Impact:** Backgrounds for the voice recorder and search cache panels appear transparent or fall back to browser defaults.
|
||||
* **Recommended Fix:** Define `--bg-surface-variant` in both global themes and the TDS theme, or switch to using `--bg-surface-low`.
|
||||
|
||||
### 4. Recent vs. Top Emoji Logic Discrepancy
|
||||
**File:** `src/app/plugins/recent-emoji.ts`
|
||||
**Status:** Logic Inconsistency
|
||||
|
||||
* **Issue:** The "Quick Reactions" hover bar is described as showing the "3 most recently used emojis." However, the implementation sorts by `usage count`, meaning it actually shows the "most frequently used" emojis.
|
||||
* **Impact:** If a user uses a new emoji, it will not appear in the quick reactions until its total usage count exceeds the current top 3. This contradicts user expectations for a "recently used" list.
|
||||
* **Recommended Fix:** Update `getRecentEmojis` to sort by the order in the array (which is unshifted in `addRecentEmoji`) or add a timestamp to each emoji entry in the account data to allow true chronological sorting.
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Technical Refinements
|
||||
|
||||
### 1. Incomplete Python Comment Highlighting
|
||||
**File:** `src/app/utils/syntaxHighlight.ts`
|
||||
**Status:** Medium Priority
|
||||
|
||||
* **Issue:** Python comments (`#`) are only highlighted if they appear at the very start of a line. Inline comments like `print("hello") # comment` are treated as plain text.
|
||||
* **Recommended Fix:** Update the detection logic to allow `#` after whitespace or tabs.
|
||||
|
||||
```typescript
|
||||
// Recommended
|
||||
(i === 0 || code[i - 1] === '\n' || code[i - 1] === ' ' || code[i - 1] === '\t')
|
||||
```
|
||||
|
||||
### 2. Duplicate Bookmarking Race Condition
|
||||
**File:** `src/app/hooks/useBookmarks.ts`
|
||||
**Status:** Data Integrity
|
||||
|
||||
* **Issue:** Rapidly clicking the bookmark button can trigger multiple `setAccountData` calls that read from the same stale local cache, potentially overwriting each other.
|
||||
* **Recommended Fix:** Implement a queue or locking mechanism for account data updates to ensure atomicity.
|
||||
|
||||
### 3. Presence Updater Matrix Base URL Access
|
||||
### 2. Presence Updater Wipes Custom Status
|
||||
**File:** `src/app/hooks/usePresenceUpdater.ts`
|
||||
**Status:** Best Practice
|
||||
**Status:** **OPEN**
|
||||
|
||||
* **Issue:** The code uses a hacky cast: `(mx as unknown as { baseUrl: string }).baseUrl`.
|
||||
* **Recommended Fix:** Use the standard SDK method `mx.getHomeserverUrl()`.
|
||||
* **Issue:** `setOnline` and `setUnavailable` still send `status_msg: ''`.
|
||||
* **Impact:** Custom status messages are wiped when the user goes idle/active.
|
||||
* **Recommended Fix:** Remove `status_msg` from the `setPresence` payload in the updater hook.
|
||||
|
||||
### 4. Encrypted Search Misses Historic Events
|
||||
**File:** `src/app/features/message-search/useLocalMessageSearch.ts`
|
||||
**Status:** Functional Limitation
|
||||
### 3. Service Worker Ephemeral Sessions
|
||||
**File:** `src/sw.ts`
|
||||
**Status:** **OPEN**
|
||||
|
||||
* **Issue:** The search only scans `room.getLiveTimeline().getEvents()`.
|
||||
* **Impact:** If a user has scrolled back and loaded historic messages into linked timelines, those messages will be ignored by the search.
|
||||
* **Recommended Fix:** Iterate through all timelines in the room's primary `TimelineSet` using `room.getUnfilteredTimelineSet().getTimelines()`.
|
||||
|
||||
### 5. useAccountDataCallback Memoization Risk
|
||||
**File:** `src/app/hooks/useAccountDataCallback.ts`
|
||||
**Status:** Maintenance Risk
|
||||
|
||||
* **Issue:** The hook attaches/detaches a Matrix event listener every time the `onAccountData` callback changes. If a component using this hook does not memoize the callback (via `useCallback`), the listener will be churned on every render.
|
||||
* **Impact:** Potential performance degradation and subtle event-handling bugs if listeners are added/removed multiple times per second.
|
||||
* **Recommended Fix:** Add a JSDoc warning to the hook file instructing developers to always wrap the callback in `useCallback`.
|
||||
* **Issue:** Access tokens are stored in an in-memory `sessions` Map within the SW.
|
||||
* **Impact:** Closing all app tabs wipes the sessions. Background tasks (like future push notification handling or media pre-fetching) will fail.
|
||||
* **Recommended Fix:** Persist the session info (accessToken/baseUrl) in IndexedDB within the Service Worker so it survives app restarts.
|
||||
|
||||
---
|
||||
|
||||
## 📝 Documentation & Accessibility
|
||||
## 📱 PWA & Mobile Issues
|
||||
|
||||
### 1. Documentation Discrepancy (EC Spotlight)
|
||||
**Files:** `LOTUS_FEATURES.md` vs `matrix/README.md`
|
||||
### 1. No PWA Precaching (Offline Mode Broken)
|
||||
**File:** `src/sw.ts`, `vite.config.js`
|
||||
**Status:** **OPEN**
|
||||
|
||||
* **Issue:** `LOTUS_FEATURES.md` claims there is an "Auto-revert spotlight on screenshare" feature. `matrix/README.md` correctly states this was **removed** as it broke fullscreen views.
|
||||
* **Action:** Update `LOTUS_FEATURES.md` to remove the mention.
|
||||
* **Issue:** The Service Worker is missing the `self.__WB_MANIFEST` injection point and `precacheAndRoute` call.
|
||||
* **Impact:** The app does not work offline and fails PWA installation requirements in most browsers.
|
||||
* **Recommended Fix:** Add `precacheAndRoute(self.__WB_MANIFEST)` to `sw.ts` and ensure `vite.config.js` has a valid `injectionPoint`.
|
||||
|
||||
### 2. Presence Badge Accessibility
|
||||
**File:** `src/app/components/presence/Presence.tsx`
|
||||
**Status:** Accessibility
|
||||
### 2. PiP Resize Impossible on Mobile
|
||||
**File:** `src/app/components/CallEmbedProvider.tsx`
|
||||
**Status:** **OPEN**
|
||||
|
||||
* **Issue:** `aria-labelledby` refers to a Tooltip ID that may not be in the DOM when the badge is first read by a screen reader.
|
||||
* **Recommended Fix:** Add a descriptive `aria-label` directly to the `Badge` component (e.g., `aria-label="Status: Online"`).
|
||||
* **Issue:** Resizing the PiP window uses `onMouseDown` handlers which do not trigger on touch devices.
|
||||
* **Impact:** Mobile users cannot resize the PiP window.
|
||||
* **Recommended Fix:** Implement `onTouchStart` handlers for the resize corners, mapping touch coordinates to the same resize logic.
|
||||
|
||||
### 3. Missing Status Tooltip on Text
|
||||
**File:** `src/app/features/room/MembersDrawer.tsx`
|
||||
**Status:** UX Improvement
|
||||
### 3. Double Background Animation (GPU Waste)
|
||||
**File:** `src/app/pages/client/SidebarNav.tsx`, `src/app/features/room/RoomView.tsx`
|
||||
**Status:** **OPEN**
|
||||
|
||||
* **Issue:** When Glassmorphism is enabled, the chat background is mirrored to `document.body` while the `RoomView` also renders it.
|
||||
* **Impact:** Two identical animations (e.g., Digital Rain) run simultaneously, doubling GPU usage on mobile.
|
||||
* **Recommended Fix:** When Glassmorphism is active, make the `RoomView` background transparent and rely on the `document.body` background.
|
||||
|
||||
### 4. Manifest Shortcut Icon 404
|
||||
**File:** `public/manifest.json`
|
||||
**Status:** **OPEN**
|
||||
|
||||
* **Issue:** The shortcut icon path is `res/android/...` but the file is copied to `public/android/...`.
|
||||
* **Recommended Fix:** Change the path to `./public/android/android-chrome-96x96.png` in `manifest.json`.
|
||||
|
||||
---
|
||||
|
||||
## 🎨 UI/UX Consistency
|
||||
|
||||
### 1. Night Light Overlay Coverage
|
||||
**File:** `src/app/pages/App.tsx`
|
||||
**Status:** **OPEN**
|
||||
|
||||
* **Issue:** Overlay is inside `#root`, bypasses `#portalContainer` (modals/tooltips).
|
||||
* **Recommended Fix:** Move to end of `document.body`.
|
||||
|
||||
* **Issue:** In the members drawer, the status message text is truncated but does not have a tooltip. The tooltip only exists on the small presence dot.
|
||||
* **Recommended Fix:** Wrap the status text in a `TooltipProvider` to allow users to read long status messages.
|
||||
|
||||
@@ -36,12 +36,12 @@ function GifPickerInner({ onSelect, requestClose, lotusTerminal }: GifPickerInne
|
||||
<div
|
||||
style={{
|
||||
padding: '5px 10px 4px',
|
||||
borderBottom: '1px solid rgba(255,107,0,0.2)',
|
||||
borderBottom: '1px solid color-mix(in srgb, var(--lt-accent-orange) 20%, transparent)',
|
||||
fontFamily: "'JetBrains Mono', 'Cascadia Code', monospace",
|
||||
fontSize: '10px',
|
||||
fontWeight: 700,
|
||||
letterSpacing: '0.1em',
|
||||
color: '#FF6B00',
|
||||
color: 'var(--lt-accent-orange)',
|
||||
userSelect: 'none',
|
||||
}}
|
||||
>
|
||||
@@ -82,11 +82,11 @@ export function GifPicker({ apiKey, onSelect, requestClose }: GifPickerProps) {
|
||||
|
||||
const containerStyle = lotusTerminal
|
||||
? {
|
||||
background: '#060c14',
|
||||
border: '1px solid rgba(255,107,0,0.35)',
|
||||
background: 'var(--lt-bg-secondary)',
|
||||
border: '1px solid color-mix(in srgb, var(--lt-accent-orange) 35%, transparent)',
|
||||
borderRadius: '4px',
|
||||
overflow: 'hidden',
|
||||
boxShadow: '0 4px 24px rgba(255,107,0,0.10), 0 0 0 1px rgba(255,107,0,0.08)',
|
||||
boxShadow: '0 4px 24px color-mix(in srgb, var(--lt-accent-orange) 10%, transparent), 0 0 0 1px color-mix(in srgb, var(--lt-accent-orange) 8%, transparent)',
|
||||
width: `${PICKER_WIDTH}px`,
|
||||
}
|
||||
: {
|
||||
|
||||
@@ -203,7 +203,7 @@ export function VoiceMessageRecorder({ onSend, onError }: VoiceRecorderProps) {
|
||||
width: toRem(8),
|
||||
height: toRem(8),
|
||||
borderRadius: '50%',
|
||||
background: lotusTerminal ? '#FF6B00' : 'var(--tc-danger-normal)',
|
||||
background: lotusTerminal ? 'var(--lt-accent-orange)' : 'var(--tc-danger-normal)',
|
||||
flexShrink: 0,
|
||||
animation: 'pttLivePulse 900ms ease-in-out infinite',
|
||||
}}
|
||||
@@ -214,7 +214,7 @@ export function VoiceMessageRecorder({ onSend, onError }: VoiceRecorderProps) {
|
||||
minWidth: toRem(32),
|
||||
fontVariantNumeric: 'tabular-nums',
|
||||
...(lotusTerminal
|
||||
? { fontFamily: 'JetBrains Mono, monospace', color: '#00FF88', fontWeight: 700 }
|
||||
? { fontFamily: 'JetBrains Mono, monospace', color: 'var(--lt-accent-green)', fontWeight: 700 }
|
||||
: {}),
|
||||
}}
|
||||
>
|
||||
@@ -233,7 +233,7 @@ export function VoiceMessageRecorder({ onSend, onError }: VoiceRecorderProps) {
|
||||
width: toRem(2),
|
||||
height: toRem(2 + (h / barMax) * 16),
|
||||
borderRadius: toRem(1),
|
||||
background: lotusTerminal ? '#00FF88' : 'var(--tc-primary-normal)',
|
||||
background: lotusTerminal ? 'var(--lt-accent-green)' : 'var(--tc-primary-normal)',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
TooltipProvider,
|
||||
toRem,
|
||||
} from 'folds';
|
||||
import React, { ReactNode, useId } from 'react';
|
||||
import React, { ReactNode } from 'react';
|
||||
import * as css from './styles.css';
|
||||
import { Presence, usePresenceLabel } from '../../hooks/useUserPresence';
|
||||
|
||||
@@ -27,7 +27,7 @@ type PresenceBadgeProps = {
|
||||
};
|
||||
export function PresenceBadge({ presence, status, size }: PresenceBadgeProps) {
|
||||
const label = usePresenceLabel();
|
||||
const badgeLabelId = useId();
|
||||
const ariaLabel = status ? `${label[presence]} — ${status}` : label[presence];
|
||||
|
||||
return (
|
||||
<TooltipProvider
|
||||
@@ -36,7 +36,7 @@ export function PresenceBadge({ presence, status, size }: PresenceBadgeProps) {
|
||||
offset={4}
|
||||
delay={200}
|
||||
tooltip={
|
||||
<Tooltip id={badgeLabelId}>
|
||||
<Tooltip>
|
||||
<Box style={{ maxWidth: toRem(250) }} alignItems="Baseline" gap="100">
|
||||
<Text size="L400">{label[presence]}</Text>
|
||||
{status && <Text size="T200">•</Text>}
|
||||
@@ -47,7 +47,7 @@ export function PresenceBadge({ presence, status, size }: PresenceBadgeProps) {
|
||||
>
|
||||
{(triggerRef) => (
|
||||
<Badge
|
||||
aria-labelledby={badgeLabelId}
|
||||
aria-label={ariaLabel}
|
||||
ref={triggerRef}
|
||||
size={size}
|
||||
variant={PresenceToColor[presence]}
|
||||
|
||||
Reference in New Issue
Block a user