Commit Graph

173 Commits

Author SHA1 Message Date
jared 10f6544e2e fix: avatar decoration centering, animation flickering, scheduled message persistence
CI / Build & Quality Checks (push) Successful in 10m32s
Trigger Desktop Build / trigger (push) Successful in 7s
- UserHero: move AvatarDecoration inside AvatarPresence so the decoration
  inline-flex container sizes to the avatar only, not the presence badge
- SidebarNav: add will-change: background-position, background-size on
  document.body for animated backgrounds, promoting them to a compositor
  layer so overlaid text/UI doesn't repaint on every animation frame
- scheduledMessages: back the atom with atomWithStorage so the scheduled
  message tray survives page refreshes via localStorage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 00:32:04 -04:00
jared 7f329e3b31 fix(ui): chat background covers full screen regardless of glassmorphism
CI / Build & Quality Checks (push) Successful in 10m27s
Trigger Desktop Build / trigger (push) Successful in 19s
Previously the background was applied directly to <Page> (room view
only) when glassmorphism was off, and to document.body only when
glassmorphism was on. This caused two bugs:
- Without glassmorphism: background only visible in the chat panel,
  not behind the sidebar
- With glassmorphism: Page reverted to its opaque theme surface color,
  so the body background only showed through the glass sidebar

Fix: SidebarNav now always applies the active background to
document.body (regardless of glassmorphism). RoomView's <Page> is
made transparent whenever a background is active so the body
background shows through both the sidebar and the chat area.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 18:09:03 -04:00
jared bf1308dd55 feat: avatar decorations (P5-13)
CI / Build & Quality Checks (push) Successful in 10m30s
Trigger Desktop Build / trigger (push) Successful in 19s
256×256 APNG overlays from avatardecoration.com (open CORS CDN).
Stored in the user's Matrix profile as io.lotus.avatar_decoration via
MSC4133 so all Lotus Chat users see each other's decorations.

- avatarDecorations.ts: curated catalog of 110 original-IP decorations
  across 9 categories (Gaming, Cyber, Space, Fantasy, Elements,
  Japanese, Nature, Spooky, Cozy)
- useAvatarDecoration: per-user profile fetch with module-level cache
  and in-flight deduplication so concurrent renders for the same userId
  share one HTTP request
- AvatarDecoration: position:relative wrapper that overlays the APNG
  8px beyond the avatar on all sides; renders nothing when no decoration
  is set (zero cost for undecorated users)
- ProfileDecoration: scrollable grid picker in Settings → Profile,
  grouped by category with live preview; Save button appears only when
  the selection differs from what's saved
- Applied at all five avatar display sites: message timeline, members
  drawer, knock list, @mention autocomplete, notifications inbox

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 11:24:04 -04:00
jared 6db07f1371 feat: seasonal theme overlays + improved animated chat backgrounds
Adds 11 CSS-only seasonal overlays (Halloween, Christmas, New Year, Autumn,
April Fool's, Lunar New Year, Valentine's Day, St. Patrick's Day, Earth Day,
Deep Space, Retro Arcade) with date-based auto-detection and a manual override
dropdown in Settings → Appearance → Seasonal Theme. All themes respect
prefers-reduced-motion. SeasonalEffect mounts at z-index 9997 in App.tsx.

Also rewrites all 5 animated chat background keyframes for smoother, more
organic motion: Digital Rain gains a phosphor glow flicker; Star Drift now
loops each layer by exactly its own tile size (no more seam); Grid Pulse adds
an independent brightness oscillation at a prime period; Aurora Flow drives
all four gradient layers through distinct paths; Fireflies adds glow-pulse and
opacity-blink animations at prime periods for unsynchronised bioluminescence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 00:33:04 -04:00
jared 053b364a44 feat: route desktop matrix: deep links into the client
CI / Build & Quality Checks (push) Successful in 15m17s
Trigger Desktop Build / trigger (push) Successful in 4s
Adds useDeepLinkNavigate (mounted in ClientNonUIFeatures): listens for the
'lotus-deeplink' DOM CustomEvent that the Tauri shell dispatches when a
matrix:/matrix.to link opens the app, converts matrix: URIs to matrix.to, and
navigates via the same path as useMentionClickHandler (reusing matrix-to.ts
parsers + useRoomNavigate). No-op outside Tauri.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 18:52:23 -04:00
jared a9787ef041 feat: Windows taskbar notification badge counter
CI / Build & Quality Checks (push) Successful in 10m31s
Trigger Desktop Build / trigger (push) Successful in 13s
Adds a Tauri command (set_badge_count) that draws a red circle badge
with a highlight count onto the Windows taskbar button overlay icon,
matching Discord's behavior. Badge shows @mention/highlight count only
(not total messages), clears to zero when all highlights are read.

