Commit Graph

1795 Commits

Author SHA1 Message Date
jared ff5a569060 feat: gallery — video lightbox, auto-scroll load, month separators; fix 3 review bugs
CI / Build & Quality Checks (push) Successful in 10m20s
New features:
- Video playback: lightbox renders <video controls autoPlay> for MsgType.Video;
  thumbnail tiles show a play-button badge overlay; LightboxImage renamed to
  LightboxMedia to handle both types.
- Auto-load on scroll: IntersectionObserver on a sentinel div replaces the
  manual "Load More" button. Detaches while loading or when history is exhausted.
- Month separators: image/video grid grouped by month ("June 2026", etc.) with
  a hairline divider; separator only shown when more than one month is present.

Bugs fixed by code review:
- flatIdx++: index was incremented before the !thumbMxc null-guard, causing
  tiles rendered after a skipped event to open the wrong lightbox item. Guard
  is now checked first; flatIdx only increments when a tile actually renders.
- lightboxIndex never reset on tab switch: stale index kept the lightbox open
  (or opened the wrong item) after switching tabs. handleTabChange() now calls
  setLightboxIndex(null) alongside setTab().
- Silent catch retry storm: pagination errors left canLoadMore=true, causing
  the IntersectionObserver to re-fire handleLoadMore on every render cycle
  when the sentinel was still visible. Error state now sets loadError=true,
  removes the sentinel, and shows a manual Retry button instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 15:38:53 -04:00
jared d52b913737 feat: gallery — decrypt E2EE thumbnails, lightbox, hover overlays
CI / Build & Quality Checks (push) Successful in 10m22s
Root cause of padlock: all images in E2EE rooms have content.file so the
gallery skipped thumbnails for all of them. Fix:

- useDecryptedMediaUrl hook: downloads + decrypts encrypted media using
  downloadEncryptedMedia/decryptFile, creates a blob URL, revokes on
  unmount. For unencrypted media returns the HTTP URL directly.
- GalleryTile: prefers content.info.thumbnail_file (smaller encrypted
  thumb) over content.file; falls back gracefully. Shows spinner while
  decrypting, broken-image icon on error. Hover overlay shows sender
  name + relative date with a gradient.
- Lightbox: full-screen overlay with ← → keyboard/button navigation,
  filename/sender/date header, image counter. Full-res decryption done
  in LightboxImage (separate component per item so keys reset the hook).
- File list: shows sender name + file size (formatted KB/MB).
- Empty states: distinct messages for "nothing in recent events" vs
  "nothing found after loading more". "Beginning of history" shown when
  pagination exhausts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 13:14:28 -04:00
jared da6452f45c chore: remove stale agent worktree tracking files
CI / Build & Quality Checks (push) Successful in 10m17s
These were left behind by previous Claude Code agent sessions and were
causing "No url found for submodule path" warnings in CI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:44:26 -04:00
jared 6c364e2efb fix: prettier formatting in PollContent and MediaGallery
CI / Build & Quality Checks (push) Successful in 10m25s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:40:28 -04:00
jared d20e6bed92 remove: ctrl+p suppress handler — Ctrl+K already covers search
CI / Build & Quality Checks (push) Failing after 5m45s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:33:01 -04:00
jared cb848be0b6 fix: ctrl+p print dialog, gallery 400 error, poll multi-choice UX
CI / Build & Quality Checks (push) Failing after 5m53s
- Suppress Ctrl+P browser print dialog via SuppressPrintShortcut in
  ClientNonUIFeatures (no UI opened, just preventDefault)
- mxcUrlToHttp: build URL manually instead of delegating to SDK.
  The SDK forces allow_redirect=true when useAuthentication=true;
  Synapse's /_matrix/client/v1/media/thumbnail rejects that with 400.
  Manual construction omits allow_redirect entirely.
- Gallery: redesign using folds color tokens (color.Surface.*) instead
  of non-existent CSS custom properties; add ThumbState so broken
  images show an icon placeholder; use useAuthentication for thumbnails
  now that the URL builder is fixed; "Load More" always visible.
- PollCreator: replace raw <button> with folds Button components so the
  Single/Multiple choice toggle renders with actual visual difference.
- PollContent: support multiple-choice polls end-to-end —
  myVote:string → myVotes:Set<string>; computeVotes collects all
  m.selections (not just [0]); toggle-select for multi, radio for
  single; checkbox/radio indicator icons next to each option;
  "◉ Poll · Multiple choice" / "Single choice" label in header;
  sends full selections array on every vote event.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:23:44 -04:00
