Commit Graph
63 Commits
Author SHA1 Message Date
Jannis Braun 7c544c1ff4 feat: security hardening, DB indexes, token revocation, and input validation
- SSRF protection: DNS resolution + private IP blocking on metadata fetcher
- Upload security: CSP/X-Frame-Options headers, SVG forced download, nosniff
- Auth hardening: JWT secret min length, password min 8 chars, token revocation via password_changed_at
- Attachment ownership verification before linking to messages
- Message length limit (4000 chars) enforced on client and server
- Asset URL validation on avatar/banner updates
- Federation instance validation (domain regex, origin scheme, length limits)
- DB indexes on all FK columns for query performance
- Migrations: nullable moderator columns, dm_messages reply_to FK constraint
- File cleanup on avatar/banner replacement and space deletion
- Fastify trustProxy, AbortController on fetches, typing map size cap
2026-03-15 00:06:15 +01:00
Jannis Braun 9382477e33 fix: real-time friend request cancel/decline via WebSocket
Cancelled requests now disappear from receiver's UI instantly, and
declined requests revert the sender's discover card from "Request
Pending" to "Send Friend Request" — no page refresh needed.

Also includes the discover endpoint and sendFriendRequest return type
changes from the prior session.
2026-03-13 22:42:25 +01:00
Jannis Braun 07ef49eac0 feat: channel permissions UI, DM Sans font, private channel filtering, migration fix
- Rewrite ChannelSettingsModal with full tri-state permission override UI
  for roles and members (allow/neutral/deny per permission bit)
- Switch font from Inter to self-hosted DM Sans (woff2 variable fonts)
- Add client-side VIEW_CHANNEL filtering in ChannelSidebar for private channels
- Broadcast isPrivate flag on channel override changes
- Fix voice permission bit migration: gate behind persistent flag to prevent
  repeated re-runs that stripped STREAM from @everyone roles
- Add speakingUserIds set to voice store for efficient user-level lookups
- Clear current channel view when a channel is deleted
- Move .glass-strip to @layer utilities for proper CSS specificity
- Simplify avatar initials font size to proportional formula
2026-03-13 02:47:32 +01:00
Jannis Braun 8af155d08f fix: federation DM identity resolution — cross-instance isSelf() failure
Add a cross-instance self-ID registry to identity.ts so isSelf() can
recognize the current user's Snowflake IDs from all connected instances.
Previously, federated DMs showed the user themselves as the other party
because remote-instance IDs didn't match the home user ID.

- Register user IDs from every WS ready event (home + remote)
- Clear the registry on session reset (login/logout/register/delete)
- Fix isSelf() username comparison to parse both sides as federated
- Replace naive ID check in MessageList WelcomeHeader with isSelf()
2026-03-12 18:53:38 +01:00
Jannis Braun 83699d7e91 feat: LWW timestamps for bidirectional federation profile & layout sync
Profile and space layout changes on remote instances were being
overwritten by stale data on reconnect. Adds Last-Writer-Wins
timestamps so the client-relay mesh rejects stale writes:

- profile_updated_at column on users table with migration + backfill
- Server LWW guards on PATCH /users/@me and PUT /space-layout
- Bidirectional profileSync: pulls newer remote profiles to home
- LWW layout sync replaces home-authoritative _layoutFromTrueHome flag
- Layout pushes to ALL connected instances in parallel
2026-03-12 18:28:37 +01:00
Jannis Braun acbcf4d4e8 feat: federated space layout sync — home-authoritative with fallback
Federated users now have their sidebar layout synced from their true
home instance instead of each browsing instance maintaining a separate
disconnected layout. Layout saves route to the true home API with
automatic fallback to the browsing instance if unreachable.
2026-03-12 17:23:10 +01:00
Jannis Braun dbd964d40c feat: space sidebar drag-and-drop reordering with folder system
Add user_space_layout table and PUT /api/users/@me/space-layout endpoint
for persisting per-user sidebar ordering. Spaces can be freely reordered
via drag-and-drop, folders created by dragging one space onto another,
and folders auto-dissolve when they have fewer than 2 members. Includes
folder context menu (rename, color, ungroup), collapsed folder mini-grid
icons, multi-tab sync via WebSocket, and localStorage collapse state.
Removes the rigid native/federated split — federated spaces now intermix
freely while keeping their globe badge.
2026-03-12 03:38:22 +01:00
Jannis Braun 39174e5454 fix: channel deletion navigates to space root instead of @me
Add stale-channel guard in AppLayout that redirects to the space root
when the URL's channelId no longer exists in the channel list. Remove
the hardcoded navigate('/channels/@me') from ChannelSettingsModal and
the redundant setCurrentChannel fallback from the WS channel_deleted
handler — AppLayout's guard now handles all cases uniformly.
2026-03-12 02:55:52 +01:00
Jannis Braun 88ca2d532b feat: channel deletion UI with full backend cleanup
Add Delete Channel button to channel settings modal with ConfirmDialog
confirmation. Fix backend DELETE route to disconnect voice users, clean
up attachment files from disk, and remove orphaned read_states. Make
deleteChannel federation-aware in spaceStore and clean up voiceUsers
on channel_deleted WebSocket event.
2026-03-12 02:44:42 +01:00
Jannis Braun c51d6b1a0e feat: channel categories with drag-and-drop reordering
Add channel categories (named groups) with full CRUD, collapsible headers
with unread indicators, and native HTML5 drag-and-drop for reordering
channels within/between categories and reordering categories themselves.

