diff --git a/LOTUS_TODO.md b/LOTUS_TODO.md new file mode 100644 index 000000000..6954f0aae --- /dev/null +++ b/LOTUS_TODO.md @@ -0,0 +1,981 @@ +# Lotus Chat — Master Feature TODO + +**Location:** `/root/code/cinny/LOTUS_TODO.md` +**Repo:** `lotus` branch at `https://code.lotusguild.org/LotusGuild/cinny` +**Deploy:** push to `lotus` → CI → auto-deploy to `chat.lotusguild.org` (~11 min) + +This file is the single source of truth for all planned, in-progress, and backlog features. +Update it as features are completed or reprioritized. + +Legend: +- `[AUDIT REQUIRED]` — at least one assumption in the description needs code/server verification before implementing +- `[UPSTREAM CHECK]` — may already be in upstream Cinny mainline; confirm before building +- `[SERVER CHECK]` — depends on a Synapse feature or MSC that may not be enabled on `matrix.lotusguild.org` +- `[LOW PRIORITY]` — agreed to add but deprioritized; implement after everything else +- `[EXTREME COMPLEXITY]` — multi-sprint, architectural; plan separately before touching + +Status: `[ ]` pending · `[~]` in progress · `[x]` completed + +--- + +## COMPLETED + +- [x] Audit & document: who reacted hover tooltip — README + landing page +- [x] Audit & document: sticker/emoji panel — README + landing page +- [x] Audit & document: custom status messages — added to README Presence section +- [x] Audit & document: pinned messages — confirmed upstream, not added to README + +--- + +## PRIORITY 0 — Quick wins: low complexity, high daily value + +These are the easiest items to ship and immediately improve the daily experience. +Attack these first; most are single-file changes or simple new components. + +--- + +### [ ] P0-1 · Report Room (MSC4151) +**Spec:** MSC4151, merged ~Matrix spec v1.12. Synapse supports it. +**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: +``` +POST /_matrix/client/v3/rooms/{roomId}/report +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. + +--- + +### [ ] P0-2 · Server support contact display (MSC1929) +**Spec:** MSC1929, stable in Matrix spec. +**What:** On load (or when Settings → Help & About is opened), fetch: +``` +GET /.well-known/matrix/support +``` +Parse the `contacts` array and `support_page` URL. Display in Settings → Help & About: +- "Homeserver admin: @jared:matrix.lotusguild.org" +- 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. + +--- + +### [ ] P0-3 · Server notices distinct rendering (m.server_notice) +**Spec:** CS-API §13.17, stable. +**Confirmed:** Currently server notices arrive as plain DMs — indistinguishable from real messages. +**What:** Detect the `m.server_notice` event type in the timeline renderer. Render with: +- A distinct "Server Notice" header badge (server icon + label) +- Slightly different background color (use `color.Warning` or neutral surface) +- Composer disabled / read-only state in the server notice DM room +- Do NOT show "Send message" input in these rooms +**Where:** `src/app/features/room/RoomTimeline.tsx` (event renderer), `src/app/features/room/RoomInput.tsx` (hide/disable composer). +**[AUDIT REQUIRED]** — Verify how matrix-js-sdk exposes `m.server_notice` — check if it's a room type (`m.server_notice` in `m.room.create`) or per-event content field. +**Complexity:** Low. + +--- + +### [ ] P0-4 · Reaction / relation redaction (MSC3892) +**Spec:** MSC3892, in Final Comment Period. +**What:** When a user removes their reaction (un-reacts), currently a full `m.room.redaction` is sent targeting the reaction event. MSC3892 adds a cleaner relation-scoped redaction. Use the MSC3892 endpoint if the server supports it; fall back to standard redaction otherwise. +Check server capability: `GET /_matrix/client/v1/capabilities` for `m.room.redaction` capability or probe `/_matrix/client/unstable/org.matrix.msc3892/`. +**[SERVER CHECK]** — Verify `matrix.lotusguild.org` supports MSC3892. If not, no change is needed (current behavior is fine); if yes, use the cleaner endpoint. +**Where:** Wherever `onReactionToggle` sends the redaction — likely `src/app/features/room/RoomTimeline.tsx` or a hook. +**Complexity:** Low — conditional API call swap. + +--- + +### [ ] P0-5 · Rich room topic rendering (MSC3765) +**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). +**[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. +**Where:** Wherever the room topic is displayed in `src/app/features/room/RoomViewHeader.tsx` or similar. +**Complexity:** Low — reuse existing HTML renderer. + +--- + +### [ ] P0-6 · Edit history viewer +**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: +``` +Original: "Hello world" — 3:41 PM +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. + +--- + +### [ ] P0-7 · Room preview before joining (MSC3266) +**Spec:** MSC3266, merged Matrix spec v1.15. Synapse supports it. +**What:** When a user follows a `matrix.to` link or is invited to a room they haven't joined, show a preview card: +- Room avatar, name, topic, member count, join rule +- "Join Room" / "Request to Join" (if knock) / "Accept Invite" button +- "Back" option without joining +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) +**Spec:** MSC4431, in review. +**What:** Let a user rename a room locally — visible only to them. Stored in Matrix account data: +``` +PUT /_matrix/client/v3/user/{userId}/account_data/m.room_names +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. + +--- + +### [ ] 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 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 +**What:** A single action that sends read receipts for all rooms with unread counts, clearing every unread badge at once. Accessible via: +- 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. + +--- + +### [ ] 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: +1. Composer correctly wraps text in `` in `formatted_body` +2. Timeline renderer displays spoiler as blurred/hidden text with a "Reveal" click +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. + +--- + +### [ ] P0-12 · URL Preview default settings + security warning +**What:** In Settings → Privacy (or Messaging), the URL preview toggle should default to ON for both regular and encrypted rooms. Next to the encrypted-room toggle, add a one-sentence security note: +> "URL previews in encrypted rooms are fetched by your homeserver, which sees the URL but not the message context." +This matches Element's approach of informed consent rather than silent disabling. +**[AUDIT REQUIRED]** — Find where URL preview settings are stored in `settingsAtom`. Find the settings UI for URL previews. Confirm current defaults (may already be on by default for non-encrypted rooms). +**Where:** `src/app/state/settings.ts` (default values), `src/app/features/settings/` privacy/messaging panel. +**Complexity:** Low — wording + default value change. + +--- + +## PRIORITY 1 — High value, moderate effort + +Core features that meaningfully expand what users can do every day. + +--- + +### [ ] P1-1 · Quick Room Switcher (Ctrl+K / Cmd+K) +**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 +- Keyboard navigable: `↑`/`↓` to move, `Enter` to open room, `Esc` to close +- Shows unread badge count inline next to each result +- Pressing Ctrl+K again while open closes it +- Recent rooms shown immediately before typing +**Architecture:** +- New component: `src/app/components/QuickSwitcher.tsx` +- Register global hotkey via `useEffect` + `window.addEventListener('keydown')` in `src/app/pages/client/ClientRoot.tsx` +- Use the existing `OverlayContainerProvider` portal (from `App.tsx`) so the modal floats above everything +- Room data: `allRoomsAtom` + `mDirectAtom` from `src/app/state/roomList.ts` +- Style with `folds` `Menu`, `Box`, `Input` components matching existing patterns +- Use `is-hotkey` library (already in the project) for the keybind +**[AUDIT REQUIRED]** — Confirm upstream Cinny does NOT already have a quick switcher. Check for any existing Ctrl+K handler in `ClientRoot.tsx` or `App.tsx`. +**Complexity:** Medium. + +--- + +### [ ] 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: +- Tab bar: Images | Videos | Files +- Infinite scroll / paginated load via `GET /rooms/{roomId}/messages` filtering for `m.image`, `m.video`, `m.file` msgtypes +- Click image → full-screen lightbox (upstream Cinny lightbox) +- Click file → download +- Shows sender + timestamp tooltip on hover +- TDS-aware grid styling +**Architecture:** +- New component: `src/app/features/room/media-gallery/MediaGallery.tsx` +- Renders as a right-side drawer (similar to the members drawer pattern) +- Add icon button to `src/app/features/room/RoomViewHeader.tsx` +- Paginate backwards from latest event using `/messages?dir=b` +**[AUDIT REQUIRED]** — Confirm upstream Cinny has no existing media gallery or file browser. Check room header icons and room settings panels. +**Complexity:** Medium. + +--- + +### [ ] 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. +**Architecture:** +- Add filter `useState('')` to each tab component +- Filter the room array before rendering: `rooms.filter(id => displayName(id).toLowerCase().includes(filter))` +- Show a small `×` clear button when filter is non-empty +- TDS: mono font, dim border input matching the design system +**Where:** `src/app/pages/client/home/Home.tsx` (Home tab), DMs equivalent, Space room list. +**[AUDIT REQUIRED]** — Confirm upstream Cinny does NOT already have a filter input in room lists. +**Complexity:** Low-Medium. + +--- + +### [ ] P1-4 · Enhanced DM List (last message preview + timestamp) +**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:** +- 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 +- Format timestamp: "just now" / "X min ago" / "Yesterday" / date +- Falls back gracefully if the room has no messages yet +**[AUDIT REQUIRED]** — Check if upstream Cinny already shows message previews in DM list. Check `src/app/pages/client/direct-messages/` or `DirectTab.tsx` for existing DM list item rendering. +**Complexity:** Medium — need to extract last event body safely across encrypted/non-encrypted rooms. + +--- + +### [ ] 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 `