Commit Graph

1814 Commits

Author SHA1 Message Date
jared 5de7f3523c fix: message scheduling 404 and date/time picker UX
CI / Build & Quality Checks (push) Successful in 10m28s
API fix: delay was embedded in the path string causing 404 — moved to
proper query param object; prefix changed from '' to the full MSC4140
unstable prefix so authedRequest builds the correct URL. Cancel and
restart endpoints fixed the same way.

Date/time picker: replaced single datetime-local (hard to use time
portion) with separate date + time inputs side by side; colorScheme:'dark'
hints the browser to render calendar/clock popups in dark mode to match
the app. Preview row now shows as a styled Primary.Container chip with
clock icon. Relative time ("in 2h 15m") shown below the label.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 15:05:09 -04:00
jared 7b8326bc31 fix: unverified device warning — rounded corners, left accent border
Replaced full-width sharp-cornered flat block with a compact rounded
notice: R300 border-radius, 3px amber left border accent, slight side
margins (S300) and bottom gap (S100) so it sits naturally above the
composer without looking jarring.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 12:48:19 -04:00
jared 49411540fb docs: mark P2-4 through P3-7 complete in LOTUS_TODO and README
CI / Build & Quality Checks (push) Successful in 10m25s
LOTUS_TODO.md: 12 features changed [  ] → [x] with COMPLETED June 2026
summaries: P2-4 export history, P2-6 activity log, P2-7 link previews
(13 domains), P2-8 extended profile fields, P2-9 local time display,
P2-10 unverified device warning, P2-11 push rule editor, P2-12 server
ACL editor, P3-1 bookmarks, P3-2 message scheduling, P3-3 compression,
P3-7 room insights

README.md: added entries for all 12 new features in their respective
sections; added 11 new rows to Key Custom Files table

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 12:40:18 -04:00
jared f1ca6e3a44 fix: compression UI — proper before/after display with folds tokens
Always shows Original size pill even before checkbox is checked.
After checking: shows 'compressing...' then reveals Compressed pill
with exact size and percentage saved. Green tint on compressed pill
when >5% saving; neutral when minimal gain.

Replaced all var(--bg-*) / var(--tc-*) CSS vars with folds
color.Surface.* and color.SurfaceVariant.* tokens so the UI renders
correctly in all themes. Blob cleanup is automatic — the compressed
Blob is referenced only during upload and GC'd with the upload atom.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 12:37:11 -04:00
jared c8b2de4a08 fix: schedule button, compression visibility, activity log, insights overflow, bookmarks UI
CI / Build & Quality Checks (push) Successful in 10m24s
Schedule message: modal now always opens (even with empty composer);
includes its own message textarea pre-filled from editor content;
removed null-content early-return guard from handleScheduleClick;
fixed error text to use color.Critical.Main not CSS var

Image compression: removed 200KB size threshold — checkbox now shows
for all JPEG/PNG uploads (not just large ones); 'no significant saving'
message handles already-small files gracefully

Activity log: auto-paginate on mount — state events are absent from
initial sync window, so the log was always empty until Load More was
clicked manually

Insights summary: Text size H4→H5 with toLocaleString() formatting and
overflow:ellipsis so large numbers don't push tiles off screen

Bookmarks panel: replaced var(--bg-*) CSS vars (undefined in folds
themes) with color.Surface/SurfaceVariant/Primary folds tokens; added
left accent border on message preview block, count badge, clear button
in search, improved empty state, cleaner button hierarchy

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 12:07:12 -04:00
jared d4d32e7a5e fix: prettier formatting on UploadCardRenderer.tsx
CI / Build & Quality Checks (push) Successful in 10m38s
CI caught missing prettier pass on the file modified by P3-3
image compression feature.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 10:38:34 -04:00
jared 6c2f8e0d8e feat: bookmarks, message scheduling, image compression, room insights
CI / Build & Quality Checks (push) Failing after 5m48s
P3-1: Message Bookmarks — right-click any message to bookmark; saved to
io.lotus.bookmarks account data (max 500, syncs across devices); star
icon in sidebar opens BookmarksPanel with filter, Jump-to-message, and
remove buttons; reactive to AccountData events

P3-2: Message Scheduling (MSC4140) — clock button next to send opens
ScheduleMessageModal with datetime-local picker; validates ≥1 min future;
calls PUT org.matrix.msc4140 delayed event API; collapsible
ScheduledMessagesTray above composer lists pending messages with cancel;
local Jotai atom tracks scheduled messages per room

P3-3: File Upload Compression — opt-in checkbox per JPEG/PNG file ≥200KB
in upload preview; canvas API compresses at 0.82 quality; shows before/
after size estimate; compressed blob used in upload when checked