jared 205fbe3b0d docs: mark all P1 features complete in TODO; update README and landing page
LOTUS_TODO.md:
- P1-1: marked UPSTREAM REMOVED (Ctrl+K already exists as full room switcher)
- P1-2 through P1-11: all marked [x] with implementation notes

README.md:
- New sections: UX & Composer, Settings (Appearance), Calls (Extended)
- Documents: media gallery, sidebar filter, DM previews, favorites, poll
  creation, voice speed, invite QR, private receipts, knock-to-join,
  syntax highlighting, night light, push-to-deafen, typing dots, char counter
- Key Custom Files table updated with 4 new entries

landing/index.html (matrix repo):
- Polls row: "display & vote" → "create, vote & display"
- Voice messages: add speed control note
- 4 new UX table rows: Media Gallery, Sidebar filter, Favorite rooms, Invite link+QR
- also-available paragraph updated with all P1 additions
- Comparison date updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 10:36:53 -04:00
jared 4af07109c3 feat: QR code in invite modal; fix CSP to allow api.qrserver.com
CI / Build & Quality Checks (push) Successful in 10m30s
InviteUserPrompt: add QR code toggle button (Icons.BlockCode) in header.
When toggled, shows a 180x180 QR code image (api.qrserver.com) and the
raw invite URL below it, between the header and the search form.
inviteUrl computed once and shared between Copy Link and QR display.

Server: added https://api.qrserver.com to img-src in CSP header on
LXC 106 (/etc/nginx/sites-available/cinny) — nginx reloaded.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 10:10:25 -04:00
jared 69249d1746 fix: media gallery thumbnails — skip auth URL, handle encrypted media
- Use useAuthentication=false for thumbnail requests: the v1 authenticated
  URL adds allow_redirect=true which Synapse rejects with 400
- Encrypted events (content.file set) show a lock+filename placeholder
  since server can't thumbnail encrypted blobs
- Unencrypted thumbnails add onError handler to hide broken images gracefully

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 01:13:18 -04:00
jared c72cd7fef3 revert: remove redundant QuickSwitcher (Ctrl+K already does this better)
The existing SearchModalRenderer (Ctrl+K) is already a polished room/DM
switcher with avatars, unread badges, fuzzy search, and keyboard nav.
Our QuickSwitcher was an inferior duplicate. Removing it entirely:
- Delete QuickSwitcher.tsx
- Remove QuickSwitcherFeature from ClientNonUIFeatures
- Remove quickSwitcherKey from settingsAtom
- Remove Keyboard Shortcuts section from General settings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 01:03:50 -04:00
jared 3bf1bfd1be fix: sidebar filter inputs full width (grow=Yes on container Box)
CI / Build & Quality Checks (push) Successful in 10m30s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 01:00:33 -04:00
jared 9dee09af6f fix: poll multiple-choice toggle + Sentry JAVASCRIPT-REACT-N
CI / Build & Quality Checks (push) Successful in 10m24s
PollCreator: replace maxSelections/options.length stale-closure pattern
with isMultiple: boolean state. max_selections computed from filledOptions
at submit time. Radio inputs replaced with styled toggle buttons that
visually highlight the active selection.

PollContent: catch getPendingEvents error (Sentry JAVASCRIPT-REACT-N).
SDK throws Cannot call getPendingEvents with pendingEventOrdering ==
chronological when sending poll vote events with m.reference relation.
Silently catch so optimistic UI update stands — vote will retry on next
sync if needed.

Fixes JAVASCRIPT-REACT-N

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 00:55:50 -04:00
jared 6251d148d0 feat: configurable keybindings for push-to-deafen and quick switcher
CI / Build & Quality Checks (push) Successful in 10m43s
- Add deafenKey (default M) and quickSwitcherKey (default P) to settingsAtom
- Settings → Calls: Push to Deafen keybind tile using shared useKeyBind hook
- Settings → Keyboard Shortcuts: new section with Quick Room Switcher keybind
- Extract useKeyBind + keyLabel helpers to reduce duplication in Calls section
- CallControls reads deafenKey from settings (reactive, re-registers on change)
- ClientNonUIFeatures reads quickSwitcherKey from settings (same pattern)
- QuickSwitcher now toggles open/closed on repeat press (Ctrl+key again closes)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 00:45:43 -04:00
jared 2d59be9dd3 fix: DM preview shows message body for E2EE rooms; filter inputs match members panel style
CI / Build & Quality Checks (push) Successful in 10m23s
- RoomNavItem: change isEncrypted() to isDecryptionFailure() so DM
  previews show actual message body for successfully decrypted E2EE
  events instead of always showing 'Encrypted message'
