Mobile follow-ups round 2 (survey findings deferred from the mobile audit),
reviewed by 2 agents on the staged diff (both SHIP).
- URL-preview cards: the Twitch / Twitter / TikTok-fallback cards render
their thumbnail/header BESIDE the content as direct children of the
UrlPreview flex row, which squeezes both on a phone. Add `StackOnMobile`
(@media max-width:750px -> flex-direction:column) scoped to those variants
via cardClass. folds Box has no default `direction`, so the override wins
uncontested; desktop (>750px) is unchanged. No-op for the single-column
embed cards (MediaEmbedCard/TikTokEmbedCard).
- 44px touch targets (MobileTouchTarget, @media max-width:750px) on the
otherwise ~28px controls: embed-player Close/Collapse/Fullscreen/View-post
buttons; image-viewer close/zoom/download; the read-receipt "seen by" pill.
Deferred (rationale, not built): PiP resize handles + fullscreen button —
enlarging four 24px corners to 44px would swallow a ~160px mobile PiP and
block "Return to call"; presence dot is a non-interactive status indicator.
Gates: tsc 0, eslint 0, prettier clean, 856/856 tests, build ok.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Every ReadReceiptAvatars row and every useMemberAvatar registered its own global
RoomStateEvent.Members listener — ~6 per receipt row — each firing on any
membership / display-name / avatar change in ANY room.
Add a module-level MemberChangeStore (mirroring the PERF-1 presence store) that
registers exactly ONE global Members listener and fans out to subscribers keyed
by roomId|userId. Two hooks: useRoomMemberChange (single) and
useRoomMembersChange (multi, one effect). useMemberAvatar and ReadReceiptAvatars
use them; behavior (re-render triggers) is byte-for-byte equivalent. Unsubscribe
is idempotent via a set-identity guard; the multi-hook key is order-independent.
Unit-tested (key-scoped fan-out, single shared listener, idempotent unsubscribe).
Reviewed by two passes (lifecycle/closure + behavioral equivalence) — clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a reactive `useMemberAvatar(room, userId, w?, h?, resize?)` hook
returning { name, avatarUrl }, standardizing the member name + avatar
trio and the RoomStateEvent.Members reactivity pattern (N6). Convert
ReadReceiptAvatars and EventReaders to render per-user avatars via a
small child component using the hook, preserving exact sizes, fallback
rendering, TDS pill/tooltip styling and behavior.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
DP14: Message send-status icon and ReadReceiptAvatars pill hardcoded
dark-theme accent hex/rgba, so TDS light mode kept bright cyan/red
instead of the theme-overridden darker values. Route all colors/glows
through the theme-aware --lt-* CSS variables (mirroring EventReaders),
using color-mix for translucent tints and accent-alpha icon colors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ReadReceiptAvatars read avatar + display-name from room member state at render
time, so they only refreshed when the parent Message re-rendered (on a receipt
change). A reader changing their avatar/display-name left a stale avatar/name
until an unrelated re-render. Subscribe to RoomStateEvent.Members (the only signal
that fires for name, avatar AND membership changes — RoomMemberEvent has no Avatar
event) scoped to the displayed readers, and force a re-render so the pill updates
live. Listener is cleaned up; userIds identity is stable (memoized in the read-
positions map) so no subscribe churn.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completes the mobile fullscreen modal pass — adds useModalStyle to
DeviceVerificationSetup, DeviceVerificationReset, AddExistingModal,
RoomEncryption prompt, RoomUpgradeDialog, Modal500, ReadReceiptAvatars,
and RoomTopicViewer. All floating Dialog/Modal components now go
fullscreen on mobile (≤750px). UIAFlowOverlay was already fullscreen
via <Overlay>; JoinRulesSwitcher/RoomNotificationSwitcher are dropdowns.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
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>