docs: LOTUS_TODO.md — mark all P0 tasks complete, add June 2026 bug fix section

- P0-1 through P0-12: all marked [x] with implementation notes
- P0-4, P0-7: remain [BLOCKED] (MSC3892, MSC3266 not on server)
- P0-9, P0-10: remain [UPSTREAM — REMOVED]
- New INFRASTRUCTURE/BUG FIXES section documents: GIF CSP fix, unhandled
  rejection pattern, Copy Link relocation, MaxListeners bump, TDS hex
  violations fixed, CORS for well-known/matrix/support, relations API
  v1 vs v3 path fix
- Architecture facts table updated with 6 new confirmed findings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-02 14:18:09 -04:00
parent a6b5e03fe5
commit 2c7c617e2e
+83 -17
View File
@@ -90,8 +90,12 @@ DM last-message preview, Media gallery, Knock-to-join full UX
| Chat backgrounds: `chatBackground.ts`, `<Page>` in `RoomView.tsx:106` | Task #77: animated backgrounds need CSS class + `::before` pseudo-element | | Chat backgrounds: `chatBackground.ts`, `<Page>` in `RoomView.tsx:106` | Task #77: animated backgrounds need CSS class + `::before` pseudo-element |
| `KeywordMessages.tsx` already has custom keyword push rules | Task #61: only non-keyword rule types need new UI | | `KeywordMessages.tsx` already has custom keyword push rules | Task #61: only non-keyword rule types need new UI |
| `StateEventEditor.tsx` in Developer Tools edits any state event | Task #69: build user-friendly ACL UI in Permissions tab | | `StateEventEditor.tsx` in Developer Tools edits any state event | Task #69: build user-friendly ACL UI in Permissions tab |
| URL preview defaults: `urlPreview: true`, `encUrlPreview: false` | Task #49: one-line default change + one warning string | | URL preview defaults: `urlPreview: true`, `encUrlPreview: false` | Task #49: DONE — default changed + Warning chip |
| Private read receipts: `ReceiptType.ReadPrivate` + `markAsRead()` param exist | Task #34: trivially simple | | Private read receipts: `ReceiptType.ReadPrivate` + `markAsRead()` param exist | Task #34: trivially simple |
| Relations API at `/_matrix/client/v1/` NOT v3 | EditHistoryModal uses raw fetch with explicit v1 path |
| `mx.getContent()` returns post-edit content after SDK applies replacements | EditHistoryModal uses `mEvent.event.content` for the "Original" entry |
| GIF CSP: `connect-src` on LXC 106 must include `https://*.giphy.com` | Fixed in nginx — live, no deploy needed |
| `getLocalRoomNamesContent` is now `export`ed from `useRoomMeta.ts` | Used by `RoomNavItem` to avoid duplicating the parse logic |
| Right-click room menu: 6 items in `RoomNavItem.tsx:70-220` | Task #102: add Mute-duration submenu via `PopOut` | | Right-click room menu: 6 items in `RoomNavItem.tsx:70-220` | Task #102: add Mute-duration submenu via `PopOut` |
| GIF links: render as generic OG preview cards, NOT auto-embedded | Task #42: needs explicit URL pattern detection + `<img>` render | | GIF links: render as generic OG preview cards, NOT auto-embedded | Task #42: needs explicit URL pattern detection + `<img>` render |
| Toolbar buttons sequential array in `RoomInput.tsx` | Task #43: straightforward — no complex layout system | | Toolbar buttons sequential array in `RoomInput.tsx` | Task #43: straightforward — no complex layout system |
@@ -175,7 +179,7 @@ Attack these first; most are single-file changes or simple new components.
--- ---
### [ ] P0-1 · Report Room (MSC4151) ### [x] 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.
@@ -188,11 +192,12 @@ 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.
**COMPLETED June 2026.** Uses `mx.reportRoom()` SDK method. Category dropdown (Spam/Harassment/Inappropriate/Other). `role="dialog"` + `aria-modal` accessibility. Error discrimination (M_LIMIT_EXCEEDED, M_FORBIDDEN, 404). Auto-close 1.5s after success. Hidden for own rooms, server-notice rooms, and rooms on servers not advertising v1.13 (Synapse 1.114+ has the endpoint regardless — spec gate removed). Shown only via Invite menu (not three-dot menu).
--- ---
### [ ] P0-2 · Server support contact display (MSC1929) ### [x] 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:
@@ -207,11 +212,12 @@ Parse the `contacts` array and `support_page` URL. Display in Settings → Help
- Link to support page if present - Link to support page if present
Cache the response in component state; no repeated fetches. Cache the response in component state; no repeated fetches.
**Where:** `src/app/features/settings/` Help/About panel. **Where:** `src/app/features/settings/` Help/About panel.
**Complexity:** Low — one fetch, display result. **Complexity:** Low — one fetch, display result.
**COMPLETED June 2026.** Fetches `/.well-known/matrix/support` via `mx.getHomeserverUrl()`. AbortController cleanup. JSON type guard. Loading state. Clickable `matrix_id``matrix.to` link; `email_address``mailto:` link (both rendered when both present). `formatRole()` handles all role strings. **Server-side:** CORS `Access-Control-Allow-Origin: *` added to LXC 139 (NPM) for `/.well-known/matrix/support`. Static JSON response configured: `@jared:matrix.lotusguild.org` (admin), support_page `https://matrix.lotusguild.org`.
--- ---
### [ ] P0-3 · Server notices distinct rendering (m.server_notice) ### [x] 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.
@@ -221,7 +227,8 @@ Parse the `contacts` array and `support_page` URL. Display in Settings → Help
- 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`)
**Where:** `src/app/features/room/RoomViewHeader.tsx` (badge), `src/app/features/room/RoomInput.tsx` (hide composer when `room.getType() === 'm.server_notice'`). **Where:** `src/app/features/room/RoomViewHeader.tsx` (badge), `src/app/features/room/RoomInput.tsx` (hide composer when `room.getType() === 'm.server_notice'`).
**Complexity:** Low. **Complexity:** Low.
**COMPLETED June 2026.** "Server Notice" `Chip variant="Warning"` in room header with tooltip. Read-only composer replaced by informational `Box` message. Invite, Room Settings, and Report Room menu items hidden for server-notice rooms (both header three-dot menu and sidebar context menu). Distinct `Icons.Warning` icon for server-notice rooms in `getRoomIconSrc()`. Detection via `room.getType() === 'm.server_notice'` (type-based, not name-based).
--- ---
@@ -233,23 +240,25 @@ Parse the `contacts` array and `support_page` URL. Display in Settings → Help
--- ---
### [ ] P0-5 · Rich room topic rendering (MSC3765) ### [x] P0-5 · Rich room topic rendering (MSC3765)
**Spec:** MSC3765, merged Matrix spec v1.15. **Spec:** MSC3765, merged Matrix spec v1.15.
**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.
**Where:** `src/app/features/room/RoomViewHeader.tsx` — where the topic string is rendered. **Where:** `src/app/features/room/RoomViewHeader.tsx` — where the topic string is rendered.
**Complexity:** Low — reuse existing HTML renderer pipeline. **Complexity:** Low — reuse existing HTML renderer pipeline.
**COMPLETED June 2026.** `RoomTopicViewer` renders `formatted_body` via `sanitizeCustomHtml` + `html-react-parser`. Header shows clean plain-text preview (markdown symbols stripped); click opens full formatted modal. `RoomIntro.tsx` also shows clickable topic that opens the same viewer. **Room Settings topic editor:** `buildTopicContent()` auto-detects markdown syntax and saves `format: "org.matrix.custom.html"` + `formatted_body` + stripped plain `topic`; B/I/S/code formatting toolbar above the textarea. `sanitize.ts` hex regex updated to support 3/4/6/8-digit CSS4 hex colors.
--- ---
### [ ] P0-6 · Edit history viewer ### [x] 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.
**Where:** `src/app/features/room/message/Message.tsx` — find where "edited" label renders (line ~1231) and add onClick handler that opens the history modal. **Where:** `src/app/features/room/message/Message.tsx` — find where "edited" label renders (line ~1231) and add onClick handler that opens the history modal.
**Complexity:** Low — one API call, display list. **Complexity:** Low — one API call, display list.
**COMPLETED June 2026.** `EditHistoryModal.tsx` — fetches via raw fetch to `/_matrix/client/v1/rooms/.../relations/.../m.replace` (v1, not v3 — Synapse only supports relations at v1). `isRawEditEvent` type guard, `next_batch` truncation indicator (50-edit limit). Formatted HTML rendered via `sanitizeCustomHtml` + `html-react-parser` with Linkify fallback. `role="dialog"` + `aria-modal` accessibility. **Critical fix:** "Original" entry uses `mEvent.event.content` (raw server data) not `mEvent.getContent()` (SDK returns post-edit content after applying replacements) — prevents original and edit-1 showing identical text.
--- ---
@@ -263,13 +272,14 @@ Parse the `contacts` array and `support_page` URL. Display in Settings → Help
--- ---
### [ ] P0-8 · Personal room name overrides (MSC4431) ### [x] 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.
**Where:** Room nav item context menu (`RoomNavItem.tsx`), sidebar room list via `useRoomName()` hook. **Where:** Room nav item context menu (`RoomNavItem.tsx`), sidebar room list via `useRoomName()` hook.
**Complexity:** Low. **Complexity:** Low.
**COMPLETED June 2026.** `useLocalRoomName` + `useHasLocalRoomName` in `useRoomMeta.ts`. Account data key `io.lotus.room_names`. `RenameRoomDialog` with `maxLength={255}`, clear/save, pre-fills with current custom name. Pencil indicator with `config.opacity.P300`. FocusTrap nesting fixed (dialog rendered outside menu, menu closes before dialog opens). `getLocalRoomNamesContent` exported for shared use. Local name applied consistently across: room header (`RoomViewHeader`), sidebar nav (`RoomNavItem`), room intro (`RoomIntro`), call overlay (`CallRoomName`). Reactive to cross-device account data updates via `ClientEvent.AccountData`.
--- ---
@@ -285,7 +295,7 @@ Parse the `contacts` array and `support_page` URL. Display in Settings → Help
--- ---
### [ ] P0-11 · Spoiler text audit and fix if broken ### [x] 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:
@@ -294,18 +304,74 @@ Parse the `contacts` array and `support_page` URL. Display in Settings → Help
3. Mobile touch works for reveal 3. Mobile touch works for reveal
If any step is broken, fix it. If all working correctly, close this task with no changes. If any step is broken, fix it. If all working correctly, close this task with no changes.
**[AUDIT REQUIRED]** — Full code audit of spoiler mark in toolbar → event content → timeline renderer. **[AUDIT REQUIRED]** — Full code audit of spoiler mark in toolbar → event content → timeline renderer.
**Complexity:** Low (audit) — fix complexity TBD. **Complexity:** Low (audit) — fix complexity TBD.
**COMPLETED June 2026 — NO CHANGES NEEDED.** Full audit confirmed: spoiler composing (`data-md` + `data-mx-spoiler` in `markdown/inline/rules.ts:99`), MSC4193 stable property name at `types/matrix/common.ts:5`, rendering in `react-custom-html-parser.tsx:461`, image/video spoiler support in `ImageContent.tsx` and `VideoContent.tsx`. All working correctly.
--- ---
### [ ] P0-12 · URL Preview default settings + security warning ### [x] P0-12 · URL Preview default settings + security warning
**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.
**COMPLETED June 2026.** `encUrlPreview` default → `true`. Encrypted URL preview setting now shows: description text explaining homeserver sees all URLs + `Chip variant="Warning" fill="Soft"` badge "Privacy risk — enabled by default". Plain `urlPreview` setting also got a description. Both labels corrected to "URL Preview" (was "Url Preview").
---
## INFRASTRUCTURE / BUG FIXES — Resolved June 2026
These were not planned features but bugs and infrastructure gaps found during P0 testing.
---
### [x] BUG · GIF sending broken (CSP + domain allowlist)
**Root cause:** Two independent issues:
1. `connect-src` CSP on LXC 106 nginx included `https://api.giphy.com` (search API) but NOT `https://*.giphy.com` (CDN). Browser blocked `fetch()` to `media2.giphy.com` with CSP violation.
2. Domain allowlist in `RoomInput.tsx` was a hardcoded list of `media0-4.giphy.com` — didn't cover all Giphy CDN shards.
**Fix:** LXC 106 nginx CSP updated: `https://*.giphy.com` added to `connect-src` (live, no deploy needed). Domain check changed to `hostname.endsWith('.giphy.com')`. All silent failure paths now show user-facing error messages.
---
### [x] BUG · Unhandled promise rejections from fire-and-forget useEffect loads
**Root cause:** `useAsync` (in `useAsyncCallback.ts`) re-throws errors after storing them in `AsyncState.Error`. When `load()` / `loadThumbSrc()` etc. are called in `useEffect` without `.catch()`, the rejected promise hits the global `onunhandledrejection` handler → Sentry error JAVASCRIPT-REACT-M.
**Fix:** Added `.catch(() => undefined)` to all 6 fire-and-forget patterns: `useAsyncCallbackValue`, `ThumbnailContent`, `ImageContent`, `VideoContent`, `ClientConfigLoader`, `EditHistoryModal`. Error is already captured in state for UI display.
---
### [x] BUG · Copy Link buried in three-dot menu
**Fix:** Removed Copy Link from both `RoomViewHeader` three-dot menu and `RoomNavItem` sidebar context menu. Added "Copy Link" button with 2s "Copied!" confirmation to `InviteUserPrompt` modal header — naturally co-located with the invite flow.
---
### [x] BUG · `useLocalRoomName` MaxListeners warning (51+ rooms)
**Fix:** `initMatrix.ts` bumped `mx.setMaxListeners(50)``mx.setMaxListeners(150)`.
---
### [x] BUG · General.tsx hardcoded hex colors violating TDS
**Fix:** `#980000``color.Critical.Main` (chat background selector). `#FF6B00` / `rgba(255,107,0,0.35)``var(--accent-orange)` / `var(--accent-orange-border)` (Lotus Terminal replay button).
---
### [x] SERVER · CORS missing for `/.well-known/matrix/support`
**Fix:** LXC 139 (Nginx Proxy Manager) — added dedicated location block for `/.well-known/matrix/support` with `add_header Access-Control-Allow-Origin * always` and static JSON response: `{"contacts":[{"matrix_id":"@jared:matrix.lotusguild.org","role":"m.role.admin"}],"support_page":"https://matrix.lotusguild.org"}`. Matches existing pattern for `/.well-known/matrix/client` and `/server` blocks.
---
### [x] SERVER · Relations API returning 404 (wrong endpoint version)
**Fix:** `EditHistoryModal` was using `mx.http.authedRequest` which prepends `/_matrix/client/v3/`. Matrix relations API lives at `/_matrix/client/v1/`. Changed to raw `fetch()` with explicit `/_matrix/client/v1/rooms/.../relations/.../m.replace` path.
--- ---