- Home.tsx / Direct.tsx: upgrade filter inputs to size 400 / radii 400
  with search icon prefix to match the members list search bar style

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 00:39:39 -04:00
jared bb65c96454 fix: media gallery encrypted rooms + Ctrl+K double-menu
- MediaGallery: switch from createMessagesRequest (returns raw encrypted
  events) to room.getLiveTimeline().getEvents() which gives already-
  decrypted MatrixEvent objects. Load More uses paginateEventTimeline().
- QuickSwitcher: change hotkey from Ctrl+K to Ctrl+P to avoid conflict
  with the existing SearchModalRenderer mod+k handler

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 00:36:54 -04:00
jared 42422bbe61 fix: CI Prettier, P1-6 poll button, P1-11 stale knock state
CI / Build & Quality Checks (push) Successful in 10m33s
- LOTUS_TODO.md: Prettier formatting (CI gate fix)
- P1-6: Wire PollCreator into RoomInput — poll button (Icons.OrderList)
  opens modal, renders PollCreator when pollOpen is true
- P1-11: Reset knocked + knockError on room.roomId change via useEffect;
  add missing useEffect import to RoomIntro.tsx

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 00:14:55 -04:00
jared 82840dc4e2 fix: prettier format react-custom-html-parser.tsx
CI / Build & Quality Checks (push) Failing after 5m57s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 19:54:01 -04:00
jared 2adf3b4ad2 feat: P1 features — quick switcher, media gallery, DM previews, knock-to-join, syntax highlighting
P1-1: Quick room switcher (Ctrl+K/Cmd+K) — QuickSwitcher.tsx + ClientNonUIFeatures hotkey
P1-2: Media gallery drawer (images/videos/files) — MediaGallery.tsx + RoomViewHeader toggle
P1-4: DM last message preview + relative timestamp in RoomNavItem when direct=true
P1-7: Code syntax highlighting — TDS tokenizer (syntaxHighlight.ts), custom CSS theme
       (.prism-tds-dark/.prism-tds-light), applied in react-custom-html-parser.tsx
P1-11: Knock-to-join — "Request to Join" in RoomIntro + Pending Requests in MembersDrawer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 19:45:57 -04:00
jared f7c39e20a9 feat: P1 features — voice speed, private receipts, room filter, favorites, invite link, poll creation
P1-5: Voice message playback speed toggle (0.75×/1×/1.5×/2×) in AudioContent.tsx
P1-10: Private read receipts toggle in Privacy settings; wired to notifications.ts
P1-3: Room filter input on Home tab and DMs tab (client-side, clears on tab switch)
P1-8: Favorite rooms via m.favourite tag — Favorites section in Home sidebar, star/unstar in right-click menu
P1-9: Room invite link + QR code in room settings (Share Room tile, api.qrserver.com QR)
P1-6: Poll creation modal in composer (PollCreator.tsx, sends m.poll.start)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 19:31:30 -04:00
jared 90c5325618 docs: mark P5-5, P5-24, P5-25, #108 completed in LOTUS_TODO
CI / Build & Quality Checks (push) Failing after 5m56s
Night Light filter, push-to-deafen hotkey, message length counter,
and typing indicator orange dots all shipped June 2026.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 18:47:36 -04:00
jared 0b44c74ab8 fix: prefer-arrow-callback ESLint rule and Prettier formatting
CI / Build & Quality Checks (push) Failing after 5m42s
Convert TypingIndicator named function expression to arrow function to
satisfy prefer-arrow-callback lint rule. Auto-format RoomInput.tsx to
resolve Prettier check failures in CI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 18:43:02 -04:00
jared 6107da517f feat: typing indicator orange dots, push-to-deafen hotkey, night light filter, message length counter
CI / Build & Quality Checks (push) Failing after 5m39s
- #108: TypingIndicator reads lotusTerminal setting; applies var(--lt-accent-orange)
  to container so dots inherit via backgroundColor:currentColor