- Schema: channel_categories table, category_id column on channels
- Server: category CRUD endpoints, batch channel-layout reorder endpoint
- WebSocket: categories in ready payload, category_created/updated/deleted
  and channel_layout_updated events with per-user VIEW_CHANNEL filtering
- Frontend: dynamic category-based sidebar layout replacing hardcoded
  Text/Voice sections, collapse state persisted to localStorage,
  category selector in CreateChannel modal, federation-aware store
2026-03-12 01:19:06 +01:00
Jannis Braun fc72e424d6 refactor: rename "Server Mute/Deafen" to "Space Mute/Deafen" across entire stack
Aligns voice moderation terminology with Backspace's "Spaces" branding.
Renames WS protocol strings, backend handlers, frontend store/hooks/utils,
user-facing labels, and documentation — 15 files, zero functional changes.
2026-03-12 00:12:33 +01:00
Jannis Braun 68a4c453de feat: drag-and-drop voice channel moves with federation fix
Add drag-and-drop support for moving users between voice channels
(MOVE_MEMBERS permission required). Fix voice_moved handler using
wrong user ID for federated users — now uses the same isHome/
getMyUserIdForOrigin pattern as adjacent voice handlers.
2026-03-11 23:44:20 +01:00
Jannis Braun c8e2945c07 feat: two-step registration with avatar/color picker, fix focus ring clipping
Refactor RegisterPage into a two-step flow: credentials first, then
personalization (display name, avatar upload, avatar color). Replace the
dual-panel sliding layout with conditional rendering and CSS keyframe
animations to eliminate overflow-hidden clipping of focus rings.

Supporting changes:
- Server accepts avatarColor on registration
- Auth store resets all user-scoped stores on login/register/logout
- Voice store gains resetSession() for full session cleanup
- Sync presence status to federated instances
- Propagate presence_update to socialStore regardless of origin
2026-03-11 03:43:20 +01:00
Jannis Braun 3790386a5f feat: real-time user profile updates and propagate avatarColor to all Avatar callsites
Broadcast user_updated events over WebSocket when profile fields change,
updating members, DM participants, friends, and cached messages in real time.
Widen useVoiceParticipantMeta to return the full user object and add a
standalone avatarColor prop to Avatar so all ~16 callsites now resolve
the user's chosen gradient color instead of falling back to hash-based colors.
2026-03-11 01:12:12 +01:00
Jannis Braun d7449bdf42 fix: load social data on WS ready and use federation-safe canonical matching for friend button state
Friends/requests were only loaded when FriendsPage or ActivityPanel rendered,
so profile modals in space views always showed "Add Friend" even for existing
friends. Now loadFriends/loadRequests fire on every WS ready event.

