fix: ESLint errors and Prettier formatting
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:
+34
-30
@@ -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
@@ -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, 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 |
|
||||
| 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, 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 |
|
||||
|
||||
+26
-26
@@ -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
@@ -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
@@ -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" />
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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`,
|
||||
}
|
||||
: {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
: {}),
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user