- #100: CallControls registers KeyM as push-to-deafen (e.code, e.repeat guard,
  ownerDocument.body iframe-safe editable check, [callEmbed] dep array)
- P5-5: nightLightEnabled/nightLightOpacity settings; position:fixed rgba(255,140,0)
  overlay inside JotaiProvider; Night Light tile + intensity slider (5–80%) in
  Settings → Appearance
- #101: RoomInput charCount state via Slate onChange + toPlainText; resets on
  room switch; displayed before send button when count > 0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 15:36:45 -04:00
jared 128d46652d fix: add m.server_notice icon case to getRoomIconSrc
CI / Build & Quality Checks (push) Failing after 5m40s
Server notice rooms were falling through to the default hash/lock icon
in the room list. Now return Icons.Warning before any other type checks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 14:47:48 -04:00
jared f9e355ce9d docs: remove resolved bug fixes section from LOTUS_TODO.md — it's a TODO list
CI / Build & Quality Checks (push) Failing after 5m43s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 14:19:21 -04:00
jared 2c7c617e2e 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>
2026-06-02 14:18:09 -04:00
jared a6b5e03fe5 fix: edit history original shows pre-edit content
CI / Build & Quality Checks (push) Successful in 10m20s
mEvent.getContent() returns post-edit content because matrix-js-sdk applies
the latest replace event in-memory. Reading mEvent.event.content gives the
raw server content (the true original before any edits). Edit entries from
the relations API correctly use m.new_content per Matrix spec.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 10:51:21 -04:00
jared 56f89ec939 fix: GIF CSP + edit history HTML rendering + unhandled rejection cleanup
CI / Build & Quality Checks (push) Successful in 10m24s
- nginx (LXC 106, live): added https://*.giphy.com to connect-src CSP —
  browser was blocking fetch() to media2.giphy.com CDN with CSP violation
- EditHistoryModal: render formatted_body as sanitized HTML (via
  html-react-parser + sanitizeCustomHtml) with linkification for plain
  text, matching how messages render in the timeline
- useAsyncCallback + ThumbnailContent + ImageContent + VideoContent +
  ClientConfigLoader: use .catch(() => undefined) instead of void to
  silence unhandled promise rejections from fire-and-forget useEffect
  calls — errors already captured in AsyncState.Error for UI display

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 10:34:46 -04:00
jared aa55ba1332 fix: suppress unhandled promise rejections from fire-and-forget useEffect loads
CI / Build & Quality Checks (push) Successful in 10m56s
useAsync re-throws errors after storing them in state — correct for awaited
callers but causes unhandled rejections when load() is called without .catch()
in useEffects. The error is already captured in AsyncState.Error so the
re-throw provides no additional value in these fire-and-forget patterns.

Fixes JAVASCRIPT-REACT-M (Sentry: Media download failed: 401 Unauthorized)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 10:01:04 -04:00
jared 50e4da8ea5 fix: GIFs, topic display, formatting toolbar, Copy Link in Invite, support URL
CI / Build & Quality Checks (push) Successful in 10m32s
- RoomInput: GIF domain check uses endsWith('.giphy.com') — handles all
  Giphy CDN shards; all silent failure paths now show user-facing error
- RoomProfile: topic plain-text field is now HTML-stripped (no markdown
  syntax visible in header); formatting toolbar (B/I/S/code) above the
  textarea wraps selected text with correct markdown syntax
- InviteUserPrompt: Copy Link button added to dialog header with
  'Copied!' confirmation; removed Copy Link from both three-dot menus
- RoomViewHeader/RoomNavItem: unused copy-link imports removed
- nginx (live): support_page URL updated from lotusguild.org →
  matrix.lotusguild.org

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 09:55:04 -04:00
jared c852b6f121 fix: P0 post-deploy bug fixes from live testing
CI / Build & Quality Checks (push) Successful in 10m36s
- EditHistoryModal: use raw fetch with /_matrix/client/v1/ path for
  relations API — Synapse only supports relations at v1, not v3 (fixes
  Sentry JAVASCRIPT-REACT-K / 404 error)
- RoomViewHeader: remove useReportRoomSupported spec-version gate —
  Synapse 1.114+ has the endpoint but only advertises spec v1.12;
  button now always shows for non-creator, non-server-notice rooms
- ReportRoomModal: handle M_UNRECOGNIZED/404 with "not supported by
  your homeserver" message
- RoomNavItem: add isServerNotice guard to Room Settings in sidebar
  context menu (was only guarded in header three-dots menu)
