diff --git a/LOTUS_TODO.md b/LOTUS_TODO.md index b571d5ad9..871e4a322 100644 --- a/LOTUS_TODO.md +++ b/LOTUS_TODO.md @@ -90,8 +90,12 @@ DM last-message preview, Media gallery, Knock-to-join full UX | Chat backgrounds: `chatBackground.ts`, `` 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 | | `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 | +| 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` | | GIF links: render as generic OG preview cards, NOT auto-embedded | Task #42: needs explicit URL pattern detection + `` render | | 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. **Confirmed:** NOT present in upstream Cinny mainline. @@ -188,11 +192,12 @@ Body: { "reason": "string" } The homeserver forwards the report to server admins. **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. **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 Cache the response in component state; no repeated fetches. **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. **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) - 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'`). - **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. **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. **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. **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. **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. **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. **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: @@ -294,18 +304,74 @@ Parse the `contacts` array and `support_page` URL. Display in Settings → Help 3. Mobile touch works for reveal 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. - **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). **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." > **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. ---