Frontend: useTauriNotificationBadge hook reads roomToUnreadAtom and
calls set_badge_count via window.__TAURI_INTERNALS__.invoke whenever
the unread map changes. No-ops silently in the browser (non-Tauri).
Mounted as TauriEffects inside JotaiProvider in App.tsx.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 17:32:38 -04:00
jared 5469740f4c feat: P5-21 mention color, P5-22 font selector, P5-27 notification presets; update docs
CI / Build & Quality Checks (push) Successful in 10m27s
Trigger Desktop Build / trigger (push) Successful in 7s
- P5-21: Custom @mention highlight color picker in Settings → Appearance.
  CSS vars with luminance-computed text color; resets cleanly to theme default.
- P5-22: Font selector (System, Inter, JetBrains Mono, Fira Code) in
  Settings → Appearance. Fira Code added to Google Fonts preload.
- P5-27: Gaming/Work/Sleep preset buttons at top of Settings → Notifications.
  Each atomically applies a group of notification settings.
- AppearanceEffects component in App.tsx applies CSS vars on settings change.
- LOTUS_BUGS.md: mark presence + manifest icon bugs as resolved.
- LOTUS_TODO.md: mark P3-6, P5-21, P5-22, P5-27 as [x].
- LOTUS_FEATURES.md: document all four completed features.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 15:39:35 -04:00
jared 78090d1c2d fix: glassmorphism actually visible + ESLint import/order
Glassmorphism root cause (from audit): the body-background useEffect
had `lotusTerminal && chatBackground === 'none' ? 'tactical'` as the
fallback guard — meaning the tactical grid only appeared when Lotus
Terminal mode was active. With default settings (TDS off, no chat
background chosen), the body got no background at all, so
backdrop-filter had a flat solid colour to blur — identical to unblurred.
Fix: drop the `lotusTerminal &&` guard so the tactical dot-grid is
always the fallback when chatBackground is 'none', regardless of theme.
Glassmorphism is now visible in all themes without any additional setup.

ESLint: RoomProfile.tsx had `../../../components/emoji-board` imported
before `matrix-js-sdk` violating import/order. Moved it after.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 14:47:18 -04:00
jared 08937c6278 fix: ESLint duplicate import + glassmorphism child opacity
ESLint: UploadCardRenderer.tsx had two separate imports from
../../utils/matrix — merged tryDeleteMxcContent into the existing
import statement. Removed now-unnecessary eslint-disable directive from
chatBackground.ts (the _anim prefix already suppresses the rule).

Glassmorphism: the Scroll inside SidebarNav had variant="Background"
which set a solid backgroundColor on the entire scrollable area,
completely hiding the sidebar's semi-transparent glass + backdrop-filter.
Fix: pass variant={undefined} when glassmorphismSidebar is on so the
inner scroll area is transparent and the blur effect is visible through
it. The document.body background (set by the previous useEffect fix)
now shows through the frosted glass as intended.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 13:41:29 -04:00
jared 9447e64e5e feat(P5-4): animated chat backgrounds + pause toggle
5 new CSS-only animated backgrounds in the chat background picker:
- Digital Rain: two-layer vertical stripe scroll with parallax (wide
  stripes at 8s, narrow at 4s via single keyframe with split positions)
- Star Drift: three-layer radial-gradient star field drifting diagonally
- Grid Pulse: neon grid lines that expand/contract (backgroundSize keyframe)
- Aurora Flow: large radial gradient bands sweeping across 200% canvas
- Fireflies: three layers of glowing dots drifting across the viewport

All use vanilla-extract keyframes (GPU-composited transforms/positions,
no canvas, no JS timers). prefers-reduced-motion is respected in
getChatBg() by stripping the animation property at call time. A "Pause
Background Animations" toggle in Settings → Appearance provides an
in-app override for the same purpose.