Also adds canonicalUserMatch() with cascading ID/homeUserId/username+instance
fallback, replacing fragile homeUserId-only matching in getFriendshipStatus.
2026-03-10 19:56:13 +01:00
Jannis Braun ce63c5ed36 feat: real-time SPEAK permission enforcement in voice channels
Permission changes now take effect immediately without requiring
disconnect/reconnect. Modeled as "permission mute" parallel to
server mute — server recomputes SPEAK for all voice participants
on role/override changes and broadcasts state via WebSocket.
Includes amber UI indicators and mic toggle blocking.
2026-03-10 14:50:01 +01:00
Jannis Braun e8fc40ab34 feat: add voice disconnect permission and fix federation identity
- Add DISCONNECT_MEMBERS permission (bit 27) to disconnect users from voice
- Implement voice_disconnect WebSocket handler with permission checks
- Add disconnect button to voice user context menu
- Grant instance admins full permissions across all spaces
- Fix voice_disconnected handler to use federation-aware identity resolution
- Update CLAUDE.md with new event types and permission docs
2026-03-10 02:13:20 +01:00
Jannis Braun 11a69fe92a fix: cache federated user identity from WS ready to fix server mute/deafen
getMyUserIdForOrigin relied on instanceStore resolver which could return
the home user ID during connection errors. Cache the authoritative user
ID directly from the WS ready payload, ensuring effective-state
computations use the correct federated identity.
2026-03-09 23:01:19 +01:00
Jannis Braun 907f8285cd fix: decouple voice intent from server enforcement to prevent involuntary unmute
When a moderator lifted a server mute/deafen, the client was involuntarily
turning on the user's microphone because isMuted/isDeafened conflated user
intent with server enforcement. Now intent (isMuted/isDeafened) is never
mutated by server events. Effective state (intent || serverEnforcement) is
computed at broadcast and hardware time via centralized helpers.
2026-03-09 22:25:56 +01:00
Jannis Braun db1909d785 fix: resolve federated identity checks for server mute/deafen pipeline
- The client now dynamically resolves the user's federated identity via `getMyUserIdForOrigin` when evaluating incoming `voice_server_muted` and `voice_server_deafened` events. Previously, the client incorrectly compared the remote event's federated `userId` against the local `authStore` home `userId`, causing federated users to silently drop restriction events.
- Client-side mic/deafen toggles (`toggleMic`, `toggleDeafen`) now accurately evaluate the user's origin-specific ID against the restriction sets, preventing federated users from bypassing locks.
- UI state selectors (`VoiceControlBar`, `ChannelSidebar`) now compute `myOriginId` to correctly render the yellow server-lockdown indicators for cross-instance users.
2026-03-09 19:54:15 +01:00
Jannis Braun 6a12fe2024 fix: rearchitect server mute/deafen pipeline to scope restrictions by spaceId
- Replaces global `userId` tracking with `spaceId:userId` composite keys across both backend and frontend, fixing the issue where server-muting a user in one space bled into others.
- Modifies client-side `ready` event hydration to merge voice states per-origin instead of completely overwriting the store, preventing federated connections from wiping out home instance mutes.
- Excludes server voice restrictions from `zustand/persist` so stale client caches don't override the server's authority on reload.
- Fixes React component reactivity by using reactive store selections for `spaceId` instead of imperative `getState()` calls, ensuring UI lockdown indicators accurately reflect the initial websocket handshake.
2026-03-09 19:44:37 +01:00
Jannis Braun d09d956a9d fix: make server mute/deafen state survive page reload
- Client ready handler now builds restriction Sets atomically in a single
  setState call, eliminating intermediate empty-Set state that caused amber
  icons to flash rose on reload
- buildReadyPayload queries all voice restrictions from DB across the
  user's spaces instead of relying on in-memory state (which is lost on
  disconnect timeout)
- voice_join early-return path now re-broadcasts restrictions to the
  reconnecting user, covering fast reload where the user is still in the room
2026-03-09 18:56:00 +01:00
Jannis Braun c2ddfe0cb7 fix: persist server mute/deafen state across reloads and prevent client bypass
Server-side: add DB persistence for voice restrictions (schema, migration,
ready payload, cleanup on leave). Client-side: fix four bugs that wiped or
bypassed server restriction state — leaveVoice() no longer clears global
restriction Sets, voice_state_update leave no longer drops amber icons,
toggleMic/toggleDeafen now guard against server restrictions, and force-mute/
deafen uses direct setState instead of fragile toggle calls.
2026-03-09 18:36:17 +01:00
Jannis Braun f6cdfe993f fix: server-deafen now shows amber mute icon across all UI surfaces
When a moderator server-deafens a user, the implied mute is server-imposed
and should display amber (not red) everywhere. Updated VoiceControlBar,
ChannelSidebar, VoiceChannel, and VoiceUser to check isServerDeafened
alongside isServerMuted for amber color and cursor-not-allowed state.