- initMatrix: bump setMaxListeners from 50 → 150 to prevent
  MaxListenersExceededWarning with large room lists
- RoomProfile: save topic with formatted_body + format when markdown
  syntax is detected; add markdown hint below topic textarea

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 22:14:21 -04:00
jared 6fbde91fc9 fix: remove unused useRoomName import, run prettier on all changed files
CI / Build & Quality Checks (push) Successful in 10m25s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 21:38:35 -04:00
jared 051d207079 fix: comprehensive P0 quality pass — audit findings resolved
CI / Build & Quality Checks (push) Failing after 5m43s
- ReportRoomModal: use mx.reportRoom() SDK method, fix undefined CSS vars
  (--mx-surface/border → folds color tokens), add role/aria-modal/aria-labelledby,
  accessible select/input labels, per-error-code messages, auto-close on success
- About.tsx: clickable matrix_id + email_address links (Text as="a"), AbortController
  cleanup, runtime JSON type guard, loading state, role display for all role values,
  remove classList theming hack, use mx.getHomeserverUrl()
- RoomViewHeader: useLocalRoomName for header title, useReportRoomSupported gate,
  hide Invite/Settings/Report for server notice rooms, isCreator guard on Report,
  FocusTrap returnFocusOnDeactivate on topic overlay, Server Notice chip tooltip
- RoomInput: replace raw <div> with folds <Box> for server notice read-only message
- EditHistoryModal: isRawEditEvent type guard, handle next_batch truncation,
  getVersionBody handles formatted_body (strips HTML for text display),
  role/aria-modal/aria-labelledby accessibility, guard for undefined eventId,
  use config.space tokens (remove var(--mx-spacing-*) strings)
- RoomNavItem: remove duplicate getExistingContent (use exported getLocalRoomNamesContent),
  maxLength={255} on rename input, fix FocusTrap nesting (renameDialog state moved to
  RoomNavItem_, RenameRoomDialog rendered outside menu, menu closes before dialog opens),
  pencil icon opacity via config.opacity.P300
- useRoomMeta: export getLocalRoomNamesContent for reuse
- RoomIntro: useLocalRoomName, formatted topic viewer with Overlay/FocusTrap/RoomTopicViewer
- CallRoomName: useLocalRoomName for consistent rename display in call overlay
- General.tsx: fix #980000/#FF6B00 hardcoded hex → color tokens/CSS vars, URL Preview
  capitalization, improved encrypted preview warning text + Warning chip, add
  description to plain urlPreview setting