P3-7: Room Insights — new Insights tab in room settings; top 5 active
members (bar chart), top 5 reactions (chips), media breakdown (4 tiles),
24-hour activity heatmap (CSS bar chart); all from local cache only with
disclaimer banner; never the first tab shown

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 10:26:08 -04:00
jared 9e9b021611 feat(P2-7): deep link preview cards for TikTok, X, Twitch, Reddit, YT Shorts
YouTube Shorts: portrait 9:16 thumbnail, red Shorts badge, channel parse
TikTok: portrait thumbnail, @user extract, caption parse (3 OG formats),
  hashtag chips, dark ♫ placeholder fallback
Twitter/X: tweet text parse from all og:title formats, media image when
  og:image:width>=300, profile vs tweet URL distinction, 𝕏 SVG badge
Twitch: live/clip/VOD detection, pulsing LIVE badge with CSS keyframes,
  game extraction from og:description, channel from URL
Reddit: r/subreddit badge, u/author + upvote + comment count parsed from
  og:description, post thumbnail 80x60, redd.it short URL support

Shared PortraitThumbnail (80x142) reused by TikTok + Shorts.
All brand hex colors in CSS file only, never in TSX.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 09:42:26 -04:00
jared d0e0069744 feat(P2-7): expand link previews to 13 domain-specific card types
CI / Build & Quality Checks (push) Successful in 10m23s
YouTube, Vimeo (shared VideoCard with thumbnail + play overlay),
GitHub (repo name/description parse), Twitter/X (tweet text parse),
Reddit (subreddit + post title), Spotify (artwork + track/artist),
Twitch (thumbnail + LIVE badge), Steam (game header image),
Wikipedia (clean text card), Discord (server icon + member count),
npm (package name/description), Stack Overflow (question excerpt),
IMDb (portrait poster + title/rating)

Generic fallback gains favicon from google/s2/favicons; empty cards
(no title or description) are suppressed. Shared SiteBadge component
with brand-colour CSS classes per domain in UrlPreview.css.tsx.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 23:34:23 -04:00
jared 7393a1471c feat: richer link preview cards and user local time display
CI / Build & Quality Checks (push) Successful in 10m25s
P2-7: Domain-specific URL preview cards — YouTube shows mqdefault.jpg
thumbnail with ▶ play overlay + title; GitHub shows inline SVG icon +
owner/repo parsed from og:title + star/language info from og:description;
generic cards get a Google favicon when og:image is absent; empty cards
(no title or description) are suppressed entirely

P2-9: Live local time in user profiles — useLocalTime(timezone, hour12)
uses Intl.DateTimeFormat with the user's m.tz IANA zone; updates every
60s; shows clock icon + formatted time + timezone abbreviation (EST/JST
etc.) in dim text; respects existing hour24Clock setting

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 23:15:29 -04:00
jared 877e1eaca7 feat: extended profile fields, push rule editor, server ACL editor
P2-8: Pronouns (m.pronouns) and Timezone (m.tz) fields in Settings →
Account → Profile; saved via MSC4133 PUT /profile/{userId}/{field};
useExtendedProfile hook fetches both in parallel; UserHero displays
pronouns below display name and timezone string below username

P2-11: Full push rule editor in Settings → Notifications below keyword
rules; covers override/room/sender/underride rule kinds; enable/disable
toggle per rule, human-readable labels for built-in rules, delete button
for custom rules, add-rule form for room and sender rules

P2-12: Server ACL viewer/editor in room settings (Server ACL tab);
reads m.room.server_acl state event; allow/deny server lists with
wildcard validation; allow IP literals toggle; power-level gated
(edit requires sufficient PL, otherwise read-only view)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 23:13:33 -04:00
jared 504c092c17 fix: export deduplication and PiP remote mute detection
CI / Build & Quality Checks (push) Successful in 11m32s
Export: timeline.getEvents() returns the entire growing window on every
pagination step, causing the same events to be added multiple times.
Fixed by tracking seen eventIds in a Set and skipping duplicates.

PiP mute: replace silence-inference with real remote participant mute
state. EC renders a [data-muted] attribute per participant tile with
aria-label=userId. Watch attribute changes via MutationObserver,
filter out local user, show overlay when any remote is muted.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 22:44:11 -04:00
jared 158fcd1309 feat: PiP mute indicator, export history, activity log, unverified device warning
CI / Build & Quality Checks (push) Successful in 10m29s
- PiP call window: mute overlay using MutationObserver on EC iframe's
  [data-testid="incall_mute"] button (data-kind="primary" = muted),
  same pattern as screenshare detection in CallControl.ts

- P2-4 Export Room History: new tab in room settings — Plain Text / JSON /
  HTML formats, optional date range, progress counter, paginated via
  paginateEventTimeline, blob download; E2EE-aware (skips failed decryptions)

- P2-6 Room Activity Log: new tab in room settings — filterable log of
  m.room.member, m.room.power_levels, m.room.name/topic/avatar/server_acl
  events with human-readable descriptions, relative timestamps, Load More
  pagination