Also includes smart mute/deafen toggle logic (Discord-style coupling),
server-side enforcement of mute/deafen bypass, and cleanup of server
voice state on user departure.
2026-03-09 17:12:00 +01:00
Jannis Braun e2c18ad2b0 feat: bans system, voice moderation, and federated space settings fixes
Add ban/unban functionality with BansPanel in space settings, voice
moderation context menu (mute/deafen/disconnect), and fix federated
space settings panels to use origin-aware API client. Show domain
indicators for federated members in MembersPanel.
2026-03-09 15:56:46 +01:00
Jannis Braun 22e7616c70 feat: explore page space banners, icon-derived gradients, and space descriptions
- Redesign explore cards with banner images, overlapping icons, and frosted fade
- Extract dominant colors from space icons for dynamic banner gradients
- Add space description/banner fields to schema with migration
- Move origin label from banner overlay to content metadata row
- Support space descriptions in settings overview panel
2026-03-09 13:49:50 +01:00
Jannis Braun c3bf635fc9 fix: normalize federated asset URLs in explore and social stores
Resolve remote instance avatars and space icons at the data boundary
so they render correctly instead of 404ing against the home instance.
2026-03-08 22:40:02 +01:00
Jannis Braun fc06e25731 refactor: rename Server → Space across entire codebase
Renames all domain terminology from "Server" to "Space" throughout the
application — database schema, API routes, shared types, stores, components,
and UI strings. Files renamed: ServerSidebar → SpaceSidebar, CreateServer →
CreateSpace, JoinServer → JoinSpace, ServerSettings → SpaceSettings,
serverStore → spaceStore, routes/servers → routes/spaces.
2026-03-08 20:08:24 +01:00
Jannis Braun d18d9c51f7 fix: resolve duplicate voice user in federated channel sidebar
Use origin-aware user ID resolution for optimistic voice add/remove
instead of always using the home instance ID. Adds getMyUserIdForOrigin
resolver (same pattern as getApiForOrigin) so joinVoiceChannel and
leaveVoice use the correct federated user ID. Also fixes WS reconnect
voice re-registration, invite URL for remote servers, and chatStore
early-load guard for unmapped channels.
2026-03-07 14:33:37 +01:00
Jannis Braun 39372980f7 fix: re-send voice_join on WS reconnect to restore sidebar after server restart 2026-03-05 00:35:26 +01:00
Jannis Braun dd02c1ed8d fix: prevent ghost users in voice sidebar after deploy
Send voice_join to WS server only after LiveKit successfully connects,
not immediately on joinVoiceChannel() or WS reconnect. Background tabs
can reconnect WebSocket but not WebRTC, causing phantom voice users.
2026-03-04 22:11:50 +01:00
Jannis Braun a5073f89fd fix: optimistic voice state so user appears in sidebar immediately
Previously, the voice channel sidebar only showed users after a server
round-trip (voice_state_update broadcast). After a deploy/reconnect,
this left the user invisible in the sidebar despite being connected.
Now joinVoiceChannel, leaveVoice, and the WS ready handler all
optimistically update voiceUsers for the local user immediately.
2026-03-04 21:48:39 +01:00
Jannis Braun 5e4a5c2ee9 fix: clean up cross-instance voice state when switching federated servers
Send explicit voice_leave to the old instance when joining voice on a
different origin, preventing stale voice state from showing the user in
two channels. Also make WS reconnect voice re-registration origin-aware
so remote reconnects properly restore voice state.
2026-03-04 20:57:15 +01:00
Jannis Braun c65a7387fa fix: remove spurious reconnect sound on deploy/laptop wake, fix federated typing/asset display 2026-03-04 20:40:05 +01:00
Jannis Braun 65e9ee5203 fix: prevent duplicate DMs at creation time, clean up corrupted read states
Replace the unreliable client-side DM dedup loop in populateFromReady with
a creation-time guard (findExistingDmForUser) that checks all instances
before opening a new DM. Guards added to FriendsPage, NewDmModal, and
UserProfilePopout.

Also fixes: corrupted read_states from temp_ optimistic message IDs (server
migration + client-side validation), federation-aware closeDm/addDmMember
API routing, isSelf-based DM member filtering in sidebar/header, and WS
event error isolation.
2026-03-04 20:04:15 +01:00
Jannis Braun 33ae79bae9 fix: federation avatar colors, cross-instance DMs, and instance persistence
- Fix avatar gradient colors in 7 UI sites by using homeUserId fallback
- Merge remote DM channels in populateFromReady (no longer home-only)
- Remove isHome guards from 8 DM WS event handlers for cross-instance DMs
- Route DM typing events to correct instance origin
- Fix autoConnectAll deleting disconnected instances from localStorage
- Distinguish network errors from auth errors in instance reconnection
- Start WebSocket retry for network-unreachable instances (auto-recovery)
- Add reconnectInstance method and Reconnect button in UI
2026-03-04 18:46:16 +01:00
Jannis Braun 6e44a4ef2f fix: DM avatar color and reactions in federation + explore/server discovery
- Fix DM welcome header avatar using home identity for correct gradient color
- Register DM channel IDs in channelOriginMap so federated DM operations
  (reactions, messages, typing) route to the correct instance
