docs: final audit pass — all 17 remaining audits resolved

New findings:
- Service worker EXISTS at src/sw.ts — task #95 just needs notificationclick handler
- Highlight animation EXISTS in layout.css.ts:44-66 — task #81 just wires it to @mentions
- CallControl.toggleSound() EXISTS — task #100 push-to-deafen trivial
- Sanitizer strips <math>/<mml> — task #56 needs sanitizer changes too
- Folds uses vanilla-extract not CSS vars in non-TDS — task #74 needs theme variant
- Cinny cannot inject audio into EC stream — task #88 redesigned as local-only soundboard
- Policy list code: zero existing code, completely additive
- Notification dispatch: only 2 code points — task #12 is 4-line addition
- Upload preview: UploadCardRenderer.tsx:19-98 — task #36 insertion point found
- Room stats cache limited to ~80 events — task #45 must label clearly
- MSC3489/3672 live location: BLOCKED on server
- Profile banner: DROPPED — not in matrix-js-sdk or any Matrix standard

Server checks:
- /.well-known/matrix/support: 404 (needs server-side file creation)
- MSC3489 live location: false → task #64 added to BLOCKED section
- preview_url: requires auth (endpoint exists, works with user token)

Stale [AUDIT REQUIRED] tags scrubbed from P0 section.
All upstream-confirmed items removed or marked clearly.
Architecture reference table expanded with 15 new confirmed facts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 14:42:50 -04:00
parent 3a58d4f94d
commit 4223ac85d3
+67 -74
View File
@@ -73,34 +73,50 @@ DM last-message preview, Media gallery, Knock-to-join full UX
| ~~JetBrains Mono not bundled~~ | **Already loaded via Google Fonts CDN** in `index.html:33-35`. Other fonts: use `@fontsource` npm packages | | ~~JetBrains Mono not bundled~~ | **Already loaded via Google Fonts CDN** in `index.html:33-35`. Other fonts: use `@fontsource` npm packages |
| ~~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 #### Confirmed facts (all audits complete as of June 2026)
| Finding | Impact | | Finding | Impact |
|---|---| |---|---|
| `folds AvatarImage` does NOT accept children | Add frame/overlay inside `UserAvatar.tsx` component itself (see correction above) | | `folds AvatarImage` does NOT accept children | Add frame/overlay inside `UserAvatar.tsx` itself — optional `frameName` prop |
| No in-app toast system exists | Toast redesign (#80): build `ToastProvider` + Jotai atom queue; insert after `OverlayContainerProvider` at `App.tsx:65`; use `portalContainer` div from `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` |
| `useUnverifiedDeviceCount()` hook EXISTS | Task #65 is trivial: `src/app/hooks/useDeviceVerificationStatus.ts:65-106` — call it in `RoomInput.tsx` | | `useUnverifiedDeviceCount()` hook EXISTS | Task #65 is trivial: `src/app/hooks/useDeviceVerificationStatus.ts:65-106` |
| Voice player: `AudioContent.tsx:44-223` | Task #8: `playbackRate` on hidden `<audio>` at line 217 | | Voice player: `AudioContent.tsx:44-223` | Task #8: `playbackRate` on hidden `<audio>` at line 217 |
| Notification sounds: 2 hardcoded `.ogg` files in `ClientNonUIFeatures.tsx` | Task #22: replace hardcoded paths with `settingsAtom` value | | Notification sounds: 2 hardcoded `.ogg` files | Task #22: replace paths with `settingsAtom` value |
| Chat backgrounds: `chatBackground.ts`, applied to `<Page>` in `RoomView.tsx:106` | Task #77: animated backgrounds need CSS class + `::before` pseudo-element approach | | Chat backgrounds: `chatBackground.ts`, `<Page>` in `RoomView.tsx:106` | Task #77: animated backgrounds need CSS class + `::before` pseudo-element |
| `KeywordMessages.tsx` already implements 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 can already edit any state event | Task #69: build user-friendly ACL UI in Permissions tab, not from scratch | | `StateEventEditor.tsx` in Developer Tools edits any state event | Task #69: build user-friendly ACL UI in Permissions tab |
| URL preview: `urlPreview: true`, `encUrlPreview: false` in settings.ts | Task #49: one-line default change + warning string | | URL preview defaults: `urlPreview: true`, `encUrlPreview: false` | Task #49: one-line default change + one warning string |
| Private read receipts: `ReceiptType.ReadPrivate` in SDK, `markAsRead()` has param | Task #34: trivially simple | | Private read receipts: `ReceiptType.ReadPrivate` + `markAsRead()` param exist | Task #34: trivially simple |
| Right-click room menu: 6 items in `RoomNavItem.tsx:70-220` | Task #102: add Mute-duration submenu using `PopOut` same as Notifications item | | 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 | Task #42: inline GIF embed 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 (in order): Attach, Formatting, Emoji/Sticker, GIF, Location, Voice, Send | Task #43: sequential array in `RoomInput.tsx` after/before props | | Toolbar buttons sequential array in `RoomInput.tsx` | Task #43: straightforward — no complex layout system |
| `MessageQuickReactions` already in hover toolbar (4 recent emojis) | Task #92: increase limit, adjust layout position | | `MessageQuickReactions` already in hover toolbar (4 recent emojis) | Task #92: increase limit + layout tweak |
| `knockSupported()` utility EXISTS at `matrix.ts:376-391` | Task #58: just need "Request to Join" button in `RoomIntro.tsx:25-119` | | `knockSupported()` utility exists at `matrix.ts:376-391` | Task #58: only need "Request to Join" in `RoomIntro.tsx:25-119` |
| `CallControl.setMicrophone(bool)` at `CallControl.ts:206-212` | Task #84: call this directly for AFK auto-mute | | `CallControl.setMicrophone(bool)` at `CallControl.ts:206-212` | Task #84: call for AFK auto-mute |
| matrix-js-sdk has NO arbitrary profile field methods | Task #62: use `mx.http.authedRequest()` for MSC4133 raw HTTP calls | | `CallControl.toggleSound()` at `CallControl.ts:230-251` | Task #100: push-to-deafen — just wire a hotkey to this |
| `JumpToTime.tsx` FULLY IMPLEMENTED and wired in `RoomViewHeader.tsx:215` | Task #7: DELETED — already upstream | | matrix-js-sdk has NO arbitrary profile field methods | Task #62: use `mx.http.authedRequest()` for MSC4133 |
| Poll voting already implemented in `PollContent.tsx:189-213` | Task #9: only CREATE UI needed | | `JumpToTime.tsx` FULLY IMPLEMENTED, wired in `RoomViewHeader.tsx:215` | Task #7: DELETED — already upstream |
| `useRecentEmoji(mx, 4)` provides the quick reaction data | Task #92: same source as full emoji board | | Poll voting implemented in `PollContent.tsx:189-213` | Task #9: only CREATE UI needed |
| `getMatrixToRoom()` in `matrix-to.ts` already generates invite URLs | Task #24: just add QR code display to room settings | | `getMatrixToRoom()` in `matrix-to.ts` generates invite URLs | Task #24: just add QR code to room settings |
| `/.well-known/matrix/support` not configured on server | Task #67: client reads gracefully if present; Jared must CREATE the file server-side | | `/.well-known/matrix/support` returns 404 | Task #67: Jared must CREATE the file; client handles gracefully |
| MSC4151 report room endpoint returned HTTP 405 on GET → POST-only → EXISTS | Task #59: endpoint is live, just needs POST with JSON body | | MSC4151 report room: HTTP 405 on GET = endpoint exists (POST only) | Task #59: endpoint live, just POST with JSON body |
| `/timestamp_to_event` returned 401 (needs auth) → EXISTS | Jump to Date already works — task deleted as upstream | | `/timestamp_to_event` returns 401 = endpoint exists | Task #7 deleted — Jump to Date already upstream |
| `useCallSpeakers.ts` uses CSS MutationObserver polling on EC iframe DOM | Task #107: can augment with TDS ring animation on top of existing data | | `useCallSpeakers.ts` CSS MutationObserver polling | Task #107: TDS ring animation on top of existing data |
| Highlight animation EXISTS in `layout.css.ts:44-66` (2s infinite keyframe) | Task #81: wire to @mention events, make one-shot (0.6s) |
| Cindy CANNOT inject audio into EC call stream | Task #88: redesign as local-only soundboard |
| `toggleSound()` at `CallControl.ts:230-251` mutes EC `<audio>` elements | Task #100: push-to-deafen trivial — hotkey → toggleSound() |
| Folds uses vanilla-extract in non-TDS, NOT CSS custom properties | Task #74: must create new vanilla-extract theme variant dynamically |
| Theme presets need ~50 CSS custom properties each | Task #75: significant design work before coding |
| Sanitizer (`sanitize.ts`) allows table, div, span, a, code, hr | Task #82: LFG HTML card is safe locally; test on Element/FluffyChat |
| Sanitizer STRIPS `<math>`/MathML tags | Task #56: must also modify sanitizer to add math tags |
| Service worker EXISTS at `src/sw.ts` (session + media handling) | Task #95: just add `notificationclick` handler to existing sw.ts |
| No policy list code anywhere in codebase | Task #70: completely additive, zero conflict risk with Draupnir |
| Notification dispatch: 2 points (ClientNonUIFeatures.tsx:96, :165) | Task #12: 4-line addition across 2 functions + useQuietHours() hook |
| Developer Tools has current-state browser, NO history | Task #41: build audit log via /messages API with state event filter |
| Room names not processed (emoji displays fine, truncation may break emoji) | Task #79: simple render improvement; add emoji-aware truncation |
| Upload preview: `UploadCardRenderer.tsx:19-98`, `RoomInput.tsx:608-639` | Task #36: add compression before file enters upload queue |
| Room cache: PAGINATION_LIMIT=80 events, no total count API | Task #45: stats limited to loaded history; must label clearly |
| MSC3489/3672 live location: BOTH false on server | Task #64: BLOCKED |
| Profile banner: NOT in matrix-js-sdk v41.6.0-rc.0, not in MSC4133 yet | Task #91: DROPPED — not a standard Matrix field |
#### Key file quick reference #### Key file quick reference
| What you need | File | Lines | | What you need | File | Lines |
@@ -202,75 +218,47 @@ Cache the response in component state; no repeated fetches.
### [ ] P0-5 · Rich room topic rendering (MSC3765) ### [ ] P0-5 · Rich room topic rendering (MSC3765)
**Spec:** MSC3765, merged Matrix spec v1.15. **Spec:** MSC3765, merged Matrix spec v1.15.
**What:** Room topics can now include formatted text via the `m.topic` content block (bold, links, italic). Currently topics render as plain text in the room header. Pipe the `formatted_body` of the `m.room.topic` state event through the existing HTML/Markdown renderer (same renderer used for message bodies). **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.
**[AUDIT REQUIRED]** — Check if `matrix.lotusguild.org` Synapse version supports sending `m.topic` content blocks. Also check if existing room topics on the server have `formatted_body` set. The rendering improvement is worthwhile even if new formatted topics aren't being set yet. **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:** Wherever the room topic is displayed in `src/app/features/room/RoomViewHeader.tsx` or similar. **Where:** `src/app/features/room/RoomViewHeader.tsx` — where the topic string is rendered.
**Complexity:** Low — reuse existing HTML renderer. **Complexity:** Low — reuse existing HTML renderer pipeline.
--- ---
### [ ] 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.
**What:** When a message shows the "edited" label, clicking it opens a popover or small modal listing every prior version of the message with timestamps: **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.
Original: "Hello world" — 3:41 PM **Where:** `src/app/features/room/message/Message.tsx` — find where "edited" label renders (line ~1231) and add onClick handler that opens the history modal.
Edit 1: "Hello world!" — 3:42 PM
Edit 2: "Hello everyone!" — 3:45 PM (current)
```
Fetch edit history via:
```
GET /_matrix/client/v1/rooms/{roomId}/relations/{eventId}/m.replace
```
**[AUDIT REQUIRED]** — Confirm upstream Cinny does NOT already show edit history on click. If it does, this is upstream and should not be added to our README.
**Where:** `src/app/features/room/message/Message.tsx` — find where "edited" label renders and add click handler.
**Complexity:** Low — one API call, display list. **Complexity:** Low — one API call, display list.
--- ---
### [ ] P0-7 · Room preview before joining (MSC3266) ### [BLOCKED] P0-7 · Room preview before joining (MSC3266)
**Spec:** MSC3266, merged Matrix spec v1.15. Synapse supports it. **Spec:** MSC3266, merged Matrix spec v1.15.
**What:** When a user follows a `matrix.to` link or is invited to a room they haven't joined, show a preview card: **Server check result:** `GET /_matrix/client/v1/rooms/{roomId}/summary` returned **404** on `matrix.lotusguild.org`. Endpoint not available despite Synapse 1.153.0.
- Room avatar, name, topic, member count, join rule **Audit result:** Confirmed NOT in upstream Cinny — no pre-join preview screen exists.
- "Join Room" / "Request to Join" (if knock) / "Accept Invite" button **Status:** BLOCKED until homeserver exposes the `/summary` endpoint. When unblocked: build a preview card (avatar, name, topic, member count, join rule) shown before the user joins, with Join/Request/Accept buttons.
- "Back" option without joining **Complexity:** Low-medium (when unblocked).
Uses: `GET /_matrix/client/v1/rooms/{roomId}/summary`
**[AUDIT REQUIRED]** — Check if upstream Cinny already has a room preview screen before joining. Many Matrix clients have this. If Cinny has it, this is upstream and only Lotus-specific styling/improvements are needed.
**[SERVER CHECK]** — Verify `matrix.lotusguild.org` Synapse version supports the `/summary` endpoint.
**Where:** Likely in the routing/navigation layer when a room is selected but not joined.
**Complexity:** Low-medium.
--- ---
### [ ] P0-8 · Personal room name overrides (MSC4431) ### [ ] P0-8 · Personal room name overrides (MSC4431)
**Spec:** MSC4431, in review. **Spec:** MSC4431, still in review.
**What:** Let a user rename a room locally — visible only to them. Stored in Matrix account data: **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.
PUT /_matrix/client/v3/user/{userId}/account_data/m.room_names **Where:** Room nav item context menu (`RoomNavItem.tsx`), sidebar room list via `useRoomName()` hook.
Body: { "rooms": { "!roomId:server": "My Custom Name" } }
```
Access via right-click on room in sidebar → "Rename for me…". Show a small edit icon next to locally-renamed rooms. The original room name remains unchanged for all other members.
**[SERVER CHECK]** — MSC4431 is still in review; it uses account data which is universally supported even if the MSC isn't finalized. The account data key name may change when merged.
**Where:** Room nav item context menu, sidebar room list rendering.
**Complexity:** Low. **Complexity:** Low.
--- ---
### [ ] P0-9 · "Back to Latest" button ### [UPSTREAM — REMOVED] P0-9 · "Back to Latest" button
**What:** A floating pill button that appears at the bottom of the room timeline when the user has scrolled up (away from the live timeline). Displays "↓ Jump to latest" (and shows unread count if applicable, e.g. "↓ 12 new messages"). Clicking scrolls to the live timeline bottom and hides the pill. **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 REQUIRED]** — Confirm this does NOT already exist in upstream Cinny. Check `RoomTimeline.tsx` for any existing "scroll to bottom" UI — there is already a "Jump to Unread" chip (confirmed in code exploration); verify this is distinct.
**Where:** `src/app/features/room/RoomTimeline.tsx` — add a `TimelineFloat` element at the bottom, shown conditionally based on scroll position.
**Complexity:** Low.
--- ---
### [ ] P0-10 · Mark all rooms as read ### [UPSTREAM — REMOVED] P0-10 · Mark all rooms as read
**What:** A single action that sends read receipts for all rooms with unread counts, clearing every unread badge at once. Accessible via: **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.
- Right-click on the "Home" icon in the sidebar → "Mark all as read"
- Or a button in the Home view header
Iterates all rooms with `room.getUnreadNotificationCount() > 0` and calls `mx.sendReadReceipt(room.getLastActiveTimestamp())` for each.
**[AUDIT REQUIRED]** — Confirm upstream Cinny does NOT already have this. Check sidebar context menus and Home view for any existing "mark all read" action.
**Where:** `src/app/pages/client/sidebar/HomeTab.tsx` context menu or `src/app/pages/client/home/Home.tsx` header.
**Complexity:** Low.
--- ---
@@ -1050,6 +1038,11 @@ All toggleable manually in Settings → Appearance regardless of date. Respects
These features are confirmed desirable but cannot be built until the listed dependency is resolved. These features are confirmed desirable but cannot be built until the listed dependency is resolved.
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 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.
**Action when unblocked:** Both MSCs must be enabled on the homeserver before any client work.
### [BLOCKED] · Reaction / Relation Redaction (MSC3892) ### [BLOCKED] · Reaction / Relation Redaction (MSC3892)
**Blocked by:** `org.matrix.msc3892` = false on `matrix.lotusguild.org` **Blocked by:** `org.matrix.msc3892` = false on `matrix.lotusguild.org`
**What it would do:** Cleanly remove a reaction without redacting the parent message. **What it would do:** Cleanly remove a reaction without redacting the parent message.