Adds opt-in (default OFF) auto-lock: after X minutes of no user input
the app calls signOut() (full memory wipe + PIN re-entry on next open).
Settings dropdown (Aus / 5 / 15 / 30 / 60 min) lives in SecurityCenter
below the existing wipe-on-close toggle. The idle timer is mounted in
AppShell via useIdleAutoLock; activity events are throttled to 1 Hz to
avoid timer thrash on rapid mouse movement. The localStorage key is
added to PRESERVE_LOCAL_STORAGE so a wipe never silently disables the
feature.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Audited all four i18next namespaces (common, auth, errors, app) against
static t() call-site grep across the entire desktop source tree.
Pruned 40 dead keys per locale, 80 total lines removed across 6 files.
Dead keys removed:
- app: call.{e2ee_active_hint,still_live,voice_connected},
chats.{show_archived,show_active}, admin.nav,
friends.confirm_unfriend, settings.{danger_zone,this_device,
presence,section_ringtone}
- auth: signed_in.{title,session_active,user_id,admin,yes,no,sign_out,
device_active,device_platform,device_registered_at},
entire device.* section (old device-registration UI)
- common: loading, cancel, retry, online, offline, idle, dnd, invisible
Dynamic-key patterns were found and respected: t('errors:'+code)
keeps all errors keys; t('app:presence.'+val) keeps all presence keys;
t('app:annotator.tool.'+id) uses defaultValue so its locale entries
were not required.
Fire a no-await profiles query in AuthContext on session establish to absorb
cold-connection latency before the first user-triggered request. Add
loading='lazy' default to the central Avatar component so all off-screen
avatars (chat list, friends list, popovers, message senders) skip eager
Supabase Storage fetches; set loading='eager' on ConversationHeader (active
conv header) and CallParticipantTile inline imgs (both AudioContent and
VideoStub) which are always above-the-fold when visible.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All 57 exported SVG icon components in icons.tsx are now memoised via
React.memo, giving React permission to skip re-renders when props are
referentially equal. Consumer icon-prop types updated from the legacy
SVGProps (includes string refs) to ComponentPropsWithoutRef<'svg'> so
the MemoExoticComponent return type satisfies TypeScript without casts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Update globals.css reduced-motion block: 0.01ms → 0.001ms durations
and add scroll-behavior: auto to suppress all transitions/animations
when OS reduced-motion preference is active.
- Gate canvas-confetti burst in GameModal behind matchMedia check so
the particle effect is skipped entirely for users who opt out of motion.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After the ciphertext UPDATE succeeds in editEncryptedMessage, delete the
existing message_mentions rows for that message then re-resolve and
re-insert from the new plaintext — matching the best-effort pattern used
by insertMessage. No signature change needed; conversationId and
newPlaintext were already present in EditMessageParams.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds conversation_games table type + game_make_move RPC to db-types, GamePayload variant and parseMessagePayload branch to shared/attachments, games.ts with createGame/getGame/makeGameMove wrappers plus pure tttWinningLine/c4WinningCells/c4DropRow/isBoardFull helpers, 11 tests covering all winner helpers, and re-export from chat/index.ts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mount useSoundboardSync in SoundboardManagerDialog, thread badges map through
SoundboardCategoryGroup/SoundboardRow, render a cloud-state glyph badge inline
with each row's size/mime metadata, and wrap handleDelete to attempt a
best-effort remote delete via deleteRemoteSound before the local deleteSound call.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a whiteboard branch to the MessageBubble ternary chain that renders
a card with an icon, label, and Öffnen button dispatching the
chatapp:open-whiteboard CustomEvent. ConversationPage now listens for
that event via a useEffect and calls setOpenWhiteboardId to open the
modal.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds WhiteboardPayload wire-format variant to MessagePayload/ParsedMessagePayload,
creates the whiteboards.ts CRUD wrapper (createWhiteboard, listWhiteboardStrokes,
insertWhiteboardStroke, clearWhiteboardStrokes), writes 5 unit tests (38/38 pass),
re-exports from chat/index.ts, and registers conversation_whiteboards +
whiteboard_strokes in packages/db-types so typecheck passes cleanly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Hover an image attachment in the composer to reveal a pencil-edit button;
clicking it opens ImageAnnotator and Save replaces the File in attachments[].
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shrink the StripToggleIcon SVG from 18x18 to 14x14 px so the 2-people
icon sits centred with ~11 px of breathing room inside its 36x36 button,
matching the h-4/w-4 small-icon convention used elsewhere in InCallPanel.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pass width/height as { ideal: N } constraints (not exact values) so
Chromium preserves the source's aspect ratio instead of cropping
non-matching monitors. Previously a 1920x1200 monitor under a 1080p
preset lost the bottom 120px including the Windows taskbar.
The auto-preset fallback now omits width/height entirely (both set to
undefined) so getDisplayMedia uses the screen's native resolution.
Cast through `unknown` to satisfy LiveKit's VideoResolution type, which
declares width/height as plain number but Chromium accepts the full
MediaTrackConstraints shape at runtime.