Commit Graph
1464 Commits
Author SHA1 Message Date
Jannis Braun 65a1fc1402 fix: VP9 default for both modes, clean up codec pill UI
VP9 empirically outperforms H.264 in gaming (Chrome may not use
NVENC for screen share H.264). Codec pills now match other pill
styles — simple labels, no description text. Mode change no longer
auto-switches codec; user's codec choice is independent.
2026-03-24 00:59:04 +01:00
Jannis Braun 2b2e1c7386 fix: remove preventDefault on touchstart that killed all mobile taps
Calling e.preventDefault() on touchstart inside [data-context-menu]
elements prevented the browser from synthesizing click events
(touchstart → touchend → click chain). This broke tapping on spaces,
channels, DMs, and every other surface with data-context-menu.

Text selection prevention is already handled by CSS user-select: none
on [data-context-menu] elements — the JS preventDefault was redundant
and destructive. Reverted touchstart listener back to passive.
2026-03-24 00:14:09 +01:00
Jannis Braun 8897a5894c feat: smart codec defaults + mid-stream codec switching
- Gaming mode defaults to H.264 (NVENC hardware encoding, zero CPU)
- Text mode defaults to VP9 (better compression, CPU is free)
- Changing mode auto-updates codec to smart default
- User can manually override codec via UI pills
- Changing codec mid-stream auto-restarts the stream (~1s interruption)
2026-03-23 23:35:33 +01:00
Jannis Braun 894ce68194 feat: add manual codec selection (VP9/H.264) to stream settings
VP9 is default — better quality per bit with H.264 SIMULCAST backup
for Safari. H.264 option available for zero-CPU hardware encoding.
Codec choice is independent of gaming/text mode, which still controls
contentHint and degradationPreference.
2026-03-23 22:44:31 +01:00
Jannis Braun e7a18bd28c fix: screenshare codec fallback, native FPS, and connection info
- Gaming mode uses H.264 primary (hardware NVENC encoding, zero CPU
  impact on games). Text mode uses VP9 primary with H.264 backup
  and SIMULCAST policy for Safari compatibility.
- Fix native mode starting at 30fps by decoupling frameRate constraint
  from resolution constraint in both screenShare.ts overdrive and
  useLiveKit.ts updateActiveTracks.
- Filter paused backup codec tracks in Connection Info stats so dead
  0kbps entries don't show alongside active codec tracks.
2026-03-23 22:10:16 +01:00
Jannis Braun 7693cc737e fix: send voice_leave WS message from mobile disconnect handlers
MobileVoiceFullScreen and MobileVoiceMiniBar were missing voice_leave
(and dm_call_end) WS messages in their disconnect handlers, leaving
the server showing stale voice state. Now matches VoiceControls pattern.
2026-03-23 21:18:34 +01:00
Jannis Braun 0b7206487e chore: delete dead useLongPress.ts hook
Zero imports — useGlobalLongPress in ContextMenuRenderer handles
all long-press detection at the document level.
2026-03-23 21:12:32 +01:00
Jannis Braun 9f8a4ffa90 fix: prevent native text selection on mobile long-press
iOS triggered both the app context menu and native text selection overlay
on long-press. Three-layer fix: CSS user-select:none on buttons/[role=button]/
[data-context-menu] elements; non-passive touchstart listener with conditional
preventDefault on context-menu targets; removeAllRanges() at timer fire to
clear any residual selection. Added data-context-menu to all mobile surfaces
that use onContextMenu handlers.
2026-03-23 21:12:06 +01:00
Jannis Braun 161e876df8 fix: IncomingCallModal calls connectFn directly for iOS gesture context 2026-03-23 21:09:37 +01:00
Jannis Braun 241451ca18 feat: gesture-aware voice connection, remove AppLayout auto-connect
Replace the useEffect-based auto-connect pattern in AppLayout with
direct connect/disconnect calls from user gesture contexts. This is
required for iOS (AudioContext.resume + getUserMedia must happen in
a gesture handler) and aligns with tightening autoplay policies on
desktop browsers.

Architecture:
- voiceStore gains connectFn/disconnectFn refs, registered by AppLayout
  from the single useLiveKit() instance.
