fix: ESLint errors and Prettier formatting
CI / Build & Quality Checks (push) Successful in 10m27s
Trigger Desktop Build / trigger (push) Successful in 5s

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>
This commit is contained in:
2026-06-10 22:55:32 -04:00
parent b41bfd35c0
commit 46567555e1
14 changed files with 281 additions and 215 deletions
+34 -30
View File
@@ -1,4 +1,5 @@
# Lotus Chat — Bug Report & Technical Audit
**Date:** June 2026
This document tracks identified bugs, edge cases, and architectural discrepancies.
@@ -7,66 +8,69 @@ This document tracks identified bugs, edge cases, and architectural discrepancie
## ✅ Resolved Issues (Recently Fixed)
* **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).
* **Presence Updater Wipes Custom Status**: Fixed in `usePresenceUpdater.ts` (removed `status_msg: ''`).
* **Manifest Main Icon Paths 404**: Fixed in `public/manifest.json` (`./public/android/``./res/android/`). Shortcut icon was already correct.
- **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).
- **Presence Updater Wipes Custom Status**: Fixed in `usePresenceUpdater.ts` (removed `status_msg: ''`).
- **Manifest Main Icon Paths 404**: Fixed in `public/manifest.json` (`./public/android/``./res/android/`). Shortcut icon was already correct.
---
## 🛡️ Critical Security & Logic
### 1. Edit History Broken for E2EE
**File:** `src/app/features/room/message/EditHistoryModal.tsx`
**Status:** **OPEN**
* **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.
- **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. Service Worker Ephemeral Sessions
**File:** `src/sw.ts`
**Status:** **OPEN**
* **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.
- **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.
---
## 📱 PWA & Mobile Issues
### 1. No PWA Precaching (Offline Mode Broken)
**File:** `src/sw.ts`, `vite.config.js`
**Status:** **OPEN**
* **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`.
- **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. PiP Resize Impossible on Mobile
**File:** `src/app/components/CallEmbedProvider.tsx`
**Status:** **OPEN**
* **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.
- **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. 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.
- **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.
+78 -57
View File
@@ -47,6 +47,7 @@ Last updated: June 2026.
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`
@@ -61,14 +62,17 @@ A CRT terminal aesthetic applied globally when the TDS theme is active.
| `--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`
@@ -88,6 +92,7 @@ A full light-palette counterpart to the dark TDS theme.
| `--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 `<html>` element when theme changes
@@ -155,6 +160,7 @@ A "Pause Background Animations" toggle is exposed in **Settings → Appearance**
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)
@@ -166,6 +172,7 @@ An optional frosted-glass sidebar style toggled in **Settings → Appearance**.
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
@@ -288,6 +295,7 @@ A pill of overlapping 24px user avatars displayed at the bottom-right of each me
### "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
@@ -299,11 +307,11 @@ Clicking the avatar pill opens a modal (`src/app/components/event-readers/EventR
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 |
| 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.
@@ -316,7 +324,7 @@ The indicator is hidden once the server confirms the event (when the internal st
- 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
- The room settings topic editor includes a formatting toolbar with **B**, _I_, ~~S~~, and `code` buttons
### Edit History Viewer
@@ -395,21 +403,21 @@ Redacted events display "This message has been deleted" along with the redaction
`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 |
| 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.
@@ -436,13 +444,13 @@ Generic (non-domain-specific) cards display a Google S2 favicon. Empty or unpars
A presence status selector in the user panel offering five modes:
| Mode | Matrix broadcast |
|---|---|
| Online | `online` |
| Idle | `unavailable` |
| Mode | Matrix broadcast |
| -------------- | ----------------------------------- |
| Online | `online` |
| Idle | `unavailable` |
| Do Not Disturb | `unavailable` + `status_msg: 'dnd'` |
| Invisible | `offline` |
| Auto | Standard Matrix presence lifecycle |
| 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
@@ -457,6 +465,7 @@ A presence status selector in the user panel offering five modes:
### Presence Badges
`PresenceBadge` component displays a colored dot indicating presence state. Used in:
- Members drawer
- User settings panel
@@ -465,6 +474,7 @@ A presence status selector in the user panel offering five modes:
`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
@@ -473,6 +483,7 @@ Applied in:
### 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
@@ -480,6 +491,7 @@ The browser tab title updates to reflect unread state:
### 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`)
@@ -488,6 +500,7 @@ 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
@@ -509,6 +522,7 @@ A hover toolbar appears over messages, showing the 3 most recently used emojis (
### In-App Notification Toasts
`LotusToastContainer.tsx` displays rich in-app notification toasts with:
- 24px sender avatar
- Sender display name
- Message body preview
@@ -531,6 +545,7 @@ Messages exceeding a configurable line threshold are truncated with a "Show more
### 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
@@ -539,6 +554,7 @@ A subtle animation plays on the sender's own messages as they appear in the time
### 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
@@ -558,6 +574,7 @@ A text input at the top of the room list filters rooms by display name in real t
### 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`
@@ -566,6 +583,7 @@ Direct message entries in the sidebar show:
### Room Sort Order
The room list sort order can be configured in **Settings → Appearance**:
- Recent Activity (default)
- A → Z (alphabetical)
- Unread First
@@ -582,6 +600,7 @@ Persists via the `homeRoomSort` setting.
### 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
@@ -643,6 +662,7 @@ Users can individually show or hide each composer toolbar button in **Settings
- **HTML** — styled, self-contained page
Features:
- Optional date range filter
- Progress indicator during export
- Uses `mx.paginateEventTimeline()` to retrieve history in chunks
@@ -747,6 +767,7 @@ Three one-tap presets at the top of **Settings → Notifications** that apply a
### 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
@@ -760,7 +781,7 @@ Three one-tap presets at the top of **Settings → Notifications** that apply a
`mxcUrlToHttp()` calls now use the correct argument order for MSC3916 authenticated media:
```ts
mxcUrlToHttp(mx, mxcUrl, useAuthentication, width, height, 'crop')
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.
@@ -787,32 +808,32 @@ The `encUrlPreview` setting defaults to `true` rather than `false`. A security a
## 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<eventId, userId[]>` 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, 210 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 |
| 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<eventId, userId[]>` 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, 210 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 |
+26 -26
View File
@@ -40,32 +40,32 @@ Status: `[ ]` pending · `[~]` in progress · `[x]` completed
### 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 |
| 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 |
---
+69 -59
View File
@@ -1,4 +1,5 @@
# Lotus Chat — Implementation Reference for Backlog
**Date:** June 2026
This document provides technical guidance, file paths, and architectural notes for unimplemented items in `LOTUS_TODO.md` to assist engineers during development.
@@ -8,92 +9,101 @@ This document provides technical guidance, file paths, and architectural notes f
## 🧵 Priority 3 — Higher Complexity
### P3-8 · Thread Panel (Full Side Drawer)
**⚠️ Largest Feature**
* **Objective:** Add a right-side drawer to view and reply to threads (`m.thread` relations).
* **Key Files to Reference:**
* `src/app/features/room/RoomView.tsx`: Main layout. Needs to render the new `ThreadPanel` component conditionally.
* `src/app/features/room/MembersDrawer.tsx`: Use this as a pattern for side drawers (fixed width, toggleable).
* `src/app/features/room/message/Message.tsx`: Check `isThreadedMessage` logic and the `onReplyClick(ev, true)` handler.
* **Architecture:**
* Create `activeThreadEventIdAtom` in a new state file.
* `ThreadPanel` should reuse `Timeline` components but filter for events where `m.relates_to.event_id === activeThreadEventId` and `rel_type === 'm.thread'`.
* **SDK API:** Use `mx.getThread(eventId)` or the aggregations API: `GET /rooms/{roomId}/relations/{eventId}/m.thread`.
* **Note:** `RoomTimeline.tsx` currently has `handleReplyClick` (Line 978) which already supports starting threads.
- **Objective:** Add a right-side drawer to view and reply to threads (`m.thread` relations).
- **Key Files to Reference:**
- `src/app/features/room/RoomView.tsx`: Main layout. Needs to render the new `ThreadPanel` component conditionally.
- `src/app/features/room/MembersDrawer.tsx`: Use this as a pattern for side drawers (fixed width, toggleable).
- `src/app/features/room/message/Message.tsx`: Check `isThreadedMessage` logic and the `onReplyClick(ev, true)` handler.
- **Architecture:**
- Create `activeThreadEventIdAtom` in a new state file.
- `ThreadPanel` should reuse `Timeline` components but filter for events where `m.relates_to.event_id === activeThreadEventId` and `rel_type === 'm.thread'`.
- **SDK API:** Use `mx.getThread(eventId)` or the aggregations API: `GET /rooms/{roomId}/relations/{eventId}/m.thread`.
- **Note:** `RoomTimeline.tsx` currently has `handleReplyClick` (Line 978) which already supports starting threads.
---
## 🛠️ Priority 4 — Specialized Features
### P4-3 · Knock-to-join Notifications for Admins
* **Objective:** Alert admins when users are knocking and provide an easy way to approve/deny.
* **Key Files:**
* `src/app/features/room/MembersDrawer.tsx`: Already contains logic to show "Pending Requests" (Line 412).
* `src/app/hooks/useRoomsNotificationPreferences.ts`: Add logic to detect `Membership.Knock` events in joined rooms where the user has invite permissions.
* **Implementation:**
* Create a hook `usePendingKnocks(room)` that returns `room.getMembersWithMembership(Membership.Knock)`.
* Add a notification badge to the "Members" icon in the room header if knocks > 0.
- **Objective:** Alert admins when users are knocking and provide an easy way to approve/deny.
- **Key Files:**
- `src/app/features/room/MembersDrawer.tsx`: Already contains logic to show "Pending Requests" (Line 412).
- `src/app/hooks/useRoomsNotificationPreferences.ts`: Add logic to detect `Membership.Knock` events in joined rooms where the user has invite permissions.
- **Implementation:**
- Create a hook `usePendingKnocks(room)` that returns `room.getMembersWithMembership(Membership.Knock)`.
- Add a notification badge to the "Members" icon in the room header if knocks > 0.
### P4-4 · Math / LaTeX Rendering
* **Objective:** Render `$...$` and `$$...$$` blocks using KaTeX.
* **Key Files:**
* `src/app/utils/sanitize.ts`: **Critical.** The sanitizer currently strips many tags. You must allow specific KaTeX/MathML outputs.
* `src/app/plugins/react-custom-html-parser.ts`: Add a custom rule to detect LaTeX patterns in plain text or handle the specific HTML from the server.
* `src/app/styles/CustomHtml.css.ts`: Add KaTeX CSS import/styles.
- **Objective:** Render `$...$` and `$$...$$` blocks using KaTeX.
- **Key Files:**
- `src/app/utils/sanitize.ts`: **Critical.** The sanitizer currently strips many tags. You must allow specific KaTeX/MathML outputs.
- `src/app/plugins/react-custom-html-parser.ts`: Add a custom rule to detect LaTeX patterns in plain text or handle the specific HTML from the server.
- `src/app/styles/CustomHtml.css.ts`: Add KaTeX CSS import/styles.
---
## 🎨 Priority 5 — Gamer / Aesthetic / Customization
### P5-1 · Custom Accent Color Picker
* **Objective:** User-defined accent color for non-TDS themes.
* **Key Files:**
* `src/app/hooks/useTheme.ts`: Central theme logic.
* `src/app/state/settings.ts`: Add `customAccentColor: string` to the settings atom.
* **Implementation:**
* Inject a `<style>` block into `<head>` that overrides CSS variables.
* **Variables to target:** `--lt-accent-orange`, `--lt-accent-cyan`, `--lt-accent-green` (or their non-TDS equivalents in `folds`).
- **Objective:** User-defined accent color for non-TDS themes.
- **Key Files:**
- `src/app/hooks/useTheme.ts`: Central theme logic.
- `src/app/state/settings.ts`: Add `customAccentColor: string` to the settings atom.
- **Implementation:**
- Inject a `<style>` block into `<head>` that overrides CSS variables.
- **Variables to target:** `--lt-accent-orange`, `--lt-accent-cyan`, `--lt-accent-green` (or their non-TDS equivalents in `folds`).
### P5-10 · Voice Channel User Limit
* **Objective:** Prevent joining a voice channel if the user limit is reached.
* **Key Files:**
* `src/app/features/call/CallView.tsx`: Join logic site.
* **Implementation:**
* In `CallPrescreen` (Line 77), retrieve the `io.lotus.voice_limit` state event from the room.
* Compare `callMembers.length` with the `max_users` value from the event content.
* If current members >= limit, disable the `canJoin` flag and display a "Channel Full" message.
- **Objective:** Prevent joining a voice channel if the user limit is reached.
- **Key Files:**
- `src/app/features/call/CallView.tsx`: Join logic site.
- **Implementation:**
- In `CallPrescreen` (Line 77), retrieve the `io.lotus.voice_limit` state event from the room.
- Compare `callMembers.length` with the `max_users` value from the event content.
- If current members >= limit, disable the `canJoin` flag and display a "Channel Full" message.
### P5-13 · Avatar Frame / Border Decorations
* **Objective:** Add cosmetic frames around user avatars.
* **Key Files:**
* `src/app/components/user-avatar/UserAvatar.tsx`: Rendering site.
* **Implementation:**
* Add an optional `frameName` prop to the `UserAvatar` component.
* Since `folds` components like `AvatarImage` are restrictive, wrap the entire return value (both fallback and image paths) in a new `Box` container that applies the frame/glow effects via CSS.
- **Objective:** Add cosmetic frames around user avatars.
- **Key Files:**
- `src/app/components/user-avatar/UserAvatar.tsx`: Rendering site.
- **Implementation:**
- Add an optional `frameName` prop to the `UserAvatar` component.
- Since `folds` components like `AvatarImage` are restrictive, wrap the entire return value (both fallback and image paths) in a new `Box` container that applies the frame/glow effects via CSS.
### P5-21 · Custom @Mention Highlight Color
* **Objective:** Persistent background highlight for messages that mention the user.
* **Key Files:**
* `src/app/components/message/layout/layout.css.ts`: Styling site.
* `src/app/features/room/message/Message.tsx`: Logic site.
* **Implementation:**
* In `layout.css.ts`, add a `mention` variant to the `MessageBase` recipe that sets a static `backgroundColor`.
* In `Message.tsx`, pass the `isMentioned` boolean (Line 800) into the `MessageBase` component as a new prop to trigger the highlight variant.
- **Objective:** Persistent background highlight for messages that mention the user.
- **Key Files:**
- `src/app/components/message/layout/layout.css.ts`: Styling site.
- `src/app/features/room/message/Message.tsx`: Logic site.
- **Implementation:**
- In `layout.css.ts`, add a `mention` variant to the `MessageBase` recipe that sets a static `backgroundColor`.
- In `Message.tsx`, pass the `isMentioned` boolean (Line 800) into the `MessageBase` component as a new prop to trigger the highlight variant.
### P5-20 · Quick Reply from Browser Notification
* **Objective:** Inline reply in OS notifications.
* **Key Files:**
* `src/sw.ts`: Handle the `notificationclick` event.
* **Implementation:**
* Check for `event.reply` in the service worker.
* Use the `accessToken` and `baseUrl` stored in the `sessions` map (already implemented in `sw.ts`) to send a Matrix message via `fetch` directly from the Service Worker.
* **Crucial:** Ensure the message is sent as a relation if the notification was for a thread.
- **Objective:** Inline reply in OS notifications.
- **Key Files:**
- `src/sw.ts`: Handle the `notificationclick` event.
- **Implementation:**
- Check for `event.reply` in the service worker.
- Use the `accessToken` and `baseUrl` stored in the `sessions` map (already implemented in `sw.ts`) to send a Matrix message via `fetch` directly from the Service Worker.
- **Crucial:** Ensure the message is sent as a relation if the notification was for a thread.
---
## 🧪 Pending Audits Guidance
### Audit-3 · Profile Banner Image
* **Task:** Check if MSC4133 or Matrix v1.16 defines a banner field.
* **Update:** Matrix spec does not currently have a stable `m.banner` field. Most clients use `org.matrix.msc4133.banner_url` (unstable).
* **Recommendation:** Use `mx.http.authedRequest` to experiment with this field on `matrix.lotusguild.org`.
- **Task:** Check if MSC4133 or Matrix v1.16 defines a banner field.
- **Update:** Matrix spec does not currently have a stable `m.banner` field. Most clients use `org.matrix.msc4133.banner_url` (unstable).
- **Recommendation:** Use `mx.http.authedRequest` to experiment with this field on `matrix.lotusguild.org`.
+1 -4
View File
@@ -29,10 +29,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=VT323&display=swap"
rel="stylesheet"
/>
<link href="https://fonts.googleapis.com/css2?family=VT323&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="/fonts/custom-fonts.css" />
<link id="favicon" rel="shortcut icon" href="./public/favicon.ico" />
+15 -5
View File
@@ -5,7 +5,9 @@
font-weight: 400;
font-display: swap;
src: url('/fonts/JetBrainsMono-italic-400.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'JetBrains Mono';
@@ -13,7 +15,9 @@
font-weight: 400;
font-display: swap;
src: url('/fonts/JetBrainsMono-normal-400.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'JetBrains Mono';
@@ -21,7 +25,9 @@
font-weight: 700;
font-display: swap;
src: url('/fonts/JetBrainsMono-normal-700.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Fira Code';
@@ -29,7 +35,9 @@
font-weight: 400;
font-display: swap;
src: url('/fonts/FiraCode-400.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Fira Code';
@@ -37,5 +45,7 @@
font-weight: 600;
font-display: swap;
src: url('/fonts/FiraCode-600.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
+2 -1
View File
@@ -86,7 +86,8 @@ export function GifPicker({ apiKey, onSelect, requestClose }: GifPickerProps) {
border: '1px solid color-mix(in srgb, var(--lt-accent-orange) 35%, transparent)',
borderRadius: '4px',
overflow: 'hidden',
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)',
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`,
}
: {
+5 -1
View File
@@ -214,7 +214,11 @@ export function VoiceMessageRecorder({ onSend, onError }: VoiceRecorderProps) {
minWidth: toRem(32),
fontVariantNumeric: 'tabular-nums',
...(lotusTerminal
? { fontFamily: 'JetBrains Mono, monospace', color: 'var(--lt-accent-green)', fontWeight: 700 }
? {
fontFamily: 'JetBrains Mono, monospace',
color: 'var(--lt-accent-green)',
fontWeight: 700,
}
: {}),
}}
>
+3 -3
View File
@@ -423,7 +423,7 @@ export function RoomViewHeader({ callView }: { callView?: boolean }) {
const pinnedEvents = useRoomPinnedEvents(room);
const encryptionEvent = useStateEvent(room, StateEvent.RoomEncryption);
const encryptedRoom = !!encryptionEvent;
const _encryptedRoom = !!encryptionEvent;
const avatarMxc = useRoomAvatar(room, direct);
const name = useLocalRoomName(room);
const topic = useRoomTopic(room);
@@ -558,7 +558,7 @@ export function RoomViewHeader({ callView }: { callView?: boolean }) {
</Box>
<Box shrink="No">
{(
{
<TooltipProvider
position="Bottom"
offset={4}
@@ -579,7 +579,7 @@ export function RoomViewHeader({ callView }: { callView?: boolean }) {
</IconButton>
)}
</TooltipProvider>
)}
}
<TooltipProvider
position="Bottom"
offset={4}
@@ -738,7 +738,9 @@ function ProfileTimezone() {
const [savedTimezone, setSavedTimezone] = useState<string>('');
useEffect(() => {
const cached = mx.getAccountData('im.lotus.timezone' as any)?.getContent<{ timezone: string }>();
const cached = mx
.getAccountData('im.lotus.timezone' as any)
?.getContent<{ timezone: string }>();
if (cached?.timezone) {
setTimezone(cached.timezone);
setSavedTimezone(cached.timezone);
+15 -5
View File
@@ -509,9 +509,7 @@ function Appearance() {
<select
value={fontFamily ?? 'inter'}
onChange={(e) =>
setFontFamily(
e.target.value as 'system' | 'inter' | 'jetbrains-mono' | 'fira-code',
)
setFontFamily(e.target.value as 'system' | 'inter' | 'jetbrains-mono' | 'fira-code')
}
style={{
background: 'var(--bg-surface)',
@@ -541,7 +539,14 @@ function Appearance() {
type="color"
value={mentionHighlightColor || '#4caf50'}
onChange={(e) => setMentionHighlightColor(e.target.value)}
style={{ width: '36px', height: '28px', cursor: 'pointer', borderRadius: '4px', border: 'none', padding: '2px' }}
style={{
width: '36px',
height: '28px',
cursor: 'pointer',
borderRadius: '4px',
border: 'none',
padding: '2px',
}}
/>
{mentionHighlightColor && (
<button
@@ -971,7 +976,12 @@ function Editor() {
after={<Switch variant="Primary" value={editorToolbar} onChange={setEditorToolbar} />}
/>
</SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column" gap="300">
<SequenceCard
className={SequenceCardStyle}
variant="SurfaceVariant"
direction="Column"
gap="300"
>
<SettingTile
title="Composer Toolbar"
description="Tap a button to show or hide it in the message composer."
@@ -88,8 +88,14 @@ function NotificationPresets() {
}}
>
<span style={{ fontSize: toRem(24) }}>{preset.emoji}</span>
<Text size="T300" style={{ fontWeight: 600 }}>{preset.label}</Text>
<Text size="T200" priority="300" style={{ textAlign: 'center', maxWidth: toRem(120) }}>
<Text size="T300" style={{ fontWeight: 600 }}>
{preset.label}
</Text>
<Text
size="T200"
priority="300"
style={{ textAlign: 'center', maxWidth: toRem(120) }}
>
{preset.description}
</Text>
</button>
+17 -14
View File
@@ -17,22 +17,26 @@ export function usePresenceUpdater() {
useEffect(() => {
const userId = mx.getUserId();
const storedStatus = userId ? localStorage.getItem(`lotus-status-msg-${userId}`) ?? '' : '';
const storedStatus = userId ? (localStorage.getItem(`lotus-status-msg-${userId}`) ?? '') : '';
const setOnline = () =>
mx.setPresence({
presence: 'online',
...(storedStatus ? { status_msg: storedStatus } : {}),
}).catch(() => undefined);
mx
.setPresence({
presence: 'online',
...(storedStatus ? { status_msg: storedStatus } : {}),
})
.catch(() => undefined);
const setUnavailable = (statusMsg?: string) =>
mx.setPresence({
presence: 'unavailable',
...(statusMsg
? { status_msg: statusMsg }
: storedStatus
? { status_msg: storedStatus }
: {}),
}).catch(() => undefined);
mx
.setPresence({
presence: 'unavailable',
...(statusMsg
? { status_msg: statusMsg }
: storedStatus
? { status_msg: storedStatus }
: {}),
})
.catch(() => undefined);
const setOffline = () =>
mx.setPresence({ presence: 'offline', status_msg: '' }).catch(() => undefined);
@@ -88,7 +92,6 @@ export function usePresenceUpdater() {
};
const handlePageHide = () => {
const userId = mx.getUserId();
const token = mx.getAccessToken();
const baseUrl = mx.getHomeserverUrl();
if (!userId || !token || !baseUrl) return;
+5 -7
View File
@@ -15,13 +15,11 @@ export const getRecentEmojis = (mx: MatrixClient, limit?: number): IEmoji[] => {
const recentEmoji = recentEmojiEvent?.getContent<IRecentEmojiContent>().recent_emoji;
if (!Array.isArray(recentEmoji)) return [];
return recentEmoji
.slice(0, limit)
.reduce<IEmoji[]>((list, [unicode]) => {
const emoji = emojis.find((e) => e.unicode === unicode);
if (emoji) list.push(emoji);
return list;
}, []);
return recentEmoji.slice(0, limit).reduce<IEmoji[]>((list, [unicode]) => {
const emoji = emojis.find((e) => e.unicode === unicode);
if (emoji) list.push(emoji);
return list;
}, []);
};
export function addRecentEmoji(mx: MatrixClient, unicode: string) {