- Pass origin when creating DM channels from friends list and WS events
- Add server discovery/explore page with public server listings
- Add server visibility and description fields
2026-03-04 18:02:43 +01:00
Jannis Braun 72d1fab930 feat: federate friends/social system across connected instances
Rewrites socialStore to aggregate friends and requests from all connected
instances using Promise.allSettled. Parses user@domain in friend requests
to route to the correct instance. Removes !isHome guards on social WS
events so remote friend requests arrive in real-time. Shows "via hostname"
labels on remote friends/requests in the UI.
2026-03-04 17:28:28 +01:00
Jannis Braun f7809cf457 fix: federation reaction identity + remote server reload race condition
- Include user object in reaction_added WS broadcasts for isSelf() resolution
- Use isSelf() instead of userId comparison for reaction ownership checks
- Load remote server detail after ready event to prevent empty channel list
2026-03-04 03:53:07 +01:00
Jannis Braun dca9c4dc83 feat: stateless federated identity resolver + federation UX improvements
Add identity.ts with isSelf() and resolveDisplayIdentity() — pure
stateless functions that detect replicated-self using the immutable
(username, homeInstance) composite key. No store lookups, no data
mutation. Fixes wrong avatar gradient and missing edit/delete on
own messages in remote channels.

Also includes: optimistic message dedup fix for cross-instance
messages (content-only matching), federation toast notifications,
Username component with @domain display, invite parser, and
deploy script simplification.
2026-03-04 03:37:49 +01:00
Jannis Braun 64fd8edfc9 feat: federation batch 1+2 — bug fixes and password enforcement
Batch 1 — Bug fixes:
- Fix stale voice state on remote reconnect (clearVoiceUsersForOrigin)
- Fix logout not cleaning remote servers from store
- Fix reply-to asset normalization for remote messages
- Fix HTTPS hardcoded in autoConnectAll (store full origin, legacy fallback)

Batch 2 — Password enforcement + replication flow:
- Add connectToRemote() with home password verification before remote auth
- Auto-cascade: verify home password → register on remote → login fallback
- Replace Register/Login tabs with single password field in ConnectedInstances
- Add DifferentPasswordError for typed fallback-login UI transition
- Fallback login form shown only when remote has different password
2026-03-04 01:51:38 +01:00
Jannis Braun 758c6a7b5b feat: normalize remote asset URLs and route UI actions by instance origin
Phase 5 of multi-instance federation. Adds a two-layer fix:

Layer 1 — Data ingestion normalization: Remote instance user avatars,
server icons, and attachment filenames are rewritten to absolute URLs
when entering the app (via WebSocket events or API responses), so all
downstream components render them correctly without changes.

Layer 2 — Outbound action routing: wsSend calls (voice join/leave/status,
typing) and file uploads in UI components now route through the correct
instance based on the active channel's origin.
2026-03-03 00:28:24 +01:00
Jannis Braun 5194dbef25 feat: multi-WebSocket connection manager for instance federation
Refactor the WebSocket layer from a singleton connection to a connection
map supporting N concurrent instances. Each connection has its own
heartbeat worker, reconnect state, and token. Stores are now
instance-aware: serverStore merges servers by origin, chatStore routes
API calls through the correct client, and instanceStore triggers WS
connect/disconnect on add/remove. DM, social, and voice events remain
home-only.
2026-03-02 23:53:30 +01:00
Jannis Braun e219229b63 feat: add Backspace design prototype and rebrand from Opencord
- Add Backspace-design-prototype.html: finalized "Aether Drift" design
  language with warm matte surfaces and subtle frosted glass accents
- Update CLAUDE.md with DESIGN SYSTEM section and remove Discord clone references
- Rename all Opencord references to Backspace across the full codebase
- Archive outdated design experiments and Discord-specific assets
- Add science-backed accessibility fallback (prefers-reduced-transparency)
2026-03-01 21:26:57 +01:00
Jannis Braun 773a03b1aa feat: instance-level streaming limits with admin settings panel
Add a server-side instance_settings table (single-row, CHECK(id=1))
that stores admin-configurable streaming bounds: bitrate min/max/step,
allowed resolutions, and allowed framerates.