BG labels de-duplicated ("Digital Rain", "Star Drift", "Aurora Flow")
to avoid the duplicate "Stars" and "Aurora" entries that had appeared.
LIGHT anim-fireflies background corrected from near-black #0a0a10 to
warm white #fffdf0. Four unused keyframe exports removed from
Animations.css.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 11:46:16 -04:00
jared 8ac63e3771 fix: glassmorphism sidebar background visibility + image upload cleanup
Glassmorphism: the sidebar is a flex sibling of the room view, so
backdrop-filter had nothing behind it to blur. Fix: apply the active
chat background to document.body when glassmorphismSidebar is on
(cleaned up when it's turned off or the component unmounts). Now the
sidebar blurs through the same background pattern as the room view,
making the frosted-glass effect obvious.

Image upload cleanup: delete the pre-uploaded original MXC from the
homeserver after the compressed version is successfully uploaded
(Synapse 1.97+ DELETE /_matrix/client/v1/media/{server}/{mediaId}).
Also delete on cancel when a successful upload is removed by the user.
Both are best-effort — failures are swallowed so UX is unaffected.
Added tryDeleteMxcContent() utility to src/app/utils/matrix.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 11:25:19 -04:00
jared c6932b45fb feat: glassmorphism sidebar toggle (P5-3)
Settings → Appearance: "Glassmorphism Sidebar" toggle (off by default).
When enabled, applies backdrop-filter: blur(12px) and a semi-transparent
background to the left sidebar so chat background patterns show through.
SidebarGlass vanilla-extract class in Sidebar.css.ts; wired in
SidebarNav.tsx via classNames conditional.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 22:02:18 -04:00
jared c5fbc20394 feat: presence avatar border rings (P5-18) + room emoji prefix support (P5-6)
P5-18: PresenceRingAvatar wrapper component applies a 2px box-shadow
ring to user avatars — green (online), yellow (idle/unavailable), red
(DND via status_msg='dnd'), no ring (offline). Applied to: message
timeline sender avatars, members drawer (members + knock requests),
@mention autocomplete, and inbox notification senders.

P5-6: Leading emoji in room names renders at 1.15× in the sidebar via
Unicode emoji regex detection in RoomNavItem. Emoji picker (EmojiBoard
in PopOut) added to all three room-name inputs: Create Room dialog
(converted to controlled input), Room Settings name field (shown only
when canEditName), and the "Rename for me" local rename dialog.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 21:56:19 -04:00
jared 4876c2e4ca feat: quick emoji reactions on hover, in-app notification toasts, mention pulse audit
P5-17: MessageQuickReactions moved from 3-dots menu to hover toolbar;
shows 5 recent emoji directly on hover. Clicking a quick-reaction also
closes any open emoji picker (setEmojiBoardAnchor). Line separator
removed from component.

P5-7: LotusToastContainer slides in from bottom-right when window is
focused — replaces OS notification for in-focus events. Correct room
path (DM vs home) derived from mDirectAtom. Invite toast routes to
inbox. 4s auto-dismiss. Full TDS styling via CSS custom properties.

P5-8: Confirmed already implemented upstream (MentionHighlightPulse,
0.6s scale+glow, one-shot, prefers-reduced-motion). Marked complete.

Code-review fixes: toast navigation used nonexistent /room/ route;
emoji picker stayed open after toolbar quick-reaction.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 21:32:37 -04:00
jared 9273eb5f2e feat: bookmarks, message scheduling, image compression, room insights
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 5ae84cbeaf feat: P2-3 sort rooms, P2-5 quiet hours, P2-2 custom notification sounds
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 c798625a79 fix: DM filter input full width — add direction="Column" to wrapper Box
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 0370ed525a remove: ctrl+p suppress handler — Ctrl+K already covers search
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:33:01 -04:00
jared a3f776134f fix: ctrl+p print dialog, gallery 400 error, poll multi-choice UX
- 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 e18e089043 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 977fa8aa1b fix: sidebar filter inputs full width (grow=Yes on container Box)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 01:00:33 -04:00
jared 9d4679d260 feat: configurable keybindings for push-to-deafen and quick switcher
- 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 3cbc5112a7 fix: DM preview shows message body for E2EE rooms; filter inputs match members panel style
- 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 ed67641dd5 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 d43044ccbf 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 afe957015b 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 dedbd54199 feat: typing indicator orange dots, push-to-deafen hotkey, night light filter, message length counter
- #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 bdac3b2171 style: fix Prettier formatting in SettingsTab
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 23:59:46 -04:00
jared 51d45088c3 fix: restore avatar rendering and split presence dot into separate button
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 bd8e116cf3 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 582839fddb feat: document title unread count, draft persistence, search date range
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
jared 08b1a5c2a3 feat: full Discord-style presence tracking
- Announce online immediately on app startup
- Idle detection: unavailable after 10 min of no input, online on return
- Tab visibility: unavailable when hidden, online when focused again
- Page close: offline via fetch+keepalive (survives unload without bfcache penalty)
- hidePresence setting: broadcasts offline and stops all tracking
- Added 'Hide Online Status' toggle in General settings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 19:28:52 -04:00
Lotus Bot 5db4db1d95 fix: ESLint errors, stale disable comments, bundle splitting
- RoomTimeline.tsx: add eslint-disable comment for intentional eventsLength
  dep on timelineSegments useMemo (needed to detect in-place timeline mutations)
- Remove ~47 stale eslint-disable-next-line comments across 28 files for rules
  that are now off in the flat config (no-param-reassign, jsx-a11y/media-has-caption,
  react/no-array-index-key, etc); run prettier to reformat
- vite.config.js: move manualChunks from rollupOptions.output to
  rolldownOptions.output so Rolldown (Vite 8) actually applies it; main bundle
  drops from 3.5 MB to 814 kB gzip-248 kB, matrix-sdk gets its own 1.16 MB
  cacheable chunk

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 19:52:23 -04:00
Lotus Bot 3927f01089 fix: code splitting, route errors, Sentry CI source maps
- Lazy-import CreateRoomForm/CreateSpaceForm in CreateRoom.tsx and Create.tsx
  so create-room and create-space get their own chunks; eliminates
  INEFFECTIVE_DYNAMIC_IMPORT warnings
- Add RouteError component wired to root route errorElement so crashes show
  a reload button instead of React Router dev screen
- ci.yml: use secrets.SENTRY_AUTH_TOKEN so source maps upload on CI builds

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 19:30:55 -04:00
Lotus Bot e76f1f0ccb fix: sent messages not appearing + add Lobby/Auth skeleton loaders
- Fix timelineSegments useMemo stale cache: the Perf-5 optimization used
  timeline.linkedTimelines as its only dep, but that reference never changes
  when events are added in-place; adding eventsLength as a dep makes it
  recompute on every new live event so the binary search always finds the
  new item
- Add LobbySkeleton: shimmer placeholder for space lobby (header + hero +
  room list rows) shown while the Lobby chunk lazy-loads
- Add AuthSkeleton: shimmer placeholder for auth pages (logo + server
  picker + form fields) shown while AuthLayout chunk lazy-loads
- Wire both into Router.tsx fallback props

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:52:12 -04:00
Lotus Bot f24dff99ee fix: resolve all ESLint errors and fix CI Prettier failure
- Add jsx-a11y plugin to flat config (fixes definition-not-found errors)
- Turn off stylistic rules (no-console, no-continue, no-restricted-syntax, etc.)
- Downgrade no-explicit-any to warn; configure no-unused-vars to allow _ prefix
- Extend no-undef: off to .tsx files (TypeScript DOM types like PermissionName)
- Fix INEFFECTIVE_DYNAMIC_IMPORT: make HomeCreateRoom and Create lazy in Router
- Fix audioRef.current capture in CallEmbedProvider cleanup effect
- Fix JSX comment syntax in GifPicker (// → {/* */})
- Remove unused imports across 8 files
- Fix react-hooks/exhaustive-deps: add/remove missing/unnecessary deps
- Fix no-bitwise and no-shadow in RoomTimeline with eslint-disable comments
- Fix no-useless-concat in lotus-terminal.css.ts
- Fix Prettier formatting on src/index.tsx (extra blank line from prev commit)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 17:17:26 -04:00
Lotus Bot b088cb17b8 fix: silence expected console noise from OIDC discovery and router hydration
- ServerConfigsLoader: skip validateAuthMetadata when getAuthMetadata()
  rejects (404 on /auth_issuer means server uses traditional SSO, not
  native Matrix OIDC/MAS - this is expected and should not log errors)
- Router: use HydrateFallback={() => null} instead of hydrateFallbackElement={null}
  so react-router v7 counts it as truthy and suppresses the spurious warning

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 14:24:06 -04:00
Lotus Bot 7079462503 chore: upgrade React 18→19 and fix breaking type changes
- react 18.2.0 to 19.2.6
- react-dom 18.2.0 to 19.2.6
- @types/react 18.2.39 to 19.2.15
- @types/react-dom 18.2.17 to 19.2.3

React 19 breaking changes fixed:
- useRef<T>(null) now returns RefObject<T | null>; cast to
  RefObject<T> at 16 component call sites (safe, runtime unchanged)
- useRef<T>() without arg no longer valid; add | undefined>(undefined)
  in useDebounce, useFileDrop, useThrottle, useVirtualPaginator hooks,
  RoomInput, RoomTimeline, and ClientNonUIFeatures
- useReducer<typeof reducer> 1-arg form removed; drop explicit type arg
  in useForceUpdate (inferred from reducer function)
- global JSX namespace removed; import type { JSX } from react in
  react-custom-html-parser.tsx

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 13:24:07 -04:00
Lotus Bot d3fba1d685 fix: reduce ESLint errors and npm audit vulnerabilities
ESLint (476 → 187 errors):
- Fix import/first: move React.lazy() declarations after all imports in RoomInput.tsx and Router.tsx
- Disable react-hooks v7 React Compiler rules (refs, set-state-in-effect, immutability, purity, use-memo, react-compiler) - not using React Compiler yet
- Add eslint-disable for lotus-terminal.css.ts (no-explicit-any in CSS-in-JS)
- Add eslint-disable for cryptE2ERoomKeys.js (intentional bitwise crypto ops)
- Auto-fix 17 remaining fixable errors

npm audit (14 → 11 vulns, 5 → 3 HIGH in prod):
- Upgrade @giphy/react-components 5.9.4 → 10.1.2, js-fetch-api → 5.8.0, js-types → 5.1.0
- Add npm overrides to force dompurify >=3.3.4 and uuid >=11.1.1 in @giphy/js-util
- CI audit now uses --omit=dev to exclude devDep transitive vulns (lodash in commitizen)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 12:08:50 -04:00
Lotus Bot 266e47f240 chore: upgrade TypeScript 4.9 to 5.9, ESLint 8.29 to 8.57, @typescript-eslint 5 to 7
Resolves all TS2345/TS2347/TS7006 type errors introduced by stricter TypeScript 5.x.
Fix Icons.Settings to Icons.Setting, cast account data returns, fix implicit any.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 11:16:11 -04:00
Lotus Bot a5e5b3c413 fix: remove manual encodeURIComponent from pathUtils (react-router v7 encodes automatically)
react-router v7's generatePath() now calls encodeURIComponent() on all
path params. pathUtils.ts was also calling encodeURIComponent() before
passing to generatePath, resulting in double-encoding (e.g. '#' became
'%2523' instead of '%23').

This caused spaces/rooms with alias paths to receive double-encoded
room IDs from useParams(), which were then re-encoded by matrix-sdk
when making HTTP requests (400 Bad Request from Synapse).

Remove the manual encodeURIComponent() calls -- generatePath handles it.
2026-05-22 01:59:00 -04:00
Lotus Bot 7168b11323 fix: prettier formatting, viteStaticCopy paths, HydrateFallback warning
- Fix prettier formatting in useCall.ts and initMatrix.ts (unblocks CI)
- Fix viteStaticCopy stripBase so manifest.json and public/locales/ land
  at correct output paths (was getting extra 'public/' prefix from v4 path
  preservation behavior)
- Silence react-router v7 HydrateFallback warning on root route (SPA has
  no SSR hydration, null is intentional)
2026-05-22 00:36:30 -04:00
Lotus Bot 337d5e2b78 fix: graceful recovery for IDB schema version conflict
When matrix-sdk is briefly upgraded then reverted, the local IndexedDB
schema version is higher than the SDK expects. Detect the VersionError
DOMException and show a clear 'Clear local data and reload' button
instead of a cryptic error message.
2026-05-21 23:50:24 -04:00
Lotus Bot 61a1f008d0 chore: upgrade i18next 26, prettier 3, fontsource-variable, domhandler 6, lint-staged 17
- i18next 23->26 + react-i18next 15->17
- prettier 2->3, reformat all files
- replace @fontsource/inter with @fontsource-variable/inter 5, update import path
- domhandler 5->6 (aligns with transitive deps)
- lint-staged 16->17
2026-05-21 23:30:50 -04:00
Lotus Bot b3666fa876 fix: revert giphy 10.x (styled-components API break), clean WelcomePage
@giphy/react-components@10.x calls styled-components internals
(mergeAttributes) that do not exist in styled-components v6 — crashes
on open. Reverted to 1.6.0 until giphy publishes a v6-compatible release.

WelcomePage: remove Sentry test button (verified working), rename
Support -> Lotus Matrix Guide.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 23:09:13 -04:00
Lotus Bot d32055ee3d chore: bulk dependency updates + fix immer v11 default import + sentry test
Package updates (safe minor/major bumps, all build-verified):
- @tanstack/react-query 5.24->5.100, react-virtual 3.2->3.13
- jotai 2.6->2.20, immer 9->11, dayjs, chroma-js, classnames, blurhash
- slate/slate-dom/slate-react 0.123->0.124
- focus-trap-react 10->12, react-error-boundary 4->6
- html-dom-parser 4->7, html-react-parser 4->6
- pdfjs-dist 4->5, ua-parser-js 1->2
- i18next-http-backend 3->4, i18next-browser-languagedetector 8.0->8.2
- react-aria 3.29->3.48, matrix-widget-api 1.16->1.17
- @atlaskit/pragmatic-drag-and-drop* minor bumps
- @rollup/plugin-inject 5.0.3->5.0.5, @rollup/plugin-wasm 6.1->6.2
- @element-hq/element-call-embedded 0.19.3->0.19.4
- @types/* patches, eslint-plugin-* minors

Breaking change fix:
- immer v11 removed default export; updated 11 files to named import

Temporary: add Sentry test button to WelcomePage for verification.
Remove after confirming errors reach the dashboard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 22:23:19 -04:00
Lotus Bot 42b9cc2b64 chore: prettier format all files, brotli, Sentry release tagging, CI gates
Prettier: auto-formatted 103 files to fix baseline. Prettier check in CI
  is now a hard gate (removed continue-on-error).

Brotli: installed libnginx-mod-http-brotli-filter/static. Enabled in nginx
  with brotli_static on for pre-compressed assets and comp_level 6.

Sentry releases: deploy script now exports VITE_APP_VERSION=<git-short-sha>
  before building so each Sentry release maps to an exact commit.
  CI also passes github.sha as VITE_APP_VERSION.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 20:49:33 -04:00
Lotus Bot d121a22c15 feat: skeleton loaders, Sentry source maps, auto-deploy via webhook
RoomSkeleton: shimmer skeleton matching Room header/timeline/input layout,
  used as Suspense fallback for all three Room routes (home/direct/space)

Sentry source maps: @sentry/vite-plugin uploads 72 hidden source map files
  to Sentry on each build then deletes them from dist — stack traces now show
  real file/line numbers instead of minified bundle positions.
  Auth token loaded from /etc/lotus-deploy.env (not in git).

Auto-deploy: webhook receiver on port 9001, nginx proxies
  /hooks/lotus-deploy, HMAC-SHA256 verified, triggers on lotus branch push.
  Deploy script: git reset --hard + npm ci + npm run build + rsync to webroot.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 20:30:44 -04:00
Lotus Bot 6037e10a23 perf: split chunks and lazy-load Room to shrink initial bundle
manualChunks: add sentry, folds, i18n, jotai, immer
Router: lazy-load Room component (used in home/direct/space routes)
Sentry: wire in real DSN with browserTracingIntegration, 5% trace rate,
  tracePropagationTargets scoped to matrix.lotusguild.org, sendDefaultPii=false

Main bundle: 2481 kB -> 1857 kB gzip 623 kB -> 450 kB (-28% initial load)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 19:56:38 -04:00
Lotus Bot 41899adafa feat: add Sentry error tracking with defensive error boundary
- Initialize Sentry SDK in index.tsx when VITE_SENTRY_DSN env var is set
- Wrap entire App with Sentry.ErrorBoundary (replaces the hard crash with a retry UI)
- 5% trace sample rate, sendDefaultPii disabled, strip events containing accessToken
- Add .env.production template with VITE_SENTRY_DSN placeholder
- Get your DSN from sentry.io -> Project Settings -> Client Keys

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 19:44:51 -04:00