- All voice join paths (ChannelSidebar, MobileSpacesScreen, MainContent,
  voice_moved WS handler) pass connectFn to joinVoiceChannel().
- All disconnect paths (VoiceControls, voiceActions, MobileVoiceFullScreen,
  MobileVoiceMiniBar, dm_call_ended/rejected WS handlers, ready handler)
  call disconnectFn() directly.
- dm_call_accepted WS handler calls connectFn() to initiate the DM call
  LiveKit connection.
- The 55-line auto-connect useEffect and lastAttemptedRef are removed.
2026-03-23 21:06:19 +01:00
Jannis Braun 8aa66816d8 feat: add MobileVoiceJoinSheet bottom sheet for voice preview 2026-03-23 20:59:26 +01:00
Jannis Braun acf6f6fe99 refactor: align MobileVoiceFullScreen voice state resolution
Brings mute/deafen state resolution in line with VoiceChannel.tsx:
- Use LiveKit participant state (then WS fallback) for mute/deafen instead of only voiceUserStates
- Use local isMuted/isDeafened store values for the current user (self)
- Add spaceMutedUserIds, spaceDeafenedUserIds, permissionMutedUserIds checks
- Render deafened indicator (headphones-slash) when user is deafened vs muted
- Add LiveKit participant username and userId as terminal fallbacks in display name chain
2026-03-23 20:55:51 +01:00
Jannis Braun cd30774596 feat: show inline voice users in mobile sidebar 2026-03-23 20:52:12 +01:00
Jannis Braun 7c1ec5e846 refactor: use VoiceUserRow in desktop VoiceChannel 2026-03-23 20:46:47 +01:00
Jannis Braun ba11eefb0c fix: add isUnwatchedCamera prop and remove unused React import
Adds camera slash overlay for unwatched cameras, matching
VoiceChannel.tsx. Removes unnecessary React import (Vite
automatic JSX transform).
2026-03-23 20:44:44 +01:00
Jannis Braun 00c5757311 feat: add VoiceUserRow pure UI component 2026-03-23 20:40:29 +01:00
Jannis Braun f27140f1c8 feat: add space folder support to MobileSpacesScreen
Folder-aware layout resolution, folder icons in space strip,
bottom sheet folder flyout with rename/color/ungroup management.
Full folder CRUD via context menus: create, move to, remove from.
2026-03-23 19:34:24 +01:00
Jannis Braun 0d9ee44eee feat: add federation badges to mobile surfaces
Globe/amber badge on federated space icons, @domain on federated
DM users, home instance on the You screen profile card.
2026-03-23 19:27:39 +01:00
Jannis Braun 32be1bceab feat: add voice participant context menus on mobile
Long-press on voice participants shows moderation items (space mute,
deafen, disconnect, move) and local audio controls (mute, volume).
Skips self and DM calls.
2026-03-23 19:25:41 +01:00
Jannis Braun 937c4b68b2 feat: add leave group context menu on MobileDmsScreen
Long-press on group DMs shows Leave Group option.
2026-03-23 19:24:17 +01:00
Jannis Braun f84b1726bf feat: add context menus to MobileSpacesScreen
Add onContextMenu handlers for space icons (invite, folder management,
transfer, leave), channel items (settings, delete), and category headers
(settings, delete). Includes full folder management handlers.
2026-03-23 19:21:14 +01:00
Jannis Braun 8f104e1c5b fix: derive spaceId from voice channel in buildVoiceModMenuItems
Use channelToSpaceMap.get(channelId) for permission checks instead of
currentSpaceId from the navigation store. On mobile, UI navigation
decouples from the active voice session.
2026-03-23 16:32:47 +01:00
Jannis Braun fd6be8c713 fix: suppress native iOS callout on mobile long-press
Add -webkit-touch-callout: none on mobile viewports to prevent the
native iOS copy/lookup popup from racing with our context menu.
Clear any text selection when the long-press timer fires.
2026-03-23 15:26:09 +01:00
Jannis Braun ce33d8f7ea feat: global long-press context menus for all mobile touch targets
Add useGlobalLongPress to ContextMenuRenderer that detects 500ms hold
with <10px movement and dispatches a synthetic contextmenu event on the
touched element. All existing onContextMenu handlers (spaces, channels,
voice users, messages, etc.) now work on touch devices automatically.