- sanitize.ts: fix hex color regex to support 3/4/6/8 digit hex (CSS4 #RGBA, #RRGGBBAA)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 21:30:27 -04:00
jared ebdc6fc581 chore: prettier formatting for P0 feature files
CI / Build & Quality Checks (push) Failing after 5m39s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 20:55:06 -04:00
jared 683159bed8 feat: personal room name overrides (MSC4431-style)
Users can right-click any room and 'Rename for me...' to set a local
display name visible only to them. Stored in account data under
io.lotus.room_names. Shows a pencil indicator on renamed rooms.
useLocalRoomName() hook overrides useRoomName() when a local name exists.

Also includes:
- Rich room topic rendering via RoomTopicContent object (formatted_body
  support in RoomTopicViewer with HTML sanitization via sanitizeCustomHtml)
- Edit history viewer: clicking '(edited)' on a message opens a modal
  showing all prior versions with timestamps (EditHistoryModal.tsx)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 17:21:11 -04:00
jared 6135db3405 feat: server support contact display (MSC1929) and server notices UI
- Settings Help/About fetches /.well-known/matrix/support and displays
  admin contact + support page link (graceful 404 degradation)
- Server notice rooms (m.server_notice) now show a Warning badge in the
  room header and hide the message composer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 17:15:01 -04:00
jared dc5570f5f7 feat: add Report Room (MSC4151) and fix URL preview default for encrypted rooms
- Report Room: new ReportRoomModal with reason + category, POST /rooms/{id}/report
- URL preview: encUrlPreview default changed to true; security note added to settings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 17:11:21 -04:00
jared a893b13f97 docs: final audit pass — all 17 remaining audits resolved
CI / Build & Quality Checks (push) Failing after 5m49s
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>
2026-06-01 14:42:50 -04:00
jared 22e7252de8 docs: comprehensive audit pass — all 5 agent findings integrated into TODO
CI / Build & Quality Checks (push) Failing after 5m42s
Key findings:
- Jump to Date (task #7): DELETED — JumpToTime.tsx fully implemented upstream,
  wired in RoomViewHeader.tsx:215
- useUnverifiedDeviceCount() hook EXISTS (useDeviceVerificationStatus.ts:65) —
  task #65 is trivial
- useCallMembersChange() hook (useCall.ts:37-52) handles join/leave sounds
  via MatrixRTCSessionEvent.MembershipsChanged — correct approach for #89
- MessageQuickReactions already in hover toolbar (Message.tsx:146-184) — #92
  simpler than expected
- knockSupported() utility exists (matrix.ts:376) — #58 only needs RoomIntro button
- StateEventEditor in DevTools can already edit m.room.server_acl — #69 is a UX wrapper
- getMatrixToRoom() in matrix-to.ts already generates invite URLs — #24 just needs QR
- Glassmorphism: sidebar container safe for backdrop-filter (translateX only on items)
- Animated backgrounds: must use ::before pseudo-element, not backgroundImage
- matrix-js-sdk has no arbitrary profile field setters — #62 needs raw HTTP
- Toast system: build from scratch, insert at App.tsx:65 after OverlayContainerProvider
- JetBrains Mono already loaded via Google Fonts CDN in index.html:33
- MSC4151 report room endpoint confirmed live (405 on GET = POST-only endpoint exists)
- /.well-known/matrix/support not configured — needs server file creation + client reads

Full file reference table added with 30+ key file paths for all planned features.
Corrected 5 previously wrong architecture assumptions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 13:34:34 -04:00
jared c9a3edc142 docs: add blocked features section, upstream improvements, TDS design law
CI / Build & Quality Checks (push) Failing after 5m43s
- Blocked section: MSC3892 (reaction redaction), MSC3266 (room preview),
  MSC4306 (thread subscriptions), MSC4260 (report user) — all noted with
  unblock conditions and next steps
- 5 new [IMPROVE] tasks for upstream features: Jump to Latest (#104),
  spoiler transition (#105), report category (#106), speaking ring (#107),
  typing dots TDS (#108)
- TDS design law banner added at top of file and repeated in Implementation Notes
- Pinned messages count badge confirmed upstream (no action needed)
- Audits 1, 2, 4 marked complete; audit 3 (profile banner) remains open
- Upstream features table updated with improvement task cross-references

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 13:21:39 -04:00
jared ee17f6257e docs: complete audit pass — server checks, upstream checks, code architecture
Server findings:
- Synapse 1.153.0 is FULLY UP TO DATE (latest as of 2026-05-19)
- MSC4140 (scheduled msgs), MSC3771 (thread receipts), MSC4133 (extended profiles)
  all CONFIRMED supported via unstable_features flags
- MSC3892 (reaction redaction), MSC3266 (room summary) BLOCKED — not supported
- MSC4306 (thread subscriptions) BLOCKED — not supported

Upstream Cinny confirms (removed from build queue):
- Back to Latest button (RoomTimeline.tsx:2180), Mark rooms as read (Home.tsx:73),
  Tombstone/upgrade banner (RoomTombstone.tsx), Speaking indicator (useCallSpeakers.ts),
  Spoiler rendering (ImageContent/VideoContent — blur+click-reveal), Report message

Architecture facts documented:
- AvatarImage child constraint (no children — wrap externally)
- Sidebar translateX blocks backdrop-filter
- EC bridge: no participant events (use m.call.member state events instead)
- No in-app toast system (must build from scratch)
- Voice player at AudioContent.tsx:44, notification sounds hardcoded in ClientNonUIFeatures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 13:09:57 -04:00
jared 3ae99b9a69 docs: expand LOTUS_TODO.md with gamer/aesthetic feature wave and 30+ new items
CI / Build & Quality Checks (push) Failing after 5m42s
Adds Priority 5 section covering:
- Visual/theme: custom accent color, 5 new theme presets, glassmorphism,
  animated wallpapers, night light, font selector, seasonal themes
- Gamer UX: LFG command, quick reaction bar, soundboard, join/leave sounds,
  voice channel limit, AFK auto-mute, push-to-deafen hotkey
- Profile/avatar: frames, animated overlays, status-based border
- Chat polish: mention animation, collapsible messages, send animation,
  message length counter, quick reply from notification, custom mention color
- Utility: room context menu improvements, notification profiles
- Bug fix: drag-and-drop overlay doesn't dismiss on hover-away
- 4 pending audits added (profile banner, speaking indicator, etc.)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 01:02:27 -04:00
jared 685da48a6e docs: remove upstream Cinny features from README (stickers, pins, who-reacted)
CI / Build & Quality Checks (push) Successful in 10m47s
README.md only tracks Lotus Chat custom additions. Emoji/sticker picker,
pinned messages, and who-reacted viewer all ship with upstream Cinny main
and should not appear here. Custom status message (not in upstream) stays.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 23:07:40 -04:00
jared 2cbf38fe11 docs: document who reacted, sticker/emoji panel, pinned messages, custom status
Add proper README entries for four features that were implemented but
undocumented or only mentioned incidentally:
- Emoji & sticker picker in composer (sends m.sticker via mx.sendEvent)
- Pinned messages panel (header icon + context menu pin/unpin)
- Who reacted: hover tooltip + right-click ReactionViewer modal
- Custom status message: emoji picker, auto-clear timer, 64-char limit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 23:06:59 -04:00
jared c96e0a26f6 fix: clear status_msg when leaving DND presence state
CI / Build & Quality Checks (push) Successful in 10m21s
Pass status_msg: '' explicitly on setOnline/setOffline/setUnavailable(idle)
so the Matrix server overwrites the 'dnd' status_msg left from DND mode.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 10:57:27 -04:00
jared 8be0c646e3 style: fix Prettier formatting in SettingsTab
CI / Build & Quality Checks (push) Successful in 10m23s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 23:59:46 -04:00
jared a3dd873d36 fix: restore avatar rendering and split presence dot into separate button
CI / Build & Quality Checks (push) Failing after 5m41s
The previous version wrapped UserAvatar in a div inside SidebarAvatar,
which broke the folds Avatar CSS (expects AvatarImage/AvatarFallback as
direct child) — causing the white circle instead of the avatar.

New approach:
- SidebarAvatar has only UserAvatar as its direct child (restored)
- Clicking the avatar opens Settings directly (original behavior)
- PresencePicker renders a small absolutely-positioned button in the
  bottom-right corner of SidebarItem (which already has position:relative)
- Clicking the presence dot opens the status picker menu

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 23:52:15 -04:00
jared 7de0dfa3c6 fix(eslint): add missing useCallback deps, remove stale disable directives
- useMessageSearch: add fromTs/toTs to useCallback dep array (exhaustive-deps error)
- useMessageSearch: restore eslint-disable on the correct line for the `as any` cast
- VoiceMessageRecorder: remove two eslint-disable directives for rules that are
  globally off (jsx-a11y/media-has-caption) or not enabled (react/no-array-index-key)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 23:40:08 -04:00
jared b2a9040b60 docs: update README for wave-3 features and presence selector
CI / Build & Quality Checks (push) Successful in 10m20s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 23:30:10 -04:00
jared 7d223d8d45 feat: Discord-style presence status selector
Adds a manual presence picker to the sidebar user avatar. Clicking the
avatar opens a popout menu with Online, Idle, Do Not Disturb, Invisible,
and Auto (activity-based) options. The selected status is shown as a
colored badge on the avatar and stored in settings (survives reloads).

usePresenceUpdater now short-circuits for manual states and only runs
the full activity-tracking logic in Auto mode.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 23:29:29 -04:00
jared dfedba9ef8 feat: document title unread count, draft persistence, search date range
CI / Build & Quality Checks (push) Successful in 10m30s
E1 - Document title unread count: FaviconUpdater now also sets
     document.title to '(N) Lotus Chat' for mentions, '· Lotus Chat'
     for plain unreads, and 'Lotus Chat' when clear. Reuses the
     existing roomToUnread forEach loop.

E2 - Draft persistence across reloads: on room unmount, unsent message
     is written to localStorage as 'draft-msg-<roomId>'. On mount, if
     the Jotai atom is empty (page reload), the localStorage draft is
     restored. Cleared on send. Uses the existing Slate node JSON format.

E5 - Search date range filter: new DateRangeButton in SearchFilters
     with From/To date inputs in a PopOut. Dates stored as epoch ms in
     ?fromTs=&toTs= URL params. Passed to Matrix /search as from_ts /
     to_ts filter fields (valid spec fields, cast via 'as any' since
     SDK types don't include them yet).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 22:22:40 -04:00