- P2-10 Unverified Device Warning: warnOnUnverifiedDevices setting (default
  off); Warning.Container banner above composer in encrypted rooms with
  unverified devices; toggle in Settings → General → Privacy

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 22:13:22 -04:00
jared 2aca90b57d docs: mark P2-1/2/3/5 complete; update README and landing page
CI / Build & Quality Checks (push) Successful in 10m33s
- LOTUS_TODO.md: [x] P2-1 (upstream JumpToTime), P2-2 (custom sounds),
  P2-3 (sort rooms), P2-5 (quiet hours)
- README.md: new Notification Enhancements section (custom sounds, quiet
  hours); Room sort order added to UX & Composer section
- landing/index.html: three new comparison rows — custom notification
  sounds, quiet hours, room sort order

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 20:32:22 -04:00
jared bb8f9032ee feat: P2-3 sort rooms, P2-5 quiet hours, P2-2 custom notification sounds
CI / Build & Quality Checks (push) Successful in 10m27s
P2-3 — Sort Non-Space Rooms:
- homeRoomSort: 'recent' | 'alpha' | 'unread' setting (default 'recent')
- factoryRoomIdByUnread comparator: unread rooms first, tie-break by count
- Sort icon button in Rooms NavCategoryHeader opens PopOut menu with
  three options (Recent Activity / A→Z / Unread First), checkmark on active
- Collapsed state still filters to unread-only regardless of sort choice

P2-5 — Notification Quiet Hours:
- quietHoursEnabled / quietHoursStart / quietHoursEnd added to settings
  (defaults: false, '23:00', '08:00')
- isInQuietHours() helper handles both normal and overnight spans;
  start===end treated as zero-length window (disabled) to avoid silent no-op
- Both InviteNotifications and MessageNotifications gate notify() and
  playSound() behind the quiet-hours check
- Settings → Notifications: new Quiet Hours card with Switch + two
  <input type="time"> fields (only shown when enabled)

P2-2 — Custom Notification Sounds:
- messageSoundId / inviteSoundId settings: 'notification'|'invite'|'call'|'none'
- notificationSounds.ts: shared NOTIFICATION_SOUND_MAP (removes duplication
  between ClientNonUIFeatures and SystemNotification — code review fix)
- Audio source updated reactively via useEffect when sound ID changes
- Settings → Notifications: Message Sound + Invite Sound selects expand
  when the master sound toggle is on; each has a ▶ preview button
- playPreview() catches audio.play() rejections (code review fix)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 19:41:02 -04:00
jared 8e4cbc47c4 fix: invite modal QR toggle — replace {} icon with "QR Code" button label
CI / Build & Quality Checks (push) Successful in 10m21s
Icons.BlockCode is the {} curly-braces icon, which is unintuitive for a
QR code toggle. No QR-specific icon exists in folds, so replace the
IconButton with a labeled Button ("QR Code") that clearly communicates
its purpose. Also fix var(--bg-surface-border) → color.Surface.ContainerLine
in the QR panel border (the CSS variable doesn't exist in Cinny's theme).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 16:59:32 -04:00
jared 6ed1fc75de fix: DM filter input full width — add direction="Column" to wrapper Box
CI / Build & Quality Checks (push) Successful in 10m28s
Without direction="Column" the flex container defaults to row, so the
Input only takes its intrinsic width instead of stretching. Matches the
identical Box in Home.tsx which already had direction="Column".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 16:38:42 -04:00
jared 6d028e3749 fix: poll creator modal — replace non-existent CSS vars with folds tokens
CI / Build & Quality Checks (push) Successful in 10m17s
The modal was built with raw <div>/<input> and CSS custom properties
(--bg-surface, --bg-surface-low, --tc-surface-high, etc.) that don't
exist in Cinny's vanilla-extract theme, causing invisible/unstyled
inputs and a transparent background.

Rewrite to match the ReportRoomModal pattern:
- Overlay + OverlayBackdrop + OverlayCenter for the backdrop
- FocusTrap (clickOutsideDeactivates, escapeDeactivates via stopPropagation)
- Box as="form" with color.Surface.Container background and color.Other.Shadow
- Header variant="Surface" for the title bar
- folds Input variant="Background" for all text fields (replaces raw <input>)
- color.Critical.Main for error text
- Spinner before prop on submit button while submitting
- All spacing/radii from config.* tokens

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 16:03:45 -04:00
jared 715cd0076f fix: auto-clear dropdown unreadable in dark mode
CI / Build & Quality Checks (push) Successful in 10m20s
var(--bg-surface-variant) and var(--border-surface-variant) are not
defined in Cinny's vanilla-extract theme, so the select element renders
with a transparent/white background. Also native <option> elements ignore
most inherited CSS.

Fix: use folds color tokens (color.SurfaceVariant.*) for background,
border, and text color; add colorScheme:'dark' so the browser renders
the OS popup with dark styling; apply background+color to <option>
elements as a belt-and-suspenders fallback for browsers that honour it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 15:50:05 -04:00
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