- 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>
Add defensive check in folds Icon component so that if src is ever
undefined or non-function (root cause unknown, possibly data-dependent),
the SVG renders empty rather than throwing and crashing the whole app.
Also adds postinstall script to re-apply the patch after npm install.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes GHSA-q89c-q3h5-w34g: path traversal & URL injection via unsanitised
lng/ns parameters. Remaining open issues are all in devDependencies
(commitizen/lodash/tmp) or dev-server-only tools (esbuild/vite), with no
runtime impact on the production build.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Lobby, Explore/FeaturedRooms/PublicRooms, Inbox/Notifications/Invites are
now lazy-loaded via React.lazy so they only enter the bundle when navigated
to. Main bundle: 2547 kB → 2472 kB (gzip 637 → 618 kB).
Spoiler aria-pressed was initialised to false (revealed); changed to true
so the spoiler starts hidden, matching CSS logic (aria-pressed=true →
color:transparent).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously every visible Message subscribed to settingsAtom via useSetting,
creating O(80) active atom subscriptions. Now RoomTimeline reads it once
and passes it down as a prop, reducing subscriptions to 1.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
C-1 complete sweep across all components and features:
- Call controls: mic mute/unmute, deafen/undeafen, video, screenshare, chat
- RoomInput: dismiss reply, attach file, sticker, emoji, GIF, location, toolbar
- Media viewers: close in image/pdf/text viewers and editors
- Settings dialogs: close buttons in all room/space/common settings panels
- Lobby: back, toggle member list, scroll to top, pack add/remove
- Auth: server picker, UIA flow cancel
- Upload cards: cancel uploads
- URL preview: prev/next buttons
- Members drawer: close + scroll to top
- RoomViewHeader: back, start call, toggle member list
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Perf-3: Replace raw roomToUnreadAtom subscription in Home, Direct, Space with
selectAtom-derived Set<string> — components now only re-render when rooms
gain/lose unread presence, not on every notification count update
Perf-5: RoomTimeline eventRenderer now uses binary search on precomputed
timelineSegments instead of O(N×T) linear scan per visible message
A11y L-1: Add as=h2 semantic heading to Home, Direct, Inbox, Space page nav
titles so screen readers announce page sections correctly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BUG-18: clearTimeout cleanup in focusItem useLayoutEffect prevents leaked timers
BUG-24: Room timeline listener catches first poll vote before Relations object exists
BUG-25: Use timelineRef.current in handleOpenEvent to prevent stale index on rapid navigation
Perf-6: React.lazy + Suspense for GifPicker and EmojiBoard (initial bundle -114 kB)
Perf-7: React.memo on RoomNavItem to prevent re-renders on unrelated state
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When enabling PTT mode, save current mic state before muting.
When disabling PTT, restore saved state instead of unconditionally unmuting.
Prevents surprising unmute if user had manually muted before switching to PTT.
- Message.tsx: show delivery status (sending/sent/failed) on own messages when
no read receipts yet; hidden once server confirms (status null); TDS-styled
- GifPicker.tsx: move terminal CSS from runtime <style> tag into lotus-terminal.css.ts
eliminating flash of unstyled content (M-6)
- lotus-terminal.css.ts: add [data-gif-terminal] selector rules for GIF picker
PollContent now:
- Reads existing m.poll.response / org.matrix.msc3381.poll.response events
from the room timeline on mount to restore vote state across refreshes
- Counts votes per answer (per-sender latest-wins deduplication)
- Shows percentage bars and vote totals in real time
- Subscribes to RelationsEvent.Add/Remove/Redaction so counts update live
when other users vote without requiring a page reload
- Optimistic local update keeps the UI snappy while the send request flies
- CallEmbed: fix memory leak — mx event listeners were never removed
because dispose() called .bind(this) again, creating new function
objects. Now uses arrow class fields so start()/dispose() share the
exact same reference.
- callPreferences: toggleVideo is a no-op when cameraOnJoin=false,
preventing internal state drift from the returned value.
- CallControls: PTT key guard now blocks on SELECT elements and walks
the DOM for inherited contentEditable to prevent key interception
inside dropdowns and custom editors.
- RoomInput: GIF fetch validates Giphy CDN domain allow-list,
HTTP Content-Type header, and enforces 20 MB size cap.
These are superseded by IncomingCallListener in CallEmbedProvider (merged from v4.12.1). IncomingCallNotification was already removed from Router.tsx in a previous commit.
- ForwardMessageDialog: use sendEvent instead of sendMessage to preserve
the original event type (stickers, polls, etc.)
- PollContent: use m.selections for stable m.poll.response (per spec),
was incorrectly using m.responses
- Router.tsx: remove IncomingCallNotification (CallEmbedProvider.IncomingCallListener now handles all calls)
- CallEmbedProvider: restore touch drag (handlePipTouchStart), grip dots on resize handles, fix normaliseToTopLeft width/height
- FallbackContent/MsgTypeRenderers: add originalBody prop to show struck-through original text on deleted messages
- RoomTimeline: cache text message bodies so they can be shown after redaction