| ~~Join/leave sounds need m.call.member state events~~ | **Use `useCallMembersChange()` hook** at `src/app/hooks/useCall.ts:37-52` — subscribes to `MatrixRTCSessionEvent.MembershipsChanged`, receives old/new membership arrays |
| ~~Join/leave sounds need m.call.member state events~~ | **Use `useCallMembersChange()` hook** at `src/app/hooks/useCall.ts:37-52` — subscribes to `MatrixRTCSessionEvent.MembershipsChanged`, receives old/new membership arrays |
| ~~Glassmorphism needs parent wrapper (translateX blocks filter)~~ | **SAFE to apply directly to sidebar container** — `translateX` only on `SidebarItem` hover, not the sidebar container. Apply backdrop-filter to `Sidebar.css.ts:6-17` |
| ~~Glassmorphism needs parent wrapper (translateX blocks filter)~~ | **SAFE to apply directly to sidebar container** — `translateX` only on `SidebarItem` hover, not the sidebar container. Apply backdrop-filter to `Sidebar.css.ts:6-17` |
@@ -74,8 +79,9 @@ DM last-message preview, Media gallery, Knock-to-join full UX
| ~~Animated backgrounds: add to backgroundImage~~ | **backgroundImage can't have @keyframes** — use CSS `::before` pseudo-element on `<Page>` component with `position:absolute, inset:0, z-index:-1` |
| ~~Animated backgrounds: add to backgroundImage~~ | **backgroundImage can't have @keyframes** — use CSS `::before` pseudo-element on `<Page>` component with `position:absolute, inset:0, z-index:-1` |
#### Confirmed facts (all audits complete as of June 2026)
#### Confirmed facts (all audits complete as of June 2026)
| `folds AvatarImage` does NOT accept children | Add frame/overlay inside `UserAvatar.tsx` itself — optional `frameName` prop |
| `folds AvatarImage` does NOT accept children | Add frame/overlay inside `UserAvatar.tsx` itself — optional `frameName` prop |
| No in-app toast system exists | Task #80: build `ToastProvider` + Jotai queue; insert at `App.tsx:65` after `OverlayContainerProvider`; `portalContainer` in `index.html:101` |
| No in-app toast system exists | Task #80: build `ToastProvider` + Jotai queue; insert at `App.tsx:65` after `OverlayContainerProvider`; `portalContainer` in `index.html:101` |
| All room IDs atom | `src/app/state/room-list/roomList.ts` | `allRoomsAtom` |
| All room IDs atom | `src/app/state/room-list/roomList.ts` | `allRoomsAtom` |
@@ -169,13 +176,16 @@ Attack these first; most are single-file changes or simple new components.
---
---
### [ ] P0-1 · Report Room (MSC4151)
### [ ] P0-1 · Report Room (MSC4151)
**Spec:** MSC4151, merged ~Matrix spec v1.12. Synapse supports it.
**Spec:** MSC4151, merged ~Matrix spec v1.12. Synapse supports it.
**Confirmed:** NOT present in upstream Cinny mainline.
**Confirmed:** NOT present in upstream Cinny mainline.
**What:** Add a "Report Room" option in the room header context menu (⋮ or room settings). Opens a modal with a reason text field (required) and a Submit button. Calls:
**What:** Add a "Report Room" option in the room header context menu (⋮ or room settings). Opens a modal with a reason text field (required) and a Submit button. Calls:
```
```
POST /_matrix/client/v3/rooms/{roomId}/report
POST /_matrix/client/v3/rooms/{roomId}/report
Body: { "reason": "string" }
Body: { "reason": "string" }
```
```
The homeserver forwards the report to server admins.
The homeserver forwards the report to server admins.
**Where:**`src/app/features/room/RoomViewHeader.tsx` (add menu item), new `ReportRoomModal.tsx` component.
**Where:**`src/app/features/room/RoomViewHeader.tsx` (add menu item), new `ReportRoomModal.tsx` component.
**Complexity:** Low — one API call, one modal.
**Complexity:** Low — one API call, one modal.
@@ -183,12 +193,16 @@ The homeserver forwards the report to server admins.
---
---
### [ ] P0-2 · Server support contact display (MSC1929)
### [ ] P0-2 · Server support contact display (MSC1929)
**Spec:** MSC1929, stable in Matrix spec.
**Spec:** MSC1929, stable in Matrix spec.
**What:** On load (or when Settings → Help & About is opened), fetch:
**What:** On load (or when Settings → Help & About is opened), fetch:
```
```
GET /.well-known/matrix/support
GET /.well-known/matrix/support
```
```
Parse the `contacts` array and `support_page` URL. Display in Settings → Help & About:
Parse the `contacts` array and `support_page` URL. Display in Settings → Help & About:
Cache the response in component state; no repeated fetches.
Cache the response in component state; no repeated fetches.
@@ -198,9 +212,11 @@ Cache the response in component state; no repeated fetches.
---
---
### [ ] P0-3 · Server notices distinct rendering (m.server_notice)
### [ ] P0-3 · Server notices distinct rendering (m.server_notice)
**Spec:** CS-API §13.17, stable.
**Spec:** CS-API §13.17, stable.
**Audit result:** CONFIRMED MISSING. Only `M_CANNOT_LEAVE_SERVER_NOTICE_ROOM` error code exists in `src/app/cs-errorcode.ts` — no rendering differentiation. Server notices currently arrive as plain DMs.
**Audit result:** CONFIRMED MISSING. Only `M_CANNOT_LEAVE_SERVER_NOTICE_ROOM` error code exists in `src/app/cs-errorcode.ts` — no rendering differentiation. Server notices currently arrive as plain DMs.
**What:** The `m.server_notice` room type is set in `m.room.create` content (`type: 'm.server_notice'`). Detect it via `room.getType() === 'm.server_notice'`. Render with:
**What:** The `m.server_notice` room type is set in `m.room.create` content (`type: 'm.server_notice'`). Detect it via `room.getType() === 'm.server_notice'`. Render with:
- A distinct "Server Notice" header badge (server icon + label) in `RoomViewHeader.tsx`
- A distinct "Server Notice" header badge (server icon + label) in `RoomViewHeader.tsx`
- Slightly different background color (use `color.Warning` or neutral surface)
- Slightly different background color (use `color.Warning` or neutral surface)
- Composer hidden/disabled in server notice rooms (check room type in `RoomInput.tsx`)
- Composer hidden/disabled in server notice rooms (check room type in `RoomInput.tsx`)
@@ -210,6 +226,7 @@ Cache the response in component state; no repeated fetches.
**Server check result:**`org.matrix.msc3892` is NOT in the server's unstable_features list — **NOT supported**. Current full-event redaction behavior is correct and should not be changed. This task is BLOCKED until the homeserver adds MSC3892 support. No action needed now.
**Server check result:**`org.matrix.msc3892` is NOT in the server's unstable_features list — **NOT supported**. Current full-event redaction behavior is correct and should not be changed. This task is BLOCKED until the homeserver adds MSC3892 support. No action needed now.
**Complexity:** N/A — blocked.
**Complexity:** N/A — blocked.
@@ -217,6 +234,7 @@ Cache the response in component state; no repeated fetches.
**Server status:** Server reports v1.12 — MSC3765 not formally available. However, the `formatted_body` field on `m.room.topic` state events is part of the generic Matrix content structure and can be stored/read regardless of spec version. The rendering improvement is worthwhile to build now: it will activate whenever any room admin sets a formatted topic.
**Server status:** Server reports v1.12 — MSC3765 not formally available. However, the `formatted_body` field on `m.room.topic` state events is part of the generic Matrix content structure and can be stored/read regardless of spec version. The rendering improvement is worthwhile to build now: it will activate whenever any room admin sets a formatted topic.
**What:** Pipe the `formatted_body` of the `m.room.topic` state event through the existing `sanitizeCustomHtml()` + `html-react-parser` pipeline (same as message bodies). Fall back to plain `body` if no `formatted_body` present.
**What:** Pipe the `formatted_body` of the `m.room.topic` state event through the existing `sanitizeCustomHtml()` + `html-react-parser` pipeline (same as message bodies). Fall back to plain `body` if no `formatted_body` present.
@@ -226,6 +244,7 @@ Cache the response in component state; no repeated fetches.
---
---
### [ ] P0-6 · Edit history viewer
### [ ] P0-6 · Edit history viewer
**Spec:** CS-API §11.8.2 (stable). Edit history is stored as `m.replace` relation events.
**Spec:** CS-API §11.8.2 (stable). Edit history is stored as `m.replace` relation events.
**Audit result:** Confirmed NOT in upstream Cinny. The "edited" label shows on messages (Message.tsx:1231) but clicking it does nothing. `getEventEdits()` is used internally but no history modal exists.
**Audit result:** Confirmed NOT in upstream Cinny. The "edited" label shows on messages (Message.tsx:1231) but clicking it does nothing. `getEventEdits()` is used internally but no history modal exists.
**What:** Click the "edited" label → popover/modal lists every prior version with timestamps. Fetch via `GET /_matrix/client/v1/rooms/{roomId}/relations/{eventId}/m.replace`. Show each version's body + timestamp. Use folds `Overlay`+`Modal` pattern.
**What:** Click the "edited" label → popover/modal lists every prior version with timestamps. Fetch via `GET /_matrix/client/v1/rooms/{roomId}/relations/{eventId}/m.replace`. Show each version's body + timestamp. Use folds `Overlay`+`Modal` pattern.
@@ -235,6 +254,7 @@ Cache the response in component state; no repeated fetches.
---
---
### [BLOCKED] P0-7 · Room preview before joining (MSC3266)
### [BLOCKED] P0-7 · Room preview before joining (MSC3266)
**Spec:** MSC3266, merged Matrix spec v1.15.
**Spec:** MSC3266, merged Matrix spec v1.15.
**Server check result:**`GET /_matrix/client/v1/rooms/{roomId}/summary` returned **404** on `matrix.lotusguild.org`. Endpoint not available despite Synapse 1.153.0.
**Server check result:**`GET /_matrix/client/v1/rooms/{roomId}/summary` returned **404** on `matrix.lotusguild.org`. Endpoint not available despite Synapse 1.153.0.
**Audit result:** Confirmed NOT in upstream Cinny — no pre-join preview screen exists.
**Audit result:** Confirmed NOT in upstream Cinny — no pre-join preview screen exists.
@@ -244,6 +264,7 @@ Cache the response in component state; no repeated fetches.
---
---
### [ ] P0-8 · Personal room name overrides (MSC4431)
### [ ] P0-8 · Personal room name overrides (MSC4431)
**Spec:** MSC4431, still in review.
**Spec:** MSC4431, still in review.
**Server check:** Uses standard account data (`PUT /user/{userId}/account_data/`) which is universally supported regardless of MSC status. Safe to implement now; use `io.lotus.room_names` as the account data key until MSC4431 is finalized (may need to rename key when merged).
**Server check:** Uses standard account data (`PUT /user/{userId}/account_data/`) which is universally supported regardless of MSC status. Safe to implement now; use `io.lotus.room_names` as the account data key until MSC4431 is finalized (may need to rename key when merged).
**What:** Right-click room in sidebar → "Rename for me…" → input dialog → saves to account data. Show a small ✏ icon next to locally-renamed rooms. `useRoomName()` hook in `useRoomMeta.ts:19-35` — override its return value when a local rename exists for that roomId.
**What:** Right-click room in sidebar → "Rename for me…" → input dialog → saves to account data. Show a small ✏ icon next to locally-renamed rooms. `useRoomName()` hook in `useRoomMeta.ts:19-35` — override its return value when a local rename exists for that roomId.
@@ -253,17 +274,21 @@ Cache the response in component state; no repeated fetches.
---
---
### [UPSTREAM — REMOVED] P0-9 · "Back to Latest" button
### [UPSTREAM — REMOVED] P0-9 · "Back to Latest" button
**Audit result:** CONFIRMED UPSTREAM. `handleJumpToLatest()` + floating "Jump to Latest" Chip already exist in `RoomTimeline.tsx:2180-2192`. Task #104 (unread count + animation improvement) remains in the build queue.
**Audit result:** CONFIRMED UPSTREAM. `handleJumpToLatest()` + floating "Jump to Latest" Chip already exist in `RoomTimeline.tsx:2180-2192`. Task #104 (unread count + animation improvement) remains in the build queue.
---
---
### [UPSTREAM — REMOVED] P0-10 · Mark all rooms as read
### [UPSTREAM — REMOVED] P0-10 · Mark all rooms as read
**Audit result:** CONFIRMED UPSTREAM. Per-section "Mark as Read" exists at `Home.tsx:73-102` (home rooms) and `DirectTab.tsx:29-61` (DMs). No global all-sections version — but that was agreed not needed.
**Audit result:** CONFIRMED UPSTREAM. Per-section "Mark as Read" exists at `Home.tsx:73-102` (home rooms) and `DirectTab.tsx:29-61` (DMs). No global all-sections version — but that was agreed not needed.
---
---
### [ ] P0-11 · Spoiler text audit and fix if broken
### [ ] P0-11 · Spoiler text audit and fix if broken
**What:** The message composer toolbar has a spoiler mark (||spoiler text||). Verify the end-to-end flow:
**What:** The message composer toolbar has a spoiler mark (||spoiler text||). Verify the end-to-end flow:
1. Composer correctly wraps text in `<span data-mx-spoiler>` in `formatted_body`
1. Composer correctly wraps text in `<span data-mx-spoiler>` in `formatted_body`
2. Timeline renderer displays spoiler as blurred/hidden text with a "Reveal" click
2. Timeline renderer displays spoiler as blurred/hidden text with a "Reveal" click
3. Mobile touch works for reveal
3. Mobile touch works for reveal
@@ -274,11 +299,13 @@ If any step is broken, fix it. If all working correctly, close this task with no
**Audit result:**`settings.ts` line 103: `urlPreview: true` (already on by default) · line 104: `encUrlPreview: false` (encrypted rooms OFF by default).
**Audit result:**`settings.ts` line 103: `urlPreview: true` (already on by default) · line 104: `encUrlPreview: false` (encrypted rooms OFF by default).
**What:** Change `encUrlPreview` default to `true` in `src/app/state/settings.ts`. Add a one-sentence security note next to the encrypted-room toggle in the settings UI:
**What:** Change `encUrlPreview` default to `true` in `src/app/state/settings.ts`. Add a one-sentence security note next to the encrypted-room toggle in the settings UI:
> "URL previews in encrypted rooms are fetched by your homeserver, which sees the URL but not the message content."
> "URL previews in encrypted rooms are fetched by your homeserver, which sees the URL but not the message content."
**Where:** `src/app/state/settings.ts` line 104 (change default), settings UI file for URL preview toggles (find via grep for `encUrlPreview`).
> **Where:** `src/app/state/settings.ts` line 104 (change default), settings UI file for URL preview toggles (find via grep for `encUrlPreview`).
**Complexity:** Very Low — one default value change + one sentence of UI text.
> **Complexity:** Very Low — one default value change + one sentence of UI text.
---
---
@@ -289,7 +316,9 @@ Core features that meaningfully expand what users can do every day.
**What:** Global keyboard shortcut opens a floating search modal above all content. Features:
**What:** Global keyboard shortcut opens a floating search modal above all content. Features:
- Fuzzy-search across ALL rooms and DMs by display name in real time
- Fuzzy-search across ALL rooms and DMs by display name in real time
- Keyboard navigable: `↑`/`↓` to move, `Enter` to open room, `Esc` to close
- Keyboard navigable: `↑`/`↓` to move, `Enter` to open room, `Esc` to close
- Shows unread badge count inline next to each result
- Shows unread badge count inline next to each result
@@ -308,7 +337,9 @@ Core features that meaningfully expand what users can do every day.
---
---
### [ ] P1-2 · Media Gallery
### [ ] P1-2 · Media Gallery
**What:** A scrollable grid of all shared images, videos, and files in a room. Accessible via a new icon in the room header (picture/grid icon). Features:
**What:** A scrollable grid of all shared images, videos, and files in a room. Accessible via a new icon in the room header (picture/grid icon). Features:
- Tab bar: Images | Videos | Files
- Tab bar: Images | Videos | Files
- Infinite scroll / paginated load via `GET /rooms/{roomId}/messages` filtering for `m.image`, `m.video`, `m.file` msgtypes
- Infinite scroll / paginated load via `GET /rooms/{roomId}/messages` filtering for `m.image`, `m.video`, `m.file` msgtypes
@@ -326,8 +357,10 @@ Core features that meaningfully expand what users can do every day.
---
---
### [ ] P1-3 · Sidebar Room Filter / Search
### [ ] P1-3 · Sidebar Room Filter / Search
**What:** A text input at the top of each room list tab (Home, DMs, Spaces) that filters visible rooms in real time by display name. Ephemeral — clears when you switch tabs. No server calls — pure client-side filter over the loaded room list.
**What:** A text input at the top of each room list tab (Home, DMs, Spaces) that filters visible rooms in real time by display name. Ephemeral — clears when you switch tabs. No server calls — pure client-side filter over the loaded room list.
**Architecture:**
**Architecture:**
- Add filter `useState<string>('')` to each tab component
- Add filter `useState<string>('')` to each tab component
- Filter the room array before rendering: `rooms.filter(id => displayName(id).toLowerCase().includes(filter))`
- Filter the room array before rendering: `rooms.filter(id => displayName(id).toLowerCase().includes(filter))`
- Show a small `×` clear button when filter is non-empty
- Show a small `×` clear button when filter is non-empty
@@ -339,8 +372,10 @@ Core features that meaningfully expand what users can do every day.
**What:** Show the last message text and relative timestamp ("2 min ago", "Yesterday") next to each DM in the sidebar, like iMessage or WhatsApp. Currently only the room name and unread badge are shown.
**What:** Show the last message text and relative timestamp ("2 min ago", "Yesterday") next to each DM in the sidebar, like iMessage or WhatsApp. Currently only the room name and unread badge are shown.
**Architecture:**
**Architecture:**
- For each DM room: get `room.getLastActiveTimestamp()` and `room.timeline[room.timeline.length - 1]` for the last event
- For each DM room: get `room.getLastActiveTimestamp()` and `room.timeline[room.timeline.length - 1]` for the last event
- Render the event body (truncated to ~60 chars), stripping any HTML
- Render the event body (truncated to ~60 chars), stripping any HTML
- Format timestamp: "just now" / "X min ago" / "Yesterday" / date
- Format timestamp: "just now" / "X min ago" / "Yesterday" / date
@@ -351,22 +386,27 @@ Core features that meaningfully expand what users can do every day.
---
---
### [ ] P1-5 · Voice Message Playback Speed Control
### [ ] P1-5 · Voice Message Playback Speed Control
**What:** Add a speed toggle to the voice message audio player in the room timeline: `0.75×` → `1×` → `1.5×` → `2×`. Clicking the current speed label cycles to the next. Uses the HTML `<audio>` element's `playbackRate` property.
**What:** Add a speed toggle to the voice message audio player in the room timeline: `0.75×` → `1×` → `1.5×` → `2×`. Clicking the current speed label cycles to the next. Uses the HTML `<audio>` element's `playbackRate` property.
**Architecture:**
**Architecture:**
-`const [speed, setSpeed] = useState(1)`
-`const [speed, setSpeed] = useState(1)`
- On click: `audioRef.current.playbackRate = nextSpeed`
- On click: `audioRef.current.playbackRate = nextSpeed`
- Render a small clickable pill: `1×` etc., next to the audio controls
- Render a small clickable pill: `1×` etc., next to the audio controls
- Persist speed preference in `settingsAtom` so it carries across messages
- Persist speed preference in `settingsAtom` so it carries across messages
**[AUDIT REQUIRED]** — Locate exactly where voice messages render in the timeline. Search for `m.audio` / `MSC3245` / `VoiceMessage` in `src/app/features/room/`. The recorder is in `VoiceMessageRecorder.tsx` but the *player* for received voice messages is elsewhere.
**[AUDIT REQUIRED]** — Locate exactly where voice messages render in the timeline. Search for `m.audio` / `MSC3245` / `VoiceMessage` in `src/app/features/room/`. The recorder is in `VoiceMessageRecorder.tsx` but the _player_ for received voice messages is elsewhere.
**Complexity:** Low-Medium.
**Complexity:** Low-Medium.
---
---
### [ ] P1-6 · Poll Creation
### [ ] P1-6 · Poll Creation
**What:** Users can create polls from the message composer. Features:
**What:** Users can create polls from the message composer. Features:
- "Create Poll" toolbar button (poll icon) in `RoomInput.tsx`
- "Create Poll" toolbar button (poll icon) in `RoomInput.tsx`
- Modal: question field (required) + 2–10 answer options (add/remove dynamically) + single vs. multiple choice toggle
- Modal: question field (required) + 2–10 answer options (add/remove dynamically) + single vs. multiple choice toggle
- Sends `m.poll.start` event (Matrix 1.7 stable format — same JSON we already render):
- Sends `m.poll.start` event (Matrix 1.7 stable format — same JSON we already render):
```json
```json
{
{
"type":"m.poll.start",
"type":"m.poll.start",
@@ -379,6 +419,7 @@ Core features that meaningfully expand what users can do every day.
}
}
}
}
```
```
-`mx.sendEvent(roomId, 'm.poll.start', content)`
-`mx.sendEvent(roomId, 'm.poll.start', content)`
**Architecture:**
**Architecture:**
- New component: `src/app/features/room/PollCreator.tsx`
- New component: `src/app/features/room/PollCreator.tsx`
@@ -388,8 +429,10 @@ Core features that meaningfully expand what users can do every day.
---
---
### [ ] P1-7 · Code Syntax Highlighting in Messages
### [ ] P1-7 · Code Syntax Highlighting in Messages
**What:** Color-code fenced code blocks in rendered messages. Language auto-detected from the code fence hint (` ```python `, ` ```js `, etc.).
**What:** Color-code fenced code blocks in rendered messages. Language auto-detected from the code fence hint (` ```python `, ` ```js `, etc.).
**⚠️ MUST match `/root/code/web_template/base.css` exactly.** The web_template defines the canonical token structure for all Lotus apps:
**⚠️ MUST match `/root/code/web_template/base.css` exactly.** The web_template defines the canonical token structure for all Lotus apps:
@@ -409,11 +452,14 @@ See `/root/code/tinker_tickets/assets/js/markdown.js` for the reference tokenize
---
---
### [ ] P1-8 · Favorite / Starred Rooms
### [ ] P1-8 · Favorite / Starred Rooms
**What:** Star any room from its context menu. Starred rooms appear in a dedicated "Favorites" section at the top of the sidebar room list. Uses Matrix's built-in `m.favourite` room tag (account data), so favorites sync across devices and clients automatically.
**What:** Star any room from its context menu. Starred rooms appear in a dedicated "Favorites" section at the top of the sidebar room list. Uses Matrix's built-in `m.favourite` room tag (account data), so favorites sync across devices and clients automatically.
```
```
PUT /_matrix/client/v3/user/{userId}/rooms/{roomId}/tags/m.favourite
PUT /_matrix/client/v3/user/{userId}/rooms/{roomId}/tags/m.favourite
Body: { "order": 0.5 }
Body: { "order": 0.5 }
```
```
- Right-click room → "Add to Favorites" / "Remove from Favorites"
- Right-click room → "Add to Favorites" / "Remove from Favorites"
- Favorites section appears above the regular room list in the Home tab
- Favorites section appears above the regular room list in the Home tab
- Star icon shown on favorited rooms in the list
- Star icon shown on favorited rooms in the list
@@ -423,7 +469,9 @@ Body: { "order": 0.5 }
---
---
### [ ] P1-9 · Invite Link Generator (with QR code)
### [ ] P1-9 · Invite Link Generator (with QR code)
**What:** In room settings (or via right-click on room), a "Copy Invite Link" button that:
**What:** In room settings (or via right-click on room), a "Copy Invite Link" button that:
1. Generates a `matrix.to` URL: `https://matrix.to/#/!roomId:server?via=matrix.lotusguild.org`
1. Generates a `matrix.to` URL: `https://matrix.to/#/!roomId:server?via=matrix.lotusguild.org`
2. Copies it to clipboard via `navigator.clipboard.writeText()`
2. Copies it to clipboard via `navigator.clipboard.writeText()`
3. Shows a QR code of the link (use a lightweight QR library e.g. `qrcode` npm package)
3. Shows a QR code of the link (use a lightweight QR library e.g. `qrcode` npm package)
@@ -435,9 +483,11 @@ Body: { "order": 0.5 }
---
---
### [ ] P1-10 · Private Read Receipts toggle
### [ ] P1-10 · Private Read Receipts toggle
**Spec:** CS-API stable — `m.read.private` vs `m.read`.
**Spec:** CS-API stable — `m.read.private` vs `m.read`.
**What:** Add a toggle in Settings → Privacy: "Send private read receipts" — when enabled, read receipts are sent as `m.read.private` (only you and the server see them) instead of `m.read` (everyone in the room sees when you read). Default: public (current behavior, unchanged).
**What:** Add a toggle in Settings → Privacy: "Send private read receipts" — when enabled, read receipts are sent as `m.read.private` (only you and the server see them) instead of `m.read` (everyone in the room sees when you read). Default: public (current behavior, unchanged).
**Architecture:**
**Architecture:**
- New `settingsAtom` field: `privateReadReceipts: boolean` (default `false`)
- New `settingsAtom` field: `privateReadReceipts: boolean` (default `false`)
- When sending a read receipt, check the setting and use the appropriate type
- When sending a read receipt, check the setting and use the appropriate type
- Find `mx.sendReadReceipt(...)` call sites and add the `receiptType` parameter
- Find `mx.sendReadReceipt(...)` call sites and add the `receiptType` parameter
@@ -447,8 +497,10 @@ Body: { "order": 0.5 }
---
---
### [ ] P1-11 · Knock-to-join UX (Room version 7)
### [ ] P1-11 · Knock-to-join UX (Room version 7)
**Spec:** Room version 7 (stable Matrix spec). Join rule: `knock`.
**Spec:** Room version 7 (stable Matrix spec). Join rule: `knock`.
**What:** Full knock UX for both sides:
**What:** Full knock UX for both sides:
- **Knocking:** When a room's join rule is `knock`, show a "Request to Join" button instead of "Join Room". Sends `POST /join` which triggers a knock state event. Show pending state ("Request sent") while waiting.
- **Knocking:** When a room's join rule is `knock`, show a "Request to Join" button instead of "Join Room". Sends `POST /join` which triggers a knock state event. Show pending state ("Request sent") while waiting.
- **Approving knocks:** Room admins/mods see a notification of pending knocks. In the members drawer or room settings, show a "Pending Requests" section listing knockers with "Approve" (`/invite`) and "Deny" (`/kick`) buttons.
- **Approving knocks:** Room admins/mods see a notification of pending knocks. In the members drawer or room settings, show a "Pending Requests" section listing knockers with "Approve" (`/invite`) and "Deny" (`/kick`) buttons.
**[SERVER CHECK]** — Verify `matrix.lotusguild.org` Synapse supports room version 7 and the knock join rule.
**[SERVER CHECK]** — Verify `matrix.lotusguild.org` Synapse supports room version 7 and the knock join rule.
@@ -462,6 +514,7 @@ Body: { "order": 0.5 }
---
---
### [ ] P2-1 · Jump to Date
### [ ] P2-1 · Jump to Date
**What:** A calendar date picker accessible from the room header (small calendar icon). Selecting a date navigates the timeline to the first message on or after that date.
**What:** A calendar date picker accessible from the room header (small calendar icon). Selecting a date navigates the timeline to the first message on or after that date.
- "None" option to disable sound for that category
- "None" option to disable sound for that category
@@ -488,7 +543,9 @@ Returns `{ event_id, origin_server_ts }`. Then scroll the timeline to that event
---
---
### [ ] P2-3 · Sort Non-Space Rooms on Home Page
### [ ] P2-3 · Sort Non-Space Rooms on Home Page
**What:** A sort control on the Home tab for orphan rooms (rooms not in any space). Options:
**What:** A sort control on the Home tab for orphan rooms (rooms not in any space). Options:
- **Unread first** — rooms with unread messages appear at top
- **Unread first** — rooms with unread messages appear at top
- **Recent activity** — sort by `room.getLastActiveTimestamp()`
- **Recent activity** — sort by `room.getLastActiveTimestamp()`
- **Alphabetical** — sort by display name A→Z
- **Alphabetical** — sort by display name A→Z
@@ -500,7 +557,9 @@ Persisted in `settingsAtom` as `homeRoomSortOrder: 'unread' | 'recent' | 'alpha'
---
---
### [ ] P2-4 · Export Room History
### [ ] P2-4 · Export Room History
**What:** Export a room's messages to plain text, JSON, or HTML. Accessible from room settings (gear icon → "Export Room History"). Features:
**What:** Export a room's messages to plain text, JSON, or HTML. Accessible from room settings (gear icon → "Export Room History"). Features:
- Format selector: Plain Text / JSON / HTML
- Format selector: Plain Text / JSON / HTML
- Date range filter (optional: all time or custom range)
- Date range filter (optional: all time or custom range)
- Only exports messages where E2EE decryption keys are available locally
- Only exports messages where E2EE decryption keys are available locally
@@ -514,10 +573,13 @@ Persisted in `settingsAtom` as `homeRoomSortOrder: 'unread' | 'recent' | 'alpha'
---
---
### [ ] P2-5 · Notification Quiet Hours
### [ ] P2-5 · Notification Quiet Hours
**What:** Set a daily time window when browser notifications are suppressed (e.g., 11:00 PM – 8:00 AM). Toggle + start/end time pickers in Settings → Notifications.
**What:** Set a daily time window when browser notifications are suppressed (e.g., 11:00 PM – 8:00 AM). Toggle + start/end time pickers in Settings → Notifications.
**Architecture:**
**Architecture:**
- New `settingsAtom` field: `quietHours: { enabled: boolean, start: string, end: string }` (times as "HH:MM" 24h strings)
- New `settingsAtom` field: `quietHours: { enabled: boolean, start: string, end: string }` (times as "HH:MM" 24h strings)
- In `ClientNonUIFeatures.tsx` or `SystemNotification.tsx`, before dispatching a browser `Notification`, check:
- In `ClientNonUIFeatures.tsx` or `SystemNotification.tsx`, before dispatching a browser `Notification`, check:
```ts
```ts
functionisQuietHours(settings):boolean{
functionisQuietHours(settings):boolean{
if(!settings.quietHours.enabled)returnfalse;
if(!settings.quietHours.enabled)returnfalse;
@@ -528,6 +590,7 @@ function isQuietHours(settings): boolean {
...
...
}
}
```
```
- Client-side only — does not touch Synapse push rules
- Client-side only — does not touch Synapse push rules
**[AUDIT REQUIRED]** — Find the exact location in the codebase where browser `Notification` objects are created. Confirm no other code paths trigger sounds/notifications that also need to be suppressed.
**[AUDIT REQUIRED]** — Find the exact location in the codebase where browser `Notification` objects are created. Confirm no other code paths trigger sounds/notifications that also need to be suppressed.
**Complexity:** Medium.
**Complexity:** Medium.
@@ -535,7 +598,9 @@ function isQuietHours(settings): boolean {
---
---
### [ ] P2-6 · Room Activity / Moderation Log
### [ ] P2-6 · Room Activity / Moderation Log
**What:** A filterable log of room state changes, accessible from room settings. Shows:
**What:** A filterable log of room state changes, accessible from room settings. Shows:
- Member joins, leaves, kicks, bans, unbans
- Member joins, leaves, kicks, bans, unbans
- Power level changes ("@alice promoted to Moderator by @bob")
- Power level changes ("@alice promoted to Moderator by @bob")
- Room name / topic / avatar changes
- Room name / topic / avatar changes
@@ -548,7 +613,9 @@ Fetches via `/rooms/{roomId}/messages?filter={"types":["m.room.member","m.room.p
**[AUDIT REQUIRED — DO THIS FIRST]** — Before any implementation, verify the current behavior:
**[AUDIT REQUIRED — DO THIS FIRST]** — Before any implementation, verify the current behavior:
1. Paste a Giphy URL (e.g. `https://giphy.com/gifs/...`) into the Lotus Chat composer and send it
1. Paste a Giphy URL (e.g. `https://giphy.com/gifs/...`) into the Lotus Chat composer and send it
2. Does it auto-embed as an animated GIF, or render as a plain URL preview card?
2. Does it auto-embed as an animated GIF, or render as a plain URL preview card?
3. Repeat for Tenor URLs
3. Repeat for Tenor URLs
@@ -709,6 +798,7 @@ User must explicitly check the box — compression is NEVER automatic.
---
---
### [ ] P3-6 · Configurable Composer Toolbar
### [ ] P3-6 · Configurable Composer Toolbar
**What:** Let users rearrange or hide individual composer toolbar buttons (GIF, Sticker, Emoji, File, Voice, Location). Changes stored in `settingsAtom`. Access via a small "⚙ Customize toolbar" option in toolbar overflow.
**What:** Let users rearrange or hide individual composer toolbar buttons (GIF, Sticker, Emoji, File, Voice, Location). Changes stored in `settingsAtom`. Access via a small "⚙ Customize toolbar" option in toolbar overflow.
**[AUDIT REQUIRED]** — Audit the current toolbar button rendering in `RoomInput.tsx`. Understand the layout system (is it a fixed array or already mapped from config?). Drag-to-reorder may require a DnD library; consider whether reorder is worth the complexity vs just toggle-visibility.
**[AUDIT REQUIRED]** — Audit the current toolbar button rendering in `RoomInput.tsx`. Understand the layout system (is it a fixed array or already mapped from config?). Drag-to-reorder may require a DnD library; consider whether reorder is worth the complexity vs just toggle-visibility.
**Spec:** MSC3771 (stable). Depends on Thread Panel (#P3-8).
**Spec:** MSC3771 (stable). Depends on Thread Panel (#P3-8).
**What:** Per-thread notification toggle: "All messages" vs "Mentions only". Accessible from the thread panel header. Tracks unread counts separately per thread.
**What:** Per-thread notification toggle: "All messages" vs "Mentions only". Accessible from the thread panel header. Tracks unread counts separately per thread.
**[AUDIT REQUIRED]** — Implement after Thread Panel. Requires understanding how the SDK tracks per-thread unread counts.
**[AUDIT REQUIRED]** — Implement after Thread Panel. Requires understanding how the SDK tracks per-thread unread counts.
@@ -776,6 +873,7 @@ Features:
---
---
### [ ] P4-2 · Thread Subscriptions (MSC4306)
### [ ] P4-2 · Thread Subscriptions (MSC4306)
**Spec:** MSC4306 (Synapse experimental). Depends on Thread Panel (#P3-8).
**Spec:** MSC4306 (Synapse experimental). Depends on Thread Panel (#P3-8).
**What:** "Follow thread" button to receive notifications for a thread you haven't posted in. Uses MSC4306 subscription endpoint.
**What:** "Follow thread" button to receive notifications for a thread you haven't posted in. Uses MSC4306 subscription endpoint.
**[SERVER CHECK]** — Verify `matrix.lotusguild.org` supports MSC4306. It is Synapse-experimental and may not be enabled.
**[SERVER CHECK]** — Verify `matrix.lotusguild.org` supports MSC4306. It is Synapse-experimental and may not be enabled.
@@ -784,6 +882,7 @@ Features:
---
---
### [ ] P4-3 · Knock-to-join Notifications for Admins
### [ ] P4-3 · Knock-to-join Notifications for Admins
**Note:** The basic knock-to-join UX is covered in P1-11. This task adds the admin notification side.
**Note:** The basic knock-to-join UX is covered in P1-11. This task adds the admin notification side.
**What:** Space/room admins see a notification badge when there are pending knock requests. A "Pending Join Requests" section in the members drawer or room settings. Approve (invite) or deny (kick) each knock.
**What:** Space/room admins see a notification badge when there are pending knock requests. A "Pending Join Requests" section in the members drawer or room settings. Approve (invite) or deny (kick) each knock.
**Complexity:** Medium.
**Complexity:** Medium.
@@ -791,6 +890,7 @@ Features:
---
---
### [ ] P4-4 · Math / LaTeX Rendering in Messages (LOW PRIORITY)
### [ ] P4-4 · Math / LaTeX Rendering in Messages (LOW PRIORITY)
**Spec:** CS-API §11.5 (stable) — `formatted_body` can contain LaTeX.
**Spec:** CS-API §11.5 (stable) — `formatted_body` can contain LaTeX.
**What:** Render `$...$` or `$$...$$` LaTeX expressions in message bodies. Use KaTeX (lightweight, ~100KB, renders server-side-compatible CSS). Must gracefully fall back to raw LaTeX text if KaTeX fails.
**What:** Render `$...$` or `$$...$$` LaTeX expressions in message bodies. Use KaTeX (lightweight, ~100KB, renders server-side-compatible CSS). Must gracefully fall back to raw LaTeX text if KaTeX fails.
**Note:** This is LOW PRIORITY — only useful for academic/technical communities. Implement last.
**Note:** This is LOW PRIORITY — only useful for academic/technical communities. Implement last.
@@ -800,6 +900,7 @@ Features:
---
---
### [ ] P4-5 · Live Location Sharing (MSC3489 + MSC3672) (LOW PRIORITY, HIGH COMPLEXITY)
### [ ] P4-5 · Live Location Sharing (MSC3489 + MSC3672) (LOW PRIORITY, HIGH COMPLEXITY)
**Spec:** MSC3489 + MSC3672. Implemented in Element Web.
**Spec:** MSC3489 + MSC3672. Implemented in Element Web.
**Note:** Static location sharing is already implemented. This adds live/real-time GPS beacons. Very low priority per user preference.
**Note:** Static location sharing is already implemented. This adds live/real-time GPS beacons. Very low priority per user preference.
**What:** Start sharing live location → creates `m.beacon_info` state event → client posts `m.beacon` events on a timer → other users see your position update live on a map.
**What:** Start sharing live location → creates `m.beacon_info` state event → client posts `m.beacon` events on a timer → other users see your position update live on a map.
**Context:** ~80% of homeserver users have LLDAP/Authelia/SSO accounts. SSO is currently enabled on `matrix.lotusguild.org` but accounts are not yet linked. This would allow users to log in via their SSO credentials.
**Context:** ~80% of homeserver users have LLDAP/Authelia/SSO accounts. SSO is currently enabled on `matrix.lotusguild.org` but accounts are not yet linked. This would allow users to log in via their SSO credentials.
**Confirmed bug** — drag a file over the window without dropping: the drop overlay persists.
**Confirmed bug** — drag a file over the window without dropping: the drop overlay persists.
**Fix:** Ensure `dragleave` fires correctly at the window/document level. Child element boundaries can cause spurious `dragleave` — use a counter or `relatedTarget` check.
**Fix:** Ensure `dragleave` fires correctly at the window/document level. Child element boundaries can cause spurious `dragleave` — use a counter or `relatedTarget` check.
**[AUDIT REQUIRED]** Find the drag-and-drop overlay component in `RoomInput.tsx` or the room view. Confirm the exact event listener structure.
**[AUDIT REQUIRED]** Find the drag-and-drop overlay component in `RoomInput.tsx` or the room view. Confirm the exact event listener structure.
**What:** A hex/HSL color picker in Settings → Appearance. Chosen color replaces the primary accent throughout the UI: buttons, badges, active states, highlights, presence dot, links. Applied via a CSS custom property override injected into `<head>`.
**What:** A hex/HSL color picker in Settings → Appearance. Chosen color replaces the primary accent throughout the UI: buttons, badges, active states, highlights, presence dot, links. Applied via a CSS custom property override injected into `<head>`.
**IMPORTANT:** This feature is completely inactive when TDS is enabled — TDS has its own fixed palette. Add this setting under a "Non-TDS Themes" section that is hidden when TDS is active.
**IMPORTANT:** This feature is completely inactive when TDS is enabled — TDS has its own fixed palette. Add this setting under a "Non-TDS Themes" section that is hidden when TDS is active.
**[AUDIT REQUIRED]** Identify all CSS custom properties that constitute the "accent color" in non-TDS mode. Map them to the folds/vanilla-extract token names.
**[AUDIT REQUIRED]** Identify all CSS custom properties that constitute the "accent color" in non-TDS mode. Map them to the folds/vanilla-extract token names.
@@ -839,8 +943,10 @@ Features:
---
---
### [ ] P5-2 · Additional Color Theme Presets
### [ ] P5-2 · Additional Color Theme Presets
**What:** 5 new one-click theme presets alongside TDS. Each must be a complete, polished system with proper contrast ratios (WCAG AA). All implemented as vanilla-extract themes matching the existing TDS pattern.
**What:** 5 new one-click theme presets alongside TDS. Each must be a complete, polished system with proper contrast ratios (WCAG AA). All implemented as vanilla-extract themes matching the existing TDS pattern.
Themes:
Themes:
1.**Cyberpunk** — deep navy bg (`#0a0015`), electric purple (`#bf5fff`) + hot pink (`#ff2d9b`) accents, neon glow
1.**Cyberpunk** — deep navy bg (`#0a0015`), electric purple (`#bf5fff`) + hot pink (`#ff2d9b`) accents, neon glow
2.**Ocean** — deep sea blue bg (`#020b18`), teal (`#00c9b1`) + aqua (`#0096d6`) accents, soft feel
2.**Ocean** — deep sea blue bg (`#020b18`), teal (`#00c9b1`) + aqua (`#0096d6`) accents, soft feel
3.**Blood Red** — near-black bg (`#0d0203`), deep crimson (`#7a0010`) + bright red (`#ff2233`) accents
3.**Blood Red** — near-black bg (`#0d0203`), deep crimson (`#7a0010`) + bright red (`#ff2233`) accents
@@ -852,6 +958,7 @@ Themes:
---
---
### [ ] P5-3 · Glassmorphism Sidebar Toggle
### [ ] P5-3 · Glassmorphism Sidebar Toggle
**What:** Semi-transparent sidebar + panels with `backdrop-filter: blur(12px)`. Toggled in Settings → Appearance. Off by default. Best combined with animated wallpapers.
**What:** Semi-transparent sidebar + panels with `backdrop-filter: blur(12px)`. Toggled in Settings → Appearance. Off by default. Best combined with animated wallpapers.
**[AUDIT REQUIRED]** Check whether `backdrop-filter` works on sidebar elements given the current z-index stack and CSS transforms. Some browsers require `will-change` or specific stacking context to allow blur through.
**[AUDIT REQUIRED]** Check whether `backdrop-filter` works on sidebar elements given the current z-index stack and CSS transforms. Some browsers require `will-change` or specific stacking context to allow blur through.
@@ -872,12 +981,14 @@ All pure CSS animations (keyframes + transforms) — no canvas, GPU-accelerated.
---
---
### [ ] P5-5 · Night Light / Blue Light Filter
### [ ] P5-5 · Night Light / Blue Light Filter
**What:** Warm orange overlay `rgba(255, 140, 0, 0.12)` on the full UI. `position:fixed; inset:0; pointer-events:none; z-index:9999`. Intensity slider (0–30%) in Settings → Appearance. Optional: auto-activate after a set hour. Stored in `settingsAtom`.
**What:** Warm orange overlay `rgba(255, 140, 0, 0.12)` on the full UI. `position:fixed; inset:0; pointer-events:none; z-index:9999`. Intensity slider (0–30%) in Settings → Appearance. Optional: auto-activate after a set hour. Stored in `settingsAtom`.
**What:** Render a leading emoji in a room name slightly larger in the sidebar for visual impact (e.g. 🎮 general). Optional: right-click room → "Set channel emoji" shortcut for admins.
**What:** Render a leading emoji in a room name slightly larger in the sidebar for visual impact (e.g. 🎮 general). Optional: right-click room → "Set channel emoji" shortcut for admins.
**Note:** Matrix room names already support Unicode — this is purely a rendering enhancement.
**Note:** Matrix room names already support Unicode — this is purely a rendering enhancement.
**[AUDIT REQUIRED]** Confirm upstream Cinny doesn't strip or truncate leading emoji in sidebar room name display. Also confirm emoji in room names works end-to-end on `matrix.lotusguild.org`.
**[AUDIT REQUIRED]** Confirm upstream Cinny doesn't strip or truncate leading emoji in sidebar room name display. Also confirm emoji in room names works end-to-end on `matrix.lotusguild.org`.
@@ -886,6 +997,7 @@ All pure CSS animations (keyframes + transforms) — no canvas, GPU-accelerated.
**What:** Slim dark card sliding in from bottom-right: user avatar, display name (orange), truncated message preview, room name (dim), × dismiss. 4-second auto-dismiss. TDS variables only — non-TDS keeps existing behavior.
**What:** Slim dark card sliding in from bottom-right: user avatar, display name (orange), truncated message preview, room name (dim), × dismiss. 4-second auto-dismiss. TDS variables only — non-TDS keeps existing behavior.
**[AUDIT REQUIRED]** Find where in-app notification toasts are currently rendered in `src/app/`. May be in `ClientNonUIFeatures.tsx`.
**[AUDIT REQUIRED]** Find where in-app notification toasts are currently rendered in `src/app/`. May be in `ClientNonUIFeatures.tsx`.
**Complexity:** Medium.
**Complexity:** Medium.
@@ -893,6 +1005,7 @@ All pure CSS animations (keyframes + transforms) — no canvas, GPU-accelerated.
---
---
### [ ] P5-8 · Mention Highlight Animation
### [ ] P5-8 · Mention Highlight Animation
**What:** Brief pulse/glow animation (0.4–0.6s ease-out) on incoming @mention messages. CSS keyframe: scale 1.0 → 1.005 → 1.0 + background glow pulse. Only fires on new incoming messages, not on page load. Respects `prefers-reduced-motion`.
**What:** Brief pulse/glow animation (0.4–0.6s ease-out) on incoming @mention messages. CSS keyframe: scale 1.0 → 1.005 → 1.0 + background glow pulse. Only fires on new incoming messages, not on page load. Respects `prefers-reduced-motion`.
**[AUDIT REQUIRED]** Find where mentioned messages receive their highlight class in the timeline. Verify animation doesn't affect scroll position.
**[AUDIT REQUIRED]** Find where mentioned messages receive their highlight class in the timeline. Verify animation doesn't affect scroll position.
**Complexity:** Low.
**Complexity:** Low.
@@ -900,6 +1013,7 @@ All pure CSS animations (keyframes + transforms) — no canvas, GPU-accelerated.
**What:**`/lfg` generates a formatted LFG post visible on ALL Matrix clients using standard `m.room.message` HTML. Fields: Game, Players Needed, Platform, Skill Level, Description, DM link. Other clients see clean formatted HTML; Lotus Chat renders an enhanced styled card.
**What:**`/lfg` generates a formatted LFG post visible on ALL Matrix clients using standard `m.room.message` HTML. Fields: Game, Players Needed, Platform, Skill Level, Description, DM link. Other clients see clean formatted HTML; Lotus Chat renders an enhanced styled card.
**[AUDIT REQUIRED]** Test which HTML tags survive Matrix HTML sanitization on Element/FluffyChat before designing the card structure. Test with minimal HTML.
**[AUDIT REQUIRED]** Test which HTML tags survive Matrix HTML sanitization on Element/FluffyChat before designing the card structure. Test with minimal HTML.
**Complexity:** Medium.
**Complexity:** Medium.
@@ -907,6 +1021,7 @@ All pure CSS animations (keyframes + transforms) — no canvas, GPU-accelerated.
---
---
### [ ] P5-10 · Voice Channel User Limit
### [ ] P5-10 · Voice Channel User Limit
**What:** Admins set max participants via custom state event `io.lotus.voice_limit: { max_users: N }`. Show "Channel Full (5/5)" to users over the limit. Local enforcement only.
**What:** Admins set max participants via custom state event `io.lotus.voice_limit: { max_users: N }`. Show "Channel Full (5/5)" to users over the limit. Local enforcement only.
**[AUDIT REQUIRED]** Check if Element Call has its own participant limit that should be integrated with rather than duplicated.
**[AUDIT REQUIRED]** Check if Element Call has its own participant limit that should be integrated with rather than duplicated.
**Complexity:** Medium.
**Complexity:** Medium.
@@ -914,6 +1029,7 @@ All pure CSS animations (keyframes + transforms) — no canvas, GPU-accelerated.
---
---
### [ ] P5-11 · AFK / Idle Auto-Mute in Voice
### [ ] P5-11 · AFK / Idle Auto-Mute in Voice
**What:** Auto-mute mic after X minutes of silence (detected via Web Audio AnalyserNode). Show "You were auto-muted due to inactivity" toast with click-to-unmute. Admin-configurable via `io.lotus.afk_timeout` state event. Disableable in Settings → Calls.
**What:** Auto-mute mic after X minutes of silence (detected via Web Audio AnalyserNode). Show "You were auto-muted due to inactivity" toast with click-to-unmute. Admin-configurable via `io.lotus.afk_timeout` state event. Disableable in Settings → Calls.
**[AUDIT REQUIRED]** Verify auto-mute must go through the same CallControl bridge as manual mute.
**[AUDIT REQUIRED]** Verify auto-mute must go through the same CallControl bridge as manual mute.
**Complexity:** Medium.
**Complexity:** Medium.
@@ -921,7 +1037,9 @@ All pure CSS animations (keyframes + transforms) — no canvas, GPU-accelerated.
---
---
### [ ] P5-12 · Seasonal / Event Themes
### [ ] P5-12 · Seasonal / Event Themes
**What:** Automatic + manually toggleable seasonal overlays with CSS particle effects and accent color variants:
**What:** Automatic + manually toggleable seasonal overlays with CSS particle effects and accent color variants:
**What:** Animated WebM/GIF overlays that float around avatars (transparent center showing avatar). Curated built-in set OR user-uploaded mxc:// overlay. Stored in account data. Only Lotus Chat users see them.
**What:** Animated WebM/GIF overlays that float around avatars (transparent center showing avatar). Curated built-in set OR user-uploaded mxc:// overlay. Stored in account data. Only Lotus Chat users see them.
**[AUDIT REQUIRED]** See #P5-13 audit. Also decide: curated set only vs user-uploadable.
**[AUDIT REQUIRED]** See #P5-13 audit. Also decide: curated set only vs user-uploadable.
**Complexity:** Medium.
**Complexity:** Medium.
@@ -947,6 +1067,7 @@ All toggleable manually in Settings → Appearance regardless of date. Respects
---
---
### [ ] P5-15 · In-Call Soundboard
### [ ] P5-15 · In-Call Soundboard
**What:** Grid of short audio clips playable into the call audio stream via Web Audio API (AudioBufferSourceNode → MediaStreamDestinationNode → mixed with mic). Built-in clips + user-uploadable custom clips (stored as mxc://). Accessible from call controls bar.
**What:** Grid of short audio clips playable into the call audio stream via Web Audio API (AudioBufferSourceNode → MediaStreamDestinationNode → mixed with mic). Built-in clips + user-uploadable custom clips (stored as mxc://). Accessible from call controls bar.
**[AUDIT REQUIRED]** Verify the Element Call integration exposes the mic MediaStream for mixing. This is the highest-risk part of this feature.
**[AUDIT REQUIRED]** Verify the Element Call integration exposes the mic MediaStream for mixing. This is the highest-risk part of this feature.
**Complexity:** High.
**Complexity:** High.
@@ -954,6 +1075,7 @@ All toggleable manually in Settings → Appearance regardless of date. Respects
---
---
### [ ] P5-16 · Custom Join / Leave Sound Effects
### [ ] P5-16 · Custom Join / Leave Sound Effects
**What:** Local-only sounds when participants join/leave a call you're in. Built-in options + per-user settable. Detect via Element Call participant list change events.
**What:** Local-only sounds when participants join/leave a call you're in. Built-in options + per-user settable. Detect via Element Call participant list change events.
**[AUDIT REQUIRED]** Find how Element Call exposes join/leave participant events to the parent window via postMessage bridge.
**[AUDIT REQUIRED]** Find how Element Call exposes join/leave participant events to the parent window via postMessage bridge.
**Complexity:** Medium.
**Complexity:** Medium.
@@ -961,6 +1083,7 @@ All toggleable manually in Settings → Appearance regardless of date. Respects
**What:** Floating mini-bar of 5 most recent reactions above the hover toolbar. One-click react. 6th button opens full emoji board.
**What:** Floating mini-bar of 5 most recent reactions above the hover toolbar. One-click react. 6th button opens full emoji board.
**[AUDIT REQUIRED]** Find the message hover toolbar in `Message.tsx` and confirm how to inject an additional row without breaking layout. Confirm recent emoji tracking mechanism in EmojiBoard.
**[AUDIT REQUIRED]** Find the message hover toolbar in `Message.tsx` and confirm how to inject an additional row without breaking layout. Confirm recent emoji tracking mechanism in EmojiBoard.
**Complexity:** Medium.
**Complexity:** Medium.
@@ -968,6 +1091,7 @@ All toggleable manually in Settings → Appearance regardless of date. Respects
---
---
### [ ] P5-18 · Status-Based Avatar Border Color
### [ ] P5-18 · Status-Based Avatar Border Color
**What:** Colored ring on avatars matching presence: green (online), yellow (idle), red (DND), grey (offline). Subtle 2px CSS box-shadow/border. Applied across all avatar sizes.
**What:** Colored ring on avatars matching presence: green (online), yellow (idle), red (DND), grey (offline). Subtle 2px CSS box-shadow/border. Applied across all avatar sizes.
**[AUDIT REQUIRED]** Check existing `PresenceBadge` component — this extends that concept to the avatar border. Verify folds Avatar allows border/shadow styling.
**[AUDIT REQUIRED]** Check existing `PresenceBadge` component — this extends that concept to the avatar border. Verify folds Avatar allows border/shadow styling.
**Complexity:** Low-Medium.
**Complexity:** Low-Medium.
@@ -975,6 +1099,7 @@ All toggleable manually in Settings → Appearance regardless of date. Respects
---
---
### [ ] P5-19 · Collapsible Long Messages ('Read more')
### [ ] P5-19 · Collapsible Long Messages ('Read more')
**What:** Messages >~20 lines auto-collapsed with "Read more ↓" button. Click to expand inline. Collapse threshold configurable in settings.
**What:** Messages >~20 lines auto-collapsed with "Read more ↓" button. Click to expand inline. Collapse threshold configurable in settings.
**[AUDIT REQUIRED]** Determine whether CSS `max-height` + `overflow:hidden` or a line-count approach is more appropriate for the current message renderer. Check edge cases with code blocks and media embeds.
**[AUDIT REQUIRED]** Determine whether CSS `max-height` + `overflow:hidden` or a line-count approach is more appropriate for the current message renderer. Check edge cases with code blocks and media embeds.
**Complexity:** Medium.
**Complexity:** Medium.
@@ -982,6 +1107,7 @@ All toggleable manually in Settings → Appearance regardless of date. Respects
---
---
### [ ] P5-20 · Quick Reply from Browser Notification
### [ ] P5-20 · Quick Reply from Browser Notification
**What:** Inline reply field in browser notification toasts via Notification Actions API. Reply sends as threaded reply to the triggering message.
**What:** Inline reply field in browser notification toasts via Notification Actions API. Reply sends as threaded reply to the triggering message.
**[AUDIT REQUIRED]** (1) Verify browser Notification Actions API support in target browsers. (2) This requires a Service Worker to handle the reply event — confirm if Lotus Chat has one or needs one.
**[AUDIT REQUIRED]** (1) Verify browser Notification Actions API support in target browsers. (2) This requires a Service Worker to handle the reply event — confirm if Lotus Chat has one or needs one.
**Complexity:** Medium-High.
**Complexity:** Medium-High.
@@ -989,12 +1115,14 @@ All toggleable manually in Settings → Appearance regardless of date. Respects
---
---
### [ ] P5-21 · Custom @Mention Highlight Color
### [ ] P5-21 · Custom @Mention Highlight Color
**What:** Each user sets their own mention highlight color in Settings → Appearance. Applied as `--user-mention-color` CSS property override on mention-highlighted message rows.
**What:** Each user sets their own mention highlight color in Settings → Appearance. Applied as `--user-mention-color` CSS property override on mention-highlighted message rows.
**Complexity:** Low.
**Complexity:** Low.
---
---
### [ ] P5-22 · Font Selector for the UI
### [ ] P5-22 · Font Selector for the UI
**What:** Font picker in Settings → Appearance. Options: JetBrains Mono, Inter, Geist, Fira Code, OpenDyslexic, System Default. Applied via CSS custom property overrides.
**What:** Font picker in Settings → Appearance. Options: JetBrains Mono, Inter, Geist, Fira Code, OpenDyslexic, System Default. Applied via CSS custom property overrides.
**[AUDIT REQUIRED]** Check if any fonts are already globally loaded to avoid double-loading.
**[AUDIT REQUIRED]** Check if any fonts are already globally loaded to avoid double-loading.
**Complexity:** Low-Medium.
**Complexity:** Low-Medium.
@@ -1002,12 +1130,14 @@ All toggleable manually in Settings → Appearance regardless of date. Respects
---
---
### [ ] P5-23 · Message Send Animation
### [ ] P5-23 · Message Send Animation
**What:** Own sent messages fade+scale into the timeline (0.15s ease-out: scale 0.97→1.0, opacity 0.4→1.0). Incoming messages unaffected. Respects `prefers-reduced-motion`.
**What:** Own sent messages fade+scale into the timeline (0.15s ease-out: scale 0.97→1.0, opacity 0.4→1.0). Incoming messages unaffected. Respects `prefers-reduced-motion`.
**Complexity:** Low.
**Complexity:** Low.
---
---
### [ ] P5-24 · Hotkey Push-to-Deafen
### [ ] P5-24 · Hotkey Push-to-Deafen
**What:** Configurable hotkey (default Ctrl+Shift+D) to toggle deafen. Shows "DEAFENED" badge in call bar. Configurable in Settings → Calls alongside PTT keybind.
**What:** Configurable hotkey (default Ctrl+Shift+D) to toggle deafen. Shows "DEAFENED" badge in call bar. Configurable in Settings → Calls alongside PTT keybind.
**[AUDIT REQUIRED]** Confirm Element Call widget bridge exposes speaker/audio-output control separately from microphone control.
**[AUDIT REQUIRED]** Confirm Element Call widget bridge exposes speaker/audio-output control separately from microphone control.
**Complexity:** Medium.
**Complexity:** Medium.
@@ -1015,12 +1145,14 @@ All toggleable manually in Settings → Appearance regardless of date. Respects
---
---
### [ ] P5-25 · Message Length Counter in Composer
### [ ] P5-25 · Message Length Counter in Composer
**What:** Character counter near send button. Hidden <500 chars. Shows at 500+ (neutral), orange at 2000+, red at 3500+. TDS mono font styling.
**What:** Character counter near send button. Hidden <500 chars. Shows at 500+ (neutral), orange at 2000+, red at 3500+. TDS mono font styling.
**Complexity:** Low.
**Complexity:** Low.
---
---
### [ ] P5-26 · Right-Click Room Context Menu Improvements
### [ ] P5-26 · Right-Click Room Context Menu Improvements
**What:** Consolidated right-click menu: Mute with duration submenu (15min/1hr/8hr/24hr/Indefinite), Copy room link, Mark as read, Leave room, Room settings.
**What:** Consolidated right-click menu: Mute with duration submenu (15min/1hr/8hr/24hr/Indefinite), Copy room link, Mark as read, Leave room, Room settings.
**[AUDIT REQUIRED]** Audit current right-click menu to avoid duplicating existing actions.
**[AUDIT REQUIRED]** Audit current right-click menu to avoid duplicating existing actions.
**Complexity:** Low-Medium.
**Complexity:** Low-Medium.
@@ -1028,6 +1160,7 @@ All toggleable manually in Settings → Appearance regardless of date. Respects
**What:** Saved presets that change all notification settings atomically. Gaming (mentions only), Work (DMs + mentions), Sleep (all off). Quick-switch from sidebar or settings.
**What:** Saved presets that change all notification settings atomically. Gaming (mentions only), Work (DMs + mentions), Sleep (all off). Quick-switch from sidebar or settings.
**Complexity:** Medium.
**Complexity:** Medium.
@@ -1039,27 +1172,32 @@ These features are confirmed desirable but cannot be built until the listed depe
Check back after each Synapse upgrade — re-run `/matrix/client/versions` and `unstable_features` to see if they've become available.
Check back after each Synapse upgrade — re-run `/matrix/client/versions` and `unstable_features` to see if they've become available.
### [BLOCKED] · Live Location Sharing (MSC3489 + MSC3672)
### [BLOCKED] · Live Location Sharing (MSC3489 + MSC3672)
**Blocked by:**`org.matrix.msc3489 = false` AND `org.matrix.msc3672 = false` on `matrix.lotusguild.org` (confirmed from unstable_features).
**Blocked by:**`org.matrix.msc3489 = false` AND `org.matrix.msc3672 = false` on `matrix.lotusguild.org` (confirmed from unstable_features).
**What it would do:** Real-time GPS beacon streaming upgrading the existing static location share.
**What it would do:** Real-time GPS beacon streaming upgrading the existing static location share.
**Action when unblocked:** Both MSCs must be enabled on the homeserver before any client work.
**Action when unblocked:** Both MSCs must be enabled on the homeserver before any client work.
Research whether Matrix spec or MSC4133 (v1.16) defines a standard profile banner field. `uk.tcpip.msc4133.stable = true` on our server — check if a `banner_url` or similar field is defined. If no cross-client standard exists, do not implement.
Research whether Matrix spec or MSC4133 (v1.16) defines a standard profile banner field. `uk.tcpip.msc4133.stable = true` on our server — check if a `banner_url` or similar field is defined. If no cross-client standard exists, do not implement.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.