Remove per-message useLongPress integration from Message.tsx since the
global handler covers it.
2026-03-23 15:12:58 +01:00
Jannis Braun 05a4b23d88 fix: prevent scroll snap-back during mobile momentum scrolling
ResizeObserver and media load callbacks were using isNearBottomRef
(5000px threshold) to decide whether to auto-scroll to bottom. On
mobile, inertial flick scrolling hadn't moved far enough to clear
that threshold before the observer fired, snapping the user back.

Now these callbacks check actual scroll distance at fire time with
a tight 150px threshold — only auto-scrolls if truly at the bottom.
2026-03-23 15:04:44 +01:00
Jannis Braun 2c372a5d34 fix: three mobile bugs — scroll, GeneralPanel loading, activity tab
1. Chat scroll: add flex flex-col to MobileChatScreen messages wrapper
   so MessageList's flex-1 actually works (parent must be flex container)

2. Instance General panel: add fetchInstanceSettings/fetchStreamingLimits
   to MobileInstancePanel mount (mirrors desktop InstancePanel useEffect)

3. Friends activity: add Activity tab to mobile FriendsPage showing
   friends grouped by activity status (active, online, offline)
2026-03-23 14:56:07 +01:00
Jannis Braun f90cf91760 fix: immediately clean up voice state when voice-active socket disconnects
Previously, voice cleanup only happened in finalizeDisconnect after a
5-second grace period, and only when ALL connections closed. If a user
was logged in on multiple devices and closed the one in voice, the
voice state was never cleaned up — creating a permanent ghost in the
channel sidebar.

