diff --git a/LOTUS_BUGS.md b/LOTUS_BUGS.md new file mode 100644 index 000000000..c75d715bd --- /dev/null +++ b/LOTUS_BUGS.md @@ -0,0 +1,146 @@ +# 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 & UI Bugs + +### 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. + +--- + +## ๐ŸŽจ UI/UX & Visual Consistency + +### 1. Search Button Hidden in E2EE Rooms +**File:** `src/app/features/room/RoomViewHeader.tsx` +**Status:** Major UX Regression + +* **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. + +### 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 +**File:** `src/app/hooks/usePresenceUpdater.ts` +**Status:** Best Practice + +* **Issue:** The code uses a hacky cast: `(mx as unknown as { baseUrl: string }).baseUrl`. +* **Recommended Fix:** Use the standard SDK method `mx.getHomeserverUrl()`. + +### 4. Encrypted Search Misses Historic Events +**File:** `src/app/features/message-search/useLocalMessageSearch.ts` +**Status:** Functional Limitation + +* **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`. + +--- + +## ๐Ÿ“ Documentation & Accessibility + +### 1. Documentation Discrepancy (EC Spotlight) +**Files:** `LOTUS_FEATURES.md` vs `matrix/README.md` + +* **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. + +### 2. Presence Badge Accessibility +**File:** `src/app/components/presence/Presence.tsx` +**Status:** Accessibility + +* **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"`). + +### 3. Missing Status Tooltip on Text +**File:** `src/app/features/room/MembersDrawer.tsx` +**Status:** UX Improvement + +* **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. diff --git a/LOTUS_FEATURES.md b/LOTUS_FEATURES.md new file mode 100644 index 000000000..aa62f14ef --- /dev/null +++ b/LOTUS_FEATURES.md @@ -0,0 +1,776 @@ +# Lotus Chat โ€” Feature Reference + +Everything added to Lotus Chat beyond upstream Cinny v4.12.1. +Last updated: June 2026. + +--- + +## Table of Contents + +1. [Branding & Identity](#branding--identity) +2. [LotusGuild Terminal Design System (TDS) v1.2](#lotusguild-terminal-design-system-tds-v12) +3. [Animated Chat Backgrounds (P5-4)](#animated-chat-backgrounds-p5-4) +4. [Glassmorphism Sidebar (P5-3)](#glassmorphism-sidebar-p5-3) +5. [Night Light / Blue Light Filter (P5-5)](#night-light--blue-light-filter-p5-5) +6. [Voice / Video Call Improvements](#voice--video-call-improvements) +7. [Per-Message Read Receipts](#per-message-read-receipts) +8. [Delivery Status Indicators](#delivery-status-indicators) +9. [Messaging Enhancements](#messaging-enhancements) +10. [Presence](#presence) +11. [UX & Composer](#ux--composer) +12. [Room Customization](#room-customization) +13. [Moderation](#moderation) +14. [Notifications](#notifications) +15. [Server Integration](#server-integration) +16. [Infrastructure](#infrastructure) +17. [Key Custom Files](#key-custom-files) + +--- + +## Branding & Identity + +- Package renamed to `lotus-chat`; description updated in `package.json` +- App title set to "Lotus Chat" throughout (window title, meta tags, manifest) +- Favicon, PWA icons (all sizes), and Apple touch icons replaced with `Lotus.png` +- Lotus logo displayed in the About dialog and Auth page +- Auth footer: dynamic version pulled from `package.json`; links to `lotusguild.org`, `chat.lotusguild.org`, and `matrix.lotusguild.org` +- Welcome page tagline: "A Matrix client for Lotus Guild" +- Encryption key export filename changed to `lotus-keys.txt` +- `manifest.json` updated with Lotus name, description, and branding colors + +--- + +## LotusGuild Terminal Design System (TDS) v1.2 + +### Dark Mode โ€” `LotusTerminalTheme` + +A CRT terminal aesthetic applied globally when the TDS theme is active. + +**Visual effects:** +- Scanline overlay via repeating `linear-gradient` pseudo-element +- Vignette via radial-gradient overlay +- Phosphor glow on text and accents via `text-shadow` / `box-shadow` + +**Color palette:** +| Token | Value | Role | +|---|---|---| +| `--lt-bg` | `#030508` | Page/panel background | +| `--lt-accent-orange` | `#FF6B00` | Primary accent | +| `--lt-accent-cyan` | `#00D4FF` | Secondary accent | +| `--lt-accent-green` | `#00FF88` | Success / active states | +| `--lt-text` | `#c4d9ee` | Body text | + +**Typography & chrome:** +- Monospace font stack applied to all UI elements +- Terminal-style scrollbars (thin, accent-colored track) + +**Decorative patterns:** +- Custom hex-grid CSS background pattern +- Circuit-board CSS background pattern (switchable) + +**Boot sequence:** +- Matrix-style boot messages on the welcome page; press Escape to skip +- Implemented in `src/lotus-boot.ts` + +**CSS variable family:** all custom tokens use the `--lt-*` prefix, defined in `src/lotus-terminal.css.ts`. + +### Light Mode โ€” `LotusTerminalLightTheme` + +A full light-palette counterpart to the dark TDS theme. + +**Color palette:** +| Token | Light Value | Role | +|---|---|---| +| `--lt-bg` | `#edf0f5` | Page/panel background | +| `--lt-accent-orange` | `#c44e00` | Primary accent | +| `--lt-accent-cyan` | `#0062b8` | Secondary accent | +| `--lt-accent-green` | `#006d35` | Success / active states | +| `--lt-text` | `#111827` | Body text | + +**Differences from dark mode:** +- CRT effects (scanlines, vignette, phosphor glow) are disabled +- Scoped to `html[data-theme="light"] body.lotusTerminalBodyClass` to avoid bleed into non-TDS themes +- `ThemeManager.tsx` is responsible for setting the `data-theme` attribute on the `` element when theme changes + +### Chat Background Patterns (20+ static) + +A library of CSS-only background patterns for the chat area, all using CSS custom properties so they adapt automatically to both TDS dark and light palettes: + +- Blueprint grid +- Carbon fiber +- Starfield +- Topographic contours +- Herringbone +- Crosshatch +- Chevron +- Polka dots +- Triangles +- Plaid +- (and additional variants) + +--- + +## Animated Chat Backgrounds (P5-4) + +Five CSS-only animated wallpapers implemented with vanilla-extract keyframes. No `` element is used โ€” all animation is pure CSS. + +### Available Animations + +**Digital Rain** +Two-layer vertical stripe scroll with parallax effect. Wide stripes animate at 8s, narrow stripes at 4s, creating depth. + +**Star Drift** +Three-layer radial-gradient dot field drifting diagonally across the viewport. Each layer moves at a distinct speed and angle. + +**Grid Pulse** +Neon grid lines that expand and contract via a `backgroundSize` keyframe. Grid color follows `--lt-accent-cyan` in dark mode. + +**Aurora Flow** +Four radial-gradient ellipses sweeping across a 200% canvas. Colors use the TDS green/cyan/orange palette and blend softly. + +**Fireflies** +Three layers of warm glowing dots that drift slowly. Dot color follows `--lt-accent-orange` for a warm bioluminescent feel. + +### API + +```ts +getChatBg(bg: ChatBg, isDark: boolean, pauseAnimations?: boolean): CSSProperties +``` + +Strips all `animation` properties from the returned style object when either `pauseAnimations` is `true` or the `prefers-reduced-motion: reduce` media query is active. + +### Settings Integration + +A "Pause Background Animations" toggle is exposed in **Settings โ†’ Appearance**. The preference is persisted and read by `getChatBg()` at render time. + +### Files + +- `src/app/styles/Animations.css.ts` โ€” vanilla-extract keyframe definitions +- `src/app/features/lotus/chatBackground.ts` โ€” `getChatBg()` implementation and pattern registry + +--- + +## Glassmorphism Sidebar (P5-3) + +An optional frosted-glass sidebar style toggled in **Settings โ†’ Appearance**. + +**Implementation:** +- `SidebarGlass` vanilla-extract class applies `background: rgba(3, 5, 8, 0.55)` and `backdropFilter: blur(12px)` to the sidebar element +- `SidebarNav.tsx` uses a `useEffect` to mirror the active chat background onto `document.body` when the glassmorphism setting is enabled, so the blur filter has meaningful content to work through +- Degrades gracefully on browsers without `backdrop-filter` support (falls back to the semi-transparent background) + +--- + +## Night Light / Blue Light Filter (P5-5) + +A warm orange overlay rendered over the entire UI to reduce blue light emission. + +**Implementation:** +- `NightLightOverlay` component mounted directly in `App.tsx` +- CSS: `position: fixed; inset: 0; pointer-events: none; z-index: 9998` +- Orange tint color with configurable opacity +- **Controls:** Toggle to enable/disable + intensity slider ranging from 5% to 80% opacity +- Settings persisted via the standard Lotus settings store + +--- + +## Voice / Video Call Improvements + +### Element Call Upgrade + +Upgraded embedded Element Call widget from **0.16.3** to **0.19.4**. + +### Camera Default Off + +Camera starts disabled on join. The `cameraOnJoin` setting is explicitly opt-in and is not persisted to `localStorage` between sessions, preventing the previous behavior where a prior-session preference could unexpectedly enable the camera. + +### UI Fixes + +- **Deafen button** โ€” corrected tooltip text that was previously swapped with the mute tooltip +- **Screenshare confirmation** โ€” a confirmation dialog is shown before initiating a screenshare broadcast to the room +- **Auto-revert spotlight on screenshare** โ€” removed; the 600ms grid-revert click was causing fullscreen screenshare to show avatar tiles instead of the screen. EC now handles layout natively. + +### Push to Talk (PTT) + +- Configurable keybind; defaults to `Space` +- Visual indicator shown in both TDS and non-TDS themes while PTT is active +- Event listener attached to both the main window and the Element Call iframe's `contentWindow` to ensure reliable capture regardless of focus +- Mic state is preserved correctly when switching into or out of PTT mode + +### Push to Deafen + +`M` key triggers `toggleSound()` in `CallControls.tsx`, toggling the deafen state without requiring a mouse click. + +### Noise Suppression Toggle + +A `noiseSuppression` URL parameter is passed to the Element Call widget URL, allowing the noise suppression feature to be toggled from within Lotus settings. + +### Call Button Scoping + +The call button is shown only in DMs and invite-only rooms that do not have an `m.space.parent` event. It is hidden in public rooms and space channels to avoid accidental broadcast calls. + +### Picture-in-Picture (PiP) + +- 280ร—158px floating window that stays on screen when navigating away from the call room +- Draggable via pointer events with a 5px movement threshold to distinguish drags from clicks; touch events are supported +- Clicking the PiP window navigates back to the call room +- Implemented with an imperative `useEffect` style for position overrides because `useCallEmbedPlacementSync` writes geometry directly onto the DOM element, making declarative approaches unreliable + +### Call Embed Positioning + +- Uses `getBoundingClientRect()` (viewport-relative) instead of the previous `offsetTop`/`offsetLeft` (parent-relative) calculations, fixing misalignment in scrolled layouts +- Position is synced on mount via `useEffect` with a `ResizeObserver` to handle dynamic layout changes + +### Dark Mode in Element Call + +`applyStyles()` injects `:root { color-scheme: dark | light }` into the Element Call iframe after the user joins. The theme is also updated when `setTheme()` is called, keeping the call UI in sync with the Lotus theme selection. + +### Call Embed Wallpaper + +The active `chatBackground` pattern is applied to the `div[data-call-embed-container]` wrapper. The iframe's `html, body` are forced to `background: none !important` so the host pattern shows through. + +### TDS Typing Indicator + +The animated typing indicator dots use `var(--lt-accent-orange)` as their color when the TDS theme is active, matching the terminal aesthetic. + +--- + +## Per-Message Read Receipts + +A full per-message read receipt system showing exactly who has seen each message. + +### Core Hook โ€” `useRoomReadPositions` + +```ts +useRoomReadPositions(room: Room): Map +``` + +- Listens to `Room.localEchoUpdated` and `RoomMember.typing` events to stay reactive +- Debounced 150ms to avoid excessive re-renders during rapid receipt updates +- Located at `src/app/hooks/useRoomReadPositions.ts` + +### `nearestRenderableId()` + +A utility that walks backward through the event timeline from a given event ID to find the nearest event that is actually rendered (skipping reactions, edits, and other non-display events). Used to map a user's read position to a visible message. + +### `ReadPositionsContext` + +React context at `src/app/features/room/ReadPositionsContext.ts` that provides the positions map to all timeline components without prop drilling. + +### `ReadReceiptAvatars` + +A pill of overlapping 24px user avatars displayed at the bottom-right of each message. Shows a maximum of 5 avatars; additional readers are shown as an overflow count (e.g., `+3`). + +### "Seen by" Modal โ€” `EventReaders` + +Clicking the avatar pill opens a modal (`src/app/components/event-readers/EventReaders.tsx`) listing: +- User avatar +- Display name +- Formatted timestamp of when the receipt was recorded +- Respects the `hour24Clock` setting for timestamp formatting + +--- + +## Delivery Status Indicators + +Visual feedback on message delivery state, shown on the sender's own messages: + +| State | Indicator | +|---|---| +| Sending (local echo) | โŸณ rotating clock icon | +| Sent (server ACK received) | โœ“ checkmark | +| Failed | โœ• in red; orange glow in TDS | + +The indicator is hidden once the server confirms the event (when the internal status transitions to `null`), keeping the timeline clean for settled messages. + +--- + +## Messaging Enhancements + +### Rich Room Topics + +- Topic `formatted_body` is rendered via `sanitizeCustomHtml` + `html-react-parser`, supporting bold, italic, links, and other inline HTML +- The room header shows a plain-text preview of the topic +- Clicking the topic preview opens a full modal with the formatted body +- The room settings topic editor includes a formatting toolbar with **B**, *I*, ~~S~~, and `code` buttons + +### Edit History Viewer + +`EditHistoryModal.tsx` fetches and displays the full edit history of a message. + +- API: `GET /_matrix/client/v1/rooms/{roomId}/relations/{eventId}/m.replace` +- E2EE fix: the "Original" entry uses `getClearContent()` to retrieve the decrypted content rather than the encrypted payload +- Accessible from the message context menu + +### Inline GIF Preview + +- Detects Giphy and Tenor share URLs via pattern matching on the message body +- Renders matched URLs as `` inline in the message +- URL is proxied through the Matrix `/_matrix/media/v3/preview_url` endpoint to avoid mixed-content issues + +### GIF Picker + +- Giphy-powered picker accessible from the composer toolbar +- The button is only shown when `gifApiKey` is set in `config.json` +- Selected GIFs are sent as `m.image` events +- Picker UI is styled with TDS variables when the TDS theme is active +- Located at `src/app/components/GifPicker.tsx` + +### Message Forwarding + +Context menu โ†’ **Forward** allows forwarding a message to any room the user is a member of. + +### Draft Persistence + +- Composer drafts are stored in `localStorage` keyed by `roomId` +- Draft is cleared on successful send +- The Jotai atom is the primary source of truth; `localStorage` is only read on room mount + +### Message Search Date Range + +- The search panel accepts `from_ts` and `to_ts` values (epoch milliseconds) passed to the search API +- A chip shows the active date range with an **ร—** button to clear it + +### Image / Video Captions + +Images and videos can be sent with a caption. The caption and media are sent as a single event. + +### Location Sharing + +`m.location` events render an inline map tile using the coordinates from the event content. + +### Deleted Message Placeholders + +Redacted events display "This message has been deleted" along with the redaction reason if one was provided, rather than leaving a blank gap in the timeline. This is a one-line change in the `eventRenderer` filter. + +### Message Bookmarks + +- Bookmarks are stored in `io.lotus.bookmarks` account data, syncing across all devices +- Maximum of 500 bookmarked entries +- `BookmarksPanel.tsx` is a sidebar panel accessible from the navigation rail +- Hook: `src/app/hooks/useBookmarks.ts` + +### Message Scheduling + +- Implements MSC4140 delayed events for scheduling messages to be sent at a future time +- `ScheduleMessageModal.tsx` provides the date/time picker UI +- A collapsible "Scheduled" tray in the room shows all pending scheduled messages with individual cancel buttons +- Utilities in `src/app/utils/scheduledMessages.ts` + +### File Upload Compression (opt-in) + +- Implemented in `UploadCardRenderer.tsx` +- Uses the Canvas API: `canvas.toBlob(callback, 'image/jpeg', 0.82)` for compression +- A `Switch` toggle in the upload preview UI lets the user opt in per upload +- Shows before and after file sizes so the user can evaluate the tradeoff +- Works on all image types except SVG (which cannot be drawn to canvas) +- On send, the original uncompressed MXC URL is deleted from the media store +- On cancel, any orphaned MXC from a prior upload is cleaned up via `tryDeleteMxcContent()` + +### Richer URL Preview Cards + +`UrlPreviewCard.tsx` implements 13 domain-specific card layouts: + +| Domain | Layout | +|---|---| +| YouTube | Thumbnail, title, channel, duration | +| Vimeo | Thumbnail, title, author | +| GitHub | Repo name, description, stars/forks/language | +| Twitter / X | Avatar, display name, handle, tweet body | +| Reddit | Subreddit, post title, score, comment count | +| Spotify | Album art, track/album/playlist name, artist | +| Twitch | Stream thumbnail, streamer, game, viewer count | +| Steam | Header image, game name, price, rating | +| Wikipedia | Article title, extract excerpt | +| Discord | Server name, invite metadata | +| npm | Package name, version, description, weekly downloads | +| Stack Overflow | Question title, vote/answer count, tags | +| IMDb | Poster, title, year, rating | + +Generic (non-domain-specific) cards display a Google S2 favicon. Empty or unparseable preview responses are suppressed entirely rather than showing a blank card. + +### Poll Creation + +- `PollCreator.tsx` creates stable `m.poll.start` events +- Supports 2 to 10 answer options +- Supports both single-choice and multiple-choice modes +- Accessible via the `Icons.OrderList` button in the composer toolbar + +### Poll Display + +`PollContent.tsx` renders polls in read-only mode. Handles both the stable `m.poll` format and the legacy MSC3381 unstable `org.matrix.msc3381.poll.start` format. Displays current vote counts and a note directing users to Element to cast votes. + +### Voice Message Playback Speed + +`AudioContent.tsx` adds a playback speed cycle button to voice message players. Available speeds: `[0.75, 1, 1.5, 2]ร—`. A `useEffect` sets `audioElement.playbackRate` whenever the speed selection changes. + +--- + +## Presence + +### Discord-Style Presence Selector + +A presence status selector in the user panel offering five modes: + +| Mode | Matrix broadcast | +|---|---| +| Online | `online` | +| Idle | `unavailable` | +| Do Not Disturb | `unavailable` + `status_msg: 'dnd'` | +| Invisible | `offline` | +| Auto | Standard Matrix presence lifecycle | + +- Selection persists via the `presenceStatus` setting +- `usePresenceUpdater` short-circuits its automatic presence updates when a manual mode (anything other than Auto) is selected + +### Custom Status Message + +- Up to 64 characters of free text plus an emoji +- Optional auto-clear timer with presets: 30 minutes, 1 hour, 4 hours, 1 day, 3 days, 7 days +- Status is broadcast via `mx.setPresence({ status_msg: ... })` +- Character counter appears at 56/64 characters remaining to warn of the limit + +### Presence Badges + +`PresenceBadge` component displays a colored dot indicating presence state. Used in: +- Members drawer +- User settings panel + +### Presence Avatar Ring + +`PresenceRingAvatar` wraps any avatar component using `React.cloneElement` to inject an `outline: 2px solid` ring whose color maps to the user's presence state. `outlineOffset: 2px` ensures the ring sits cleanly outside the avatar regardless of the avatar's `border-radius`. + +Applied in: +- Message timeline +- Members drawer +- `@mention` autocomplete dropdown +- Inbox / notifications panel + +### Document Title Unread Count + +The browser tab title updates to reflect unread state: +- `(N) Lotus Chat` โ€” N unread messages +- `ยท Lotus Chat` โ€” unread activity without a specific count +- `Lotus Chat` โ€” no unread items + +### Extended Profile Fields + +Supports MSC4133 custom profile fields via `PUT /_matrix/client/unstable/uk.tcpip.msc4133/{userId}/{field}`: +- `m.pronouns` โ€” displayed in profile panels +- `m.tz` โ€” IANA timezone string (e.g., `America/New_York`) + +Hook: `src/app/hooks/useExtendedProfile.ts` + +### User Local Time + +When a user has `m.tz` set in their profile: +- Their profile panel shows a clock icon, their current local time, and the timezone abbreviation +- The displayed time updates every 60 seconds +- Respects the global `hour24Clock` setting for 12h/24h formatting + +Hook: `src/app/hooks/useLocalTime.ts` + +--- + +## UX & Composer + +### Message Length Counter + +A character count indicator is shown in the composer when `charCount > 0`. The counter resets to zero when switching rooms. + +### Quick Emoji Reactions on Hover + +A hover toolbar appears over messages, showing the 3 most recently used emojis (sourced from `ElementRecentEmoji` account data) as one-click reaction buttons. The buttons are positioned between the emoji-board button and the Reply button. Clicking a quick reaction closes any open emoji picker. + +### In-App Notification Toasts + +`LotusToastContainer.tsx` displays rich in-app notification toasts with: +- 24px sender avatar +- Sender display name +- Message body preview +- Room name +- ร— dismiss button +- 4-second auto-dismiss timeout +- Slide-in animation via `@keyframes lotusToastIn` + +OS-level notifications are unchanged and still fire when the window is not focused. + +### Collapsible Long Messages + +Messages exceeding a configurable line threshold are truncated with a "Show more" toggle. + +- Default threshold: 20 lines +- Threshold is configurable in **Settings โ†’ Appearance** +- Uses CSS `max-height` + `overflow: hidden` with a smooth transition +- Transition is disabled when `prefers-reduced-motion: reduce` is active + +### Message Send Animation + +A subtle animation plays on the sender's own messages as they appear in the timeline: +- `transform: scale(0.97) โ†’ scale(1)` combined with `opacity: 0.4 โ†’ 1` +- Duration: 0.15s, ease-out +- Only applied to the current user's outgoing messages +- Disabled when `prefers-reduced-motion: reduce` is active + +### Right-Click Room Context Menu + +Right-clicking a room in the sidebar opens a context menu with: +- **Mute** with a duration submenu: 15 minutes, 1 hour, 8 hours, 24 hours, Indefinite +- **Copy Room Link** โ€” copies the `matrix.to` URI to clipboard +- **Mark as Read** โ€” marks all events in the room as read +- **Leave Room** โ€” with a confirmation step +- **Room Settings** โ€” opens the room settings panel + +### Unverified Device Warning + +- Controlled by the `warnOnUnverifiedDevices` setting (off by default) +- When enabled, a `Warning.Container` banner is displayed above the composer in encrypted rooms that have unverified device sessions +- The warning is informational only and never blocks sending + +### Sidebar Room Filter + +A text input at the top of the room list filters rooms by display name in real time. The filter is cleared automatically when switching sidebar tabs. + +### DM Last Message Preview + +Direct message entries in the sidebar show: +- A 48-character truncated preview of the last message body +- A relative timestamp (e.g., "2m ago") +- Reactivity via `useRoomLatestRenderedEvent` +- Encrypted messages show "Encrypted message" if decryption fails; successfully decrypted messages show their plaintext preview + +### Room Sort Order + +The room list sort order can be configured in **Settings โ†’ Appearance**: +- Recent Activity (default) +- A โ†’ Z (alphabetical) +- Unread First + +Persists via the `homeRoomSort` setting. + +### Favorite Rooms + +- Rooms can be favorited via the `m.favourite` tag (standard Matrix tag) +- A "Favorites" section appears above the main room list when any rooms are favorited +- Favorited rooms display a star indicator in the sidebar +- Favorites sync across all devices via account data + +### Invite Link + QR Code + +`RoomShareInvite.tsx` provides a shareable invite UI: +- 160ร—160px QR code generated via `api.qrserver.com` +- "Copy Link" button to copy the `matrix.to` URI +- Also accessible via a toggle button (โŠž) in the Invite modal + +### Private Read Receipts + +A toggle in **Settings โ†’ Privacy** switches between sending `m.read` (public receipts) and `m.read.private` (private receipts visible only to the sender and the server). + +### Media Gallery + +`MediaGallery.tsx` โ€” a right-side drawer for browsing room media. + +- Three tabs: **Images**, **Videos**, **Files** +- Reads already-decrypted events from the room timeline +- Encrypted images show a lock placeholder rather than an error +- "Load More" button triggers `mx.paginateEventTimeline()` to fetch older media + +### Knock-to-Join + +- `RoomIntro.tsx` shows a "Request to Join" button for knock-restricted rooms +- Clicking sends `mx.knockRoom(roomId)` with an optional reason +- The members drawer shows a "Pending Requests" section for room admins, listing users who have knocked + +### Code Syntax Highlighting (TDS) + +`syntaxHighlight.ts` provides TDS-aware syntax highlighting using inline styles derived from `--lt-accent-*` CSS variables. Supported languages: JavaScript, TypeScript, JSX, TSX, Python, Rust. Falls back to ReactPrism for unsupported languages. + +### Room Emoji Prefix + +A leading emoji in a room name is rendered at 1.15ร— size in the sidebar for visual hierarchy. An emoji picker button (๐Ÿ˜Š) is added to all room name input fields, prepending the selected emoji to the room name. + +--- + +## Room Customization + +### Personal Room Name Overrides + +- Users can set a personal display name for any room, stored in `io.lotus.room_names` account data +- A pencil indicator in the sidebar shows rooms with active overrides +- Overrides sync across all devices +- Applied consistently in: room header, sidebar, room intro, and call overlay + +### Export Room History + +`ExportRoomHistory.tsx` exports a room's message history in three formats: + +- **Plain Text** โ€” human-readable transcript +- **JSON** โ€” raw event data +- **HTML** โ€” styled, self-contained page + +Features: +- Optional date range filter +- Progress indicator during export +- Uses `mx.paginateEventTimeline()` to retrieve history in chunks +- E2EE-aware: exports decrypted content for rooms the user has keys for + +### Room Activity / Mod Log + +`RoomActivityLog.tsx` provides a searchable log of administrative events in the room: + +- Member join/leave/kick/ban events +- Power level changes +- Room name, topic, avatar, and ACL changes +- Human-readable descriptions for each event type +- Type filter to narrow results +- "Load More" button for pagination + +### Server ACL Editor + +`RoomServerACL.tsx` โ€” UI for editing `m.room.server_acl` state events. + +- Separate allow and deny server lists +- Wildcard validation (e.g., `*.example.com`) +- "Allow IP literal addresses" toggle +- Read-only for users without admin power level + +### Room Stats / Insights + +`RoomInsights.tsx` (not shown by default; accessible as a non-default tab in room settings). + +- Top 5 most active members (bar chart by message count) +- Top 5 most-used reactions +- Media breakdown by type (images, videos, files) +- 24-hour activity heatmap showing message volume by hour of day + +--- + +## Moderation + +### Report Room + +`ReportRoomModal.tsx` provides a UI for reporting a room to the homeserver. + +- API: `POST /_matrix/client/v3/rooms/{roomId}/report` +- Category dropdown for classifying the report +- Modal auto-closes 1.5 seconds after a successful submission +- Hidden for rooms the user owns and for server notice rooms + +### Policy List / Ban List Viewer + +Accessible via **Room/Space Settings โ†’ Policy Lists** (admin only). + +- Displays the room's subscribed policy lists in read-only format +- Subscribe (join) and unsubscribe (leave) controls for each list +- Enforcement is delegated to Draupnir or equivalent tooling; Lotus only manages list membership + +--- + +## Notifications + +### Custom Notification Sounds + +- `messageSoundId` and `inviteSoundId` settings select from a predefined map +- Sound options defined in `notificationSounds.ts` as `NOTIFICATION_SOUND_MAP` +- Preview (โ–ถ) buttons in the settings panel let users audition sounds before selecting +- Separate sound selection for: `notification`, `invite`, `call`, `none` + +### Notification Quiet Hours + +- Controlled by `quietHoursEnabled`, `quietHoursStart`, and `quietHoursEnd` settings +- Correctly handles overnight spans (e.g., 22:00 โ†’ 07:00) +- Gates both `notify()` (visual/OS notifications) and `playSound()` (audio alerts) +- When active, notifications are silently dropped rather than queued + +### Full Push Rule Editor + +A complete UI for managing Matrix push notification rules: + +- Displays rules by kind: `override`, `room`, `sender`, `underride` +- Enable/disable toggle per rule +- Delete button for removable rules +- Add-rule form for creating new `room` and `sender` rules + +--- + +## Server Integration + +### Server Support Contact + +- Fetches `/.well-known/matrix/support` on the user's homeserver +- Contact information is displayed in **Settings โ†’ Help & About** +- Styled with TDS cyan when the TDS theme is active +- Degrades gracefully with no error shown on 404 responses + +### Server Notices + +`m.server_notice` rooms receive special treatment: +- A `Chip variant="Warning"` badge reading "Server Notice" is shown in the room header +- The composer is read-only (no message input) +- Invite, Report Room, and Room Settings menu items are hidden + +--- + +## Infrastructure + +### Authenticated Media + +`mxcUrlToHttp()` calls now use the correct argument order for MSC3916 authenticated media: + +```ts +mxcUrlToHttp(mx, mxcUrl, useAuthentication, width, height, 'crop') +``` + +The `useAuthentication` parameter was previously mispositioned, causing unauthenticated requests to be sent for media in rooms that required authentication. + +### Upstream Tracking + +- An `upstream` git remote pointing to `github.com/cinnyapp/cinny` is maintained for merge tracking +- A daily divergence check runs via `cinny-upstream-check.sh` on LXC 106 +- This enables prompt review of upstream security patches and feature releases + +### Rolldown CJS Interop โ€” millify + +`src/app/plugins/millify.ts` re-exports `millify` as a named import to bypass the `__toESM` interop bug in the Rolldown bundler, which caused the default export of CJS modules to be undefined at runtime. + +### Sentry Noise Filter + +`ignoreErrors: ['Request timed out']` is added to `Sentry.init()` to suppress a high-volume, low-signal error caused by transient network conditions. This keeps the Sentry issue queue focused on actionable errors. + +### URL Preview Default in Encrypted Rooms + +The `encUrlPreview` setting defaults to `true` rather than `false`. A security advisory chip in **Settings โ†’ Privacy** explains the tradeoff (the homeserver can see which URLs are being previewed) so users can make an informed choice. + +--- + +## Key Custom Files + +| File | Purpose | +|---|---| +| `src/lotus-terminal.css.ts` | TDS CSS variable definitions, scanline/vignette effects, dark + light palette tokens | +| `src/lotus-boot.ts` | Matrix-style boot sequence animation on the welcome page | +| `src/app/hooks/useRoomReadPositions.ts` | Reactive hook returning `Map` for per-message read receipts | +| `src/app/features/room/ReadPositionsContext.ts` | React context providing read positions map to timeline components | +| `src/app/components/read-receipt-avatars/` | `ReadReceiptAvatars` component โ€” overlapping avatar pill with overflow count | +| `src/app/components/event-readers/EventReaders.tsx` | "Seen by" modal listing readers with display name and timestamp | +| `src/app/components/GifPicker.tsx` | Giphy-powered GIF picker, TDS-styled, gated on `gifApiKey` in config | +| `src/app/features/call/CallControls.tsx` | Push to Deafen (M key), PTT visual indicator, TDS typing dots | +| `src/app/plugins/call/CallControl.ts` | `onControlMutation()` state tracking, screenshare audio mute logic, call button scoping | +| `src/app/components/CallEmbedProvider.tsx` | PiP window, draggable overlay, navigate-on-click, imperative geometry sync | +| `src/app/plugins/call/CallEmbed.ts` | `getBoundingClientRect()`-based embed positioning, ResizeObserver sync, dark mode injection | +| `src/app/plugins/millify.ts` | Named re-export of `millify` to fix Rolldown `__toESM` CJS interop bug | +| `src/app/features/room/MediaGallery.tsx` | Images/Videos/Files gallery drawer with pagination and E2EE awareness | +| `src/app/features/room/PollCreator.tsx` | Poll creation UI for stable `m.poll.start`, single/multiple choice, 2โ€“10 options | +| `src/app/features/common-settings/general/RoomShareInvite.tsx` | QR code + copy link invite sharing modal | +| `src/app/utils/syntaxHighlight.ts` | TDS-aware syntax highlighter using `--lt-accent-*` inline styles | +| `src/app/features/room-settings/ExportRoomHistory.tsx` | Plain Text / JSON / HTML room history export with date range and E2EE support | +| `src/app/features/room-settings/RoomActivityLog.tsx` | Human-readable mod log for member and state change events | +| `src/app/features/room-settings/RoomServerACL.tsx` | `m.room.server_acl` editor with allow/deny lists and wildcard validation | +| `src/app/features/room-settings/RoomInsights.tsx` | Room stats: top members, top reactions, media breakdown, activity heatmap | +| `src/app/features/bookmarks/BookmarksPanel.tsx` | Bookmarks sidebar panel backed by `io.lotus.bookmarks` account data | +| `src/app/hooks/useBookmarks.ts` | Hook for reading and mutating the bookmarks account data entry | +| `src/app/features/room/ScheduleMessageModal.tsx` | MSC4140 delayed event scheduling UI with date/time picker | +| `src/app/utils/scheduledMessages.ts` | Helpers for creating, listing, and cancelling MSC4140 delayed events | +| `src/app/hooks/useExtendedProfile.ts` | MSC4133 extended profile fields (`m.pronouns`, `m.tz`) read/write | +| `src/app/hooks/useLocalTime.ts` | Derives current local time from `m.tz` profile field, updates every 60s | +| `src/app/components/url-preview/UrlPreviewCard.tsx` | 13 domain-specific URL preview layouts plus generic fallback with favicon | diff --git a/LOTUS_TODO.md b/LOTUS_TODO.md index 625e1fa05..ee455a25c 100644 --- a/LOTUS_TODO.md +++ b/LOTUS_TODO.md @@ -1,12 +1,8 @@ -# Lotus Chat โ€” Master Feature TODO +# Lotus Chat โ€” Work Backlog -**Location:** `/root/code/cinny/LOTUS_TODO.md` **Repo:** `lotus` branch at `https://code.lotusguild.org/LotusGuild/cinny` **Deploy:** push to `lotus` โ†’ CI โ†’ auto-deploy to `chat.lotusguild.org` (~11 min) -This file is the single source of truth for all planned, in-progress, and backlog features. -Update it as features are completed or reprioritized. - --- ## โš ๏ธ TDS DESIGN LAW โ€” READ BEFORE TOUCHING ANY UI @@ -19,6 +15,10 @@ Update it as features are completed or reprioritized. --- +Completed features are documented in [LOTUS_FEATURES.md](./LOTUS_FEATURES.md). + +--- + Legend: - `[AUDIT REQUIRED]` โ€” at least one assumption needs code/server verification before implementing @@ -32,103 +32,44 @@ Status: `[ ]` pending ยท `[~]` in progress ยท `[x]` completed --- -## AUDIT RESULTS โ€” completed June 2026 +## Server Capabilities (as of June 2026) -### Server Status +- **Homeserver:** `matrix.lotusguild.org` +- **Synapse version:** `1.153.0` (2026-05-19) โ€” fully up to date +- **Matrix spec:** up to `v1.12` formally; newer MSC features via `unstable_features` -- **Synapse version:** `1.153.0` (released 2026-05-19) โ€” **FULLY UP TO DATE**, no upgrade needed -- **Matrix spec reported:** up to `v1.12` formally, but newer MSC features available via `unstable_features` -- **MSC feature flags confirmed ON:** `msc4140` (delayed messages) ยท `msc3771` (thread receipts) ยท `msc3440.stable` (threading) ยท `msc4133.stable` (extended profiles) ยท `simplified_msc3575` (sliding sync) -- **MSC feature flags confirmed OFF:** `msc4306` (thread subscriptions โ€” BLOCKED) ยท `msc3882` ยท `msc3912` ยท `msc4155` -- **MSC3266** (room summary, v1.15): endpoint returned 404 โ€” NOT available on this server -- **MSC3765** (rich room topics, v1.15): NOT available as stable, but client-side rendering is still worth doing -- **MSC3892** (relation redaction): not listed in flags โ€” NOT supported, feature BLOCKED -- **MSC4260** (report user, v1.14): server at v1.12 formally โ€” NOT available as spec endpoint; **however** report user already exists upstream in Cinny (message reporting via `reportEvent`) -- **MSC4151** (report room, v1.12): merged at exactly v1.12 โ€” should be available โœ… - -### Upstream Cinny Features Confirmed (exist in upstream โ€” only improve, don't re-implement) - -| Feature | Location in upstream | Lotus improvement task | -| ------------------------------------ | ---------------------------------------------------- | ----------------------------------- | -| "Jump to Latest" button | `RoomTimeline.tsx:2180-2192` | #104 โ€” add unread count + animation | -| Mark rooms as read (per section) | `Home.tsx:73-102`, `DirectTab.tsx:29-61` | None needed | -| Room upgrade / tombstone banner | `RoomTombstone.tsx`, `RoomUpgrade.tsx` | None needed | -| Visual speaking indicator | `useCallSpeakers.ts:8-60`, `MemberSpeaking.tsx:1-78` | #107 โ€” TDS animated ring | -| Image + video spoilers (blur/reveal) | `ImageContent.tsx`, `VideoContent.tsx` | #105 โ€” smooth CSS transition | -| Report message per event | `Message.tsx:588-709` โ€” `mx.reportEvent()` | #106 โ€” add category selector | -| Drag-and-drop file upload | `useFileDrop.ts` โ€” works but overlay bug | #73 โ€” fix overlay dismiss | -| Typing indicator (animated dots) | `TypingIndicator.tsx`, `TypingIndicator.css.ts` | #108 โ€” TDS orange dots โœ… June 2026 | -| Pinned messages count badge | Confirmed in upstream header icon | None needed | - -### Upstream Cinny Features Confirmed MISSING (we should build these) - -Quick Switcher, Sidebar filter, Favorite rooms, Invite link generator, Edit history modal, -Export history, Room preview before joining, Suggested rooms display, Server notices styling, -DM last-message preview, Media gallery, Knock-to-join full UX - -### Code Architecture Facts โ€” Full Audit Results - -#### Corrected facts (previous version had errors) - -| Old assumption | Correct finding | -| ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| ~~Join/leave sounds need m.call.member state events~~ | **Use `useCallMembersChange()` hook** at `src/app/hooks/useCall.ts:37-52` โ€” subscribes to `MatrixRTCSessionEvent.MembershipsChanged`, receives old/new membership arrays | -| ~~Glassmorphism needs parent wrapper (translateX blocks filter)~~ | **SAFE to apply directly to sidebar container** โ€” `translateX` only on `SidebarItem` hover, not the sidebar container. Apply backdrop-filter to `Sidebar.css.ts:6-17` | -| ~~Avatar frames must wrap externally~~ | **Modify `UserAvatar` internally** โ€” add optional `frameName?: string` prop; renders overlay inside component. Avoids 3 different wrapper implementations | -| ~~JetBrains Mono not bundled~~ | **Already loaded via Google Fonts CDN** in `index.html:33-35`. Other fonts: use `@fontsource` npm packages | -| ~~Animated backgrounds: add to backgroundImage~~ | **backgroundImage can't have @keyframes** โ€” use CSS `::before` pseudo-element on `` component with `position:absolute, inset:0, z-index:-1` | - -#### Confirmed facts (all audits complete as of June 2026) +### Confirmed facts | Finding | Impact | | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | -| `folds AvatarImage` does NOT accept children | Add frame/overlay inside `UserAvatar.tsx` itself โ€” optional `frameName` prop | -| No in-app toast system exists | Task #80: build `ToastProvider` + Jotai queue; insert at `App.tsx:65` after `OverlayContainerProvider`; `portalContainer` in `index.html:101` | -| `useUnverifiedDeviceCount()` hook EXISTS | Task #65 is trivial: `src/app/hooks/useDeviceVerificationStatus.ts:65-106` | -| Voice player: `AudioContent.tsx:44-223` | Task #8: `playbackRate` on hidden `