Backend:
- New instance_settings schema + migrations (is_admin on users, default
  settings row, first-registered-user promoted to admin)
- GET/PATCH /api/settings/streaming endpoints with admin-only writes
  and full input validation including cross-field checks

Frontend:
- settingsStore fetches limits on WebSocket ready, exposes isAdmin flag
- ScreenShareSettingsPopover reads bounds from store instead of
  hardcoded constants, auto-clamps stale localStorage values
- buildScreenShareOptions() clamps bitrate to server limits at build
  time as enforcement backstop
- ServerSettings modal gains a "Streaming" tab (admin-only) with
  bitrate range sliders, resolution/framerate toggles, and save/reset
2026-02-26 03:33:29 +01:00
Jannis Braun 76b8a43be2 fix: enforce channel-level RBAC across WS broadcasts, REST endpoints, and frontend reactivity
Wire the bitwise permission engine end-to-end:

- Add sendToChannel() to ConnectionManager, filtering WS recipients by VIEW_CHANNEL
- Convert 6 channel-scoped events (messages, typing, reactions) from sendToServer to sendToChannel
- Add broadcastOverrideChange() to push channel_updated/channel_deleted per-user on override mutations
- Bridge legacy server_members.role TEXT to member_roles junction table on PATCH
- Add pushReadyPayload() to force re-sync frontend store after role changes
- Filter channels by VIEW_CHANNEL in GET /api/servers/:id to prevent initial load data leak
- Pre-compute viewers before CASCADE delete on channel_deleted
- Fix frontend channel event handlers to upsert/cleanup channelToServerMap and channelPermissions
- Add ChannelSettingsModal with Private Channel toggle and gear icon in ChannelSidebar
2026-02-24 06:10:11 +01:00
Jannis Braun 024833c470 fix: security hardening and Safari stability
- Remove hardcoded JWT_SECRET fallback (crash on boot if unset)
- Make LiveKit config optional with 503 guard on token endpoint
- Add REST rate limiting via @fastify/rate-limit (auth 10/15m, messages 5/5s, uploads 10/1m, global 60/1m)
- Add WebSocket token bucket rate limiter (30 burst, 2/sec refill)
- Add DM channel ownership (ownerId) with migration, enforce on add-member
- Require friendship to add users to group DMs
- Add silent 20Hz oscillator to prevent Safari AudioContext suspension
- Move WebSocket heartbeat to Web Worker to bypass Safari background throttling
2026-02-24 04:34:36 +01:00
Jannis Braun 36e27121da fix: harden data integrity, connection stability, and memory management
Wrap all multi-write DB operations in atomic transactions (server/channel
creation, message+attachment linking, DM creation, friend acceptance,
cascading deletes) to prevent partial-write corruption.

Batch N+1 queries in WS ready payload into O(1) bulk fetches with
chunked inArray() to respect SQLite's variable limit.

Fix chat history regression where background WS messages bypassed
channel load by switching the guard from messages.has() to hasMore.has().

Add LRU channel eviction (20 cached, evict to 15) and per-channel
message cap (200) to bound client memory growth.

Shorten WS heartbeat from 30s to 15s for aggressive proxy/NAT
environments. Clear all user-scoped stores on logout to prevent
cross-session data leaks.

Extract LiveKit internal accessors into shared livekitInternals utility.
2026-02-24 03:52:22 +01:00
Jannis Braun 900eb67869 feat: add group DM support (3-10 members)
Unlock group DMs by removing the 2-member assumption across the stack.
No schema migration needed — dm_members junction table already supports
N members. Includes dedup bug fix, add/leave member endpoints, late-join
call support, group-aware sidebar rendering, and AddDmMemberModal.
2026-02-23 22:39:06 +01:00
Jannis Braun 653e59bfb2 refactor: unify backend voice signaling with VoiceRoom abstraction
Replace dual voiceStates + activeCalls maps with a single VoiceRoom
system that tracks both server channels and DM calls uniformly.

Fixes four bugs:
- voice_status silently dropped for DM call participants
- DM calls not cleaned up on WebSocket disconnect
- DM call state missing from ready payload on reconnect
- No spatial tracking of DM call participants
2026-02-23 22:15:41 +01:00