Now removeConnection checks if the closing socket is the voice-active
one (tracked via voiceWs map) and immediately cleans up voice state,
broadcasts the leave event, and notifies remaining tabs.
2026-03-23 14:30:57 +01:00
Jannis Braun 319832ebc8 feat: clear voiceWs on all voice leave paths 2026-03-23 14:27:42 +01:00
Jannis Braun cc10876b93 feat: wire setVoiceWs into voice join and DM call handlers
Adds device-switch guardrail to handleVoiceJoin: if the user already
has a voice session on a different WebSocket, that old socket receives
a voice_disconnected/displaced event before the new session takes over.
Also binds voiceWs on the caller socket in handleDmCallStart and on
the acceptor socket in handleDmCallAccept.
2026-03-23 14:25:42 +01:00
Jannis Braun 357ba8b4d9 refactor: thread ws parameter through handleClientEvent to voice handlers 2026-03-23 14:23:48 +01:00
Jannis Braun 80c32ae1e6 feat: add long-press context menus for mobile messages
Create useLongPress hook with 500ms delay, 10px movement cancellation
(Euclidean distance), and ghost click suppression. Integrate into
Message component to trigger the existing context menu bottom sheet
on touch devices.
2026-03-23 14:22:38 +01:00
Jannis Braun 876326e7b7 feat: add voiceWs map and accessors to ConnectionManager 2026-03-23 14:22:28 +01:00
Jannis Braun 77a1a7920c feat: add standalone MobileMembersScreen replacing blank member panel
Create MobileMembersScreen that renders space members grouped by role
without depending on desktop memberListOpen flag or hidden md:block.
Add members button to voice full screen header for space channels.
2026-03-23 14:20:06 +01:00
Jannis Braun 54b367924d feat: add drill-down navigation for Instance settings on mobile
Replace inline InstancePanel render with MobileInstancePanel that shows
a navigation list for General, Streaming, Storage, and Users sub-panels.
Each sub-panel pushes onto the mobile screen stack with MobileScreenHeader.
2026-03-23 14:16:56 +01:00
Jannis Braun 7c40a230bc feat: add reusable MobileScreenHeader component
Extracts the back-button header pattern used across all mobile screens
into a single component. Props: title (string), rightActions (ReactNode).
2026-03-23 14:15:04 +01:00
Jannis Braun d8c5e7ff16 fix: restore mobile chat scrolling by fixing height constraint chain
Add overflow-hidden to MobileScreenStack stacked screen wrapper to
establish block formatting context. Add min-h-0 to MobileChatScreen
messages wrapper to override flex min-height: auto default.
2026-03-23 14:14:18 +01:00
Jannis Braun 58f6fb1baa fix: raise upload rate limit from 10 to 30 per minute 2026-03-23 02:55:00 +01:00
Jannis Braun 9a5e613331 feat: add upload progress bars and error toasts
Adds uploadWithProgress() using XMLHttpRequest for real-time upload
progress events. MessageInput now shows per-file progress bars with
percentage overlay during upload. Failed uploads show toast warnings
with the specific error instead of silently failing. Upload timeout
raised to 10 minutes for large files.
2026-03-23 02:51:56 +01:00
Jannis Braun fdc8732ba7 fix: raise upload size cap from 500 MB to 5 GB
The 500 MB cap was arbitrary. Self-hosted admins should decide based
on their own disk space. Global multipart ceiling raised to 5 GB,
validation cap to 5120 MB.
2026-03-23 02:36:22 +01:00
Jannis Braun 9044bfd26f fix: use toasts instead of inline banners for Storage panel feedback
Replaces the layout-shifting error banner at the bottom of the panel
with non-intrusive toast notifications. Upload limit save, cleanup
success, and all error feedback now use the existing toast system.
Cleanup preview results stay inline as expected contextual responses.
2026-03-23 02:32:57 +01:00
Jannis Braun 7ba6c4074e feat: add upload limit setting and media retention cleanup to Storage panel 2026-03-23 02:24:25 +01:00
Jannis Braun 4807bfeeb1 feat: read/write maxUploadSizeMb in instance settings API 2026-03-23 02:22:22 +01:00
Jannis Braun 5be2e73c8a fix: use per-request upload limit from DB and fix silent truncation
Reads max_upload_size_bytes from instance_settings per-request and
passes it to request.file() so Fastify kills the stream at the
admin's configured limit. Checks file.truncated to properly reject
files that exceed the limit instead of saving corrupted data.
2026-03-23 02:22:15 +01:00
Jannis Braun ee11408060 feat: add age-based media cleanup endpoint
Adds cleanupOldMedia() to the storage janitor and a new endpoint
POST /api/admin/storage/cleanup-media { maxAgeDays, dryRun }.
Deletes chat attachments older than the specified threshold while
preserving profile images.
2026-03-23 02:20:28 +01:00
Jannis Braun eb743ca2ca feat: add max_upload_size_bytes column and shared type 2026-03-23 02:19:42 +01:00
Jannis Braun 3c2c333a56 feat: show dangling attachment count in Storage panel
Adds a 'Dangling Records' card to the storage overview grid and
includes dangling records in the cleanup eligibility check.
2026-03-23 01:56:51 +01:00
Jannis Braun f634f35450 fix: add FK constraint to attachments.dm_message_id via table rebuild
Rebuilds the attachments table with ON DELETE CASCADE on dm_message_id.
Dangling records are excluded during copy — their files become standard
disk orphans detectable by the storage janitor. Also updates the raw SQL
in index.ts to include width/height/duration columns for fresh installs.
2026-03-23 01:55:26 +01:00
Jannis Braun 8bcb5cc977 fix: wrap DM message deletion in transaction for atomicity
Matches the space message delete handler pattern. Prevents inconsistent
state if the server crashes between individual delete operations.
2026-03-23 01:54:42 +01:00
Jannis Braun d687988ba8 fix: use explicit counter for danglingFilesOnDisk in referencedFiles calc
Matches the orphanedFiles counting pattern and avoids a subtle
discrepancy if two dangling records shared the same filename.
2026-03-23 01:53:25 +01:00
Jannis Braun b40999995b feat: detect and clean up dangling attachment records in storage janitor
Adds getDanglingAttachments() to find attachment records whose
messageId/dmMessageId points to a deleted message. Updates stats
to show dangling count and cleanup to delete them.
2026-03-23 01:49:41 +01:00