Commit Graph
685 Commits
Author SHA1 Message Date
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 825e9975c8 docs: comprehensive CLAUDE.md audit — add federation, discovery, moderation, search, profiles
Bring CLAUDE.md into full alignment with the production codebase after months
of active development. Adds 3 new DB tables, 8 user columns, 18+ API endpoints,
10 WS events, and 30+ new source files to the documentation. Updates deployment
section for Caddy/multi-instance setup and adds 8 new feature entries.

Also applies glass material system to UserProfileModal (backdrop + card).
2026-03-11 23:09:28 +01:00
Jannis Braun 87e3d34638 feat: unified glass material system for all floating surfaces
Add .glass-modal CSS class (0.82 opacity) for dialogs and apply
consistent glass tiers across all floating UI: modals, context menus,
tooltips, popovers, and call cards. Replaces ad-hoc bg-surface-elevated
and inline styles with the documented 5-tier glass hierarchy.
2026-03-11 22:48:11 +01:00
Jannis Braun 7572f165de feat: space avatar color with color picker UI
Add avatarColor field to spaces, matching the user avatar color system.
Spaces get a random color on creation and owners can change it in space
settings. The color controls the fallback gradient when no icon is uploaded,
replacing the old deterministic hash-based gradient. Includes full
federation support, explore page, mutual spaces, and color picker in both
create and settings modals.
2026-03-11 18:34:33 +01:00
Jannis Braun 373d685a55 fix: case-insensitive usernames and federated space ownership detection
Enforce lowercase usernames at registration/login, migrate existing
usernames, and fix ownership checks for federated spaces by resolving
user identity per-instance with getMyUserIdForOrigin.
2026-03-11 18:09:45 +01:00
Jannis Braun 77ed2a70c4 feat: unified space tooltips and ownership transfer UI
- Use custom Tooltip on all space sidebar items instead of browser title
- Add transferOwnership action to spaceStore (federation-aware)
- Add "Transfer Ownership" context menu item for space owners
- Add TransferOwnershipModal with member search and confirmation
- Add transfer ownership option in SpaceSettings > Danger Zone
2026-03-11 17:59:27 +01:00
Jannis Braun b709862741 fix: federated leaveSpace 403 and context menu for owner spaces
Use getMyUserIdForOrigin() in leaveSpace() so federated spaces send
the correct remote user ID instead of the home ID. Show context menu
for all spaces with "Invite People" action; "Leave Space" only for
non-owners.
2026-03-11 17:45:50 +01:00
Jannis Braun 3eabfb9da9 fix: portal-based space context menu, fix pill indicators and federated icon normalization
Replace ContextMenu wrapper with a single portal-based SpaceContextMenu
that renders via createPortal to document.body. Fixes pill indicator
positioning, menu overflow clipping, multiple-menu-open bugs, and
inconsistent DOM structure between owner/non-owner spaces. Also normalize
federated space icons in addSpaceFromReady() for discovery page joins.
2026-03-11 17:34:47 +01:00
Jannis Braun 1889d45a07 feat: orphaned data cleanup, file deletion on message/account removal, and group DM improvements
- Add fileCleanup utility to delete uploaded files from disk on message/account deletion
- Add migration to clean orphaned DM channels, attachments, reactions, read states, and stale moderator refs
- Add FK constraints on dm_message_id in attachments and dm_reactions schema
- Make bans.bannedBy and voiceRestrictions.moderatorId nullable for deleted moderators
- Transfer group DM ownership on member leave or account deletion
- Fully clean up orphaned DM channels (zero members) including files
- Add "Leave Group" context menu for group DMs in ChannelSidebar
- Add leaveDm action to spaceStore
2026-03-11 16:54:34 +01:00
Jannis Braun 8c8767ba2c feat: account deletion, username reuse, and real-time username availability
- Add account deletion with tombstone (isDeleted flag), password/username
  confirmation, owned-space guard, and full cleanup transaction
- Free deleted usernames by renaming to !deleted:<id> so they can be reused
- Add migration to retroactively free usernames from already-tombstoned users
- Add GET /api/auth/check-username endpoint with rate limiting for real-time
  availability checking during registration
- Add debounced username availability indicator on registration Step 1
- Add DeleteAccountModal with federation-aware remote account cleanup
- Add federation ops utility for remote instance management
- Update sanitizeUser to anonymize deleted user profiles
- Add instance store improvements and connected instances modal updates
2026-03-11 16:29:25 +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 10be7a44f8 fix: resolve connectToRemote identity for federated users
Federated users (e.g. youruser@nova browsing orbit) could not add
instances via Settings → Connections because connectToRemote assumed
window.location.host was the home instance, producing a double-@ username
like youruser@nova.ddns.net@orbit.ddns.net that failed server
validation. Now derives trueHomeHost/bareUsername/trueHomeUserId from the
user's actual homeInstance fields and branches the auth flow: login-only
with bare username when targeting home, register with correct namespacing
for third-party remotes. Also skips profile sync to home instance since
it's the source of truth.
2026-03-11 02:27:21 +01:00
Jannis Braun 36d84c5974 fix: pass user/avatarColor to Avatar in profile popout and modal
Profile panels were rendering avatars without the user's chosen
avatarColor because they passed userId but omitted the user prop.
2026-03-11 01:47:01 +01:00
Jannis Braun a5fcb78434 feat: sync user profile to remote instances on federation connect and profile update 2026-03-11 01:33:18 +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 9255683d8c fix: display real avatars and resolved names in PiP overlay
Use useVoiceParticipantMeta hook to resolve avatar and display name
from space/DM member stores, matching VoiceUser and StreamTile behavior.
2026-03-11 00:34:52 +01:00
Jannis Braun 5300e78d9d feat: implement message search and clean up header buttons
Remove placeholder buttons (Threads, Inbox, Help) from channel and DM
headers. Add full-text message search with backend endpoints for both
space channels and DMs, supporting filters (from, has, before, after)
and pagination. Search popover with debounced input, highlighted matches,
and jump-to-message that scrolls with a highlight animation. Includes
messages/around endpoints for loading context when jumping to uncached
messages.
2026-03-11 00:22:00 +01:00
Jannis Braun f82e721491 fix: perfect speaking ring circle and use real avatars/display names in voice tiles
Add flex to speaking ring wrapper divs in VoiceUser and PictureInPicture
to eliminate baseline descender space that made the ring oval. Extract
useVoiceParticipantMeta hook to resolve real display names and avatars
for voice participants instead of showing raw usernames with null avatars.
2026-03-10 23:21:37 +01:00
Jannis Braun a5c7bb6e9a fix: add missing @ prefix for federated usernames in profile cards
Add showAt prop to Username component and use it in both UserProfilePopout
and UserProfileModal to consistently display @username for all users.
Also move avatar ring styling into Avatar component's ring prop.
2026-03-10 22:54:40 +01:00
Jannis Braun 08927fad10 fix: pass raw avatar filename to preview card Avatar instead of pre-resolved URL 2026-03-10 22:33:48 +01:00
Jannis Braun 5953a8985e fix: eliminate banner color bleed by baking alpha into gradient colors
Replace element-level opacity on banner fallback divs with rgba-based
gradient color stops via new mutedGradient() utility. This prevents
compositing layer artifacts that caused visible color seams at the
banner boundary in AccountPanel, UserProfilePopout, and UserProfileModal.
2026-03-10 22:03:51 +01:00
Jannis Braun 40e8293695 fix: banner/avatar overlap in settings preview and refine profile color system
Move bg-surface-channel from info section to card wrapper in AccountPanel
so the avatar's negative margin overlaps into the banner correctly. Replace
accent color presets with 7×3 banner color palette, remove accent color
tinting from display names in popout/modal, and refactor gradient entries
to expose from/to hex stops.
2026-03-10 21:40:57 +01:00
Jannis Braun d3f58f3787 refactor: derive accent color presets from avatar gradient palette
Replace the hardcoded 16-color ACCENT_PRESETS array in AccountPanel with
a shared export from gradients.ts that extracts both hex stops from each
avatar gradient, producing 14 harmonized colors in a 7×2 grid.
2026-03-10 20:32:16 +01:00
Jannis Braun d2697d87fa feat: user-choosable avatar colors with settings picker
Add avatarColor as a stored, user-selectable field (mint, sky, lavender,
coral, rose, teal, amber). Randomly assigned on registration, changeable
in profile settings. Existing users keep hash-based fallback until they
choose a color. Includes DB migration, API validation, gradient map,
live preview in settings, and banner fallback integration.
2026-03-10 20:26:28 +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 274f5a710e fix: use space gradients for mutual space avatars and add federation indicators in profile modal
Space avatars in the mutual spaces tab now use getSpaceGradient() instead of
a flat grey background, matching the sidebar appearance. Mutual friends and
spaces from remote instances show a globe icon with the instance hostname.
Also wires up federated mutuals loading, correct API client routing for
remote user profiles, and the new mutuals utility.
2026-03-10 19:19:05 +01:00
Jannis Braun 003eff2268 feat: user profile customization with banner, accent color, bio, and full profile modal
Add banner image, accent color, and bio fields to user profiles with
full-stack support: schema migration, API validation (hex color format,
190-char bio limit), sanitizeUser propagation, and new GET /users/:id/mutuals
endpoint. Rewrite AccountPanel with live preview card, avatar/banner upload
via ImageCropModal, 16-preset accent color picker, and bio editor. Enhance
UserProfilePopout with banner display, accent-colored names, bio rendering,
and mutual counts. Add new UserProfileModal with About/Mutual Friends/Mutual
Spaces tabs and friend action buttons.
2026-03-10 17:08:29 +01:00
Jannis Braun 88bfc17580 fix: use correct space gradient in settings overview panel
Space icon in Settings → Overview was using the user Avatar component
(wrong palette + hashed on name), now uses getSpaceGradient(id, name)
to match the sidebar.
2026-03-10 16:25:01 +01:00
Jannis Braun 2755cc0aac refactor: remove USE_VOICE_ACTIVITY dead code and enforce STREAM permission in VoiceControlBar
Remove the unused USE_VOICE_ACTIVITY permission bit (was bit 25) and shift
STREAM to bit 25, DISCONNECT_MEMBERS to bit 26. Add a database migration to
remap stored permission values. Gate camera and screen share buttons in
VoiceControlBar behind canSpeak/canStream, matching VoiceControls behavior.
2026-03-10 15:46:59 +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 4d9454382c refactor: unify settings into tabbed modal with UI polish
Split monolithic UserSettings into AccountPanel, VoicePanel, ConnectionsPanel,
and InstancePanel tabs. Remove standalone InstanceSettings modal. Add reusable
Toggle component fixing size deformation, color inconsistency (green→purple),
and flex-shrink issues. Fix custom status clearing by always sending the field
to the server. Wrap Log Out button in glass bubble. Add subtle card depth with
borders.
2026-03-10 03:57:01 +01:00
Jannis Braun 3d37c70e50 feat: grey badges for local-only actions and sidebar unwatched camera state
Use neutral bg-white/20 for unwatched camera badge in grid tile instead
of bg-accent-rose/90, matching the local mute badge convention. Show
crossed-out camera icon in channel sidebar when a remote user's camera
is locally unwatched. Add local mute badge to both grid tile and sidebar.
2026-03-10 03:19:15 +01:00
Jannis Braun f9376460d4 feat: add camera watch/unwatch with click propagation fix and correct badge color
Add ability to unsubscribe from remote camera tracks via context menu,
with unwatched state tracked in voiceStore. Fix click propagation through
React portals by adding onClick stopPropagation alongside onMouseDown on
both the main context menu and MoveToSubmenu flyout portal containers.
Use rose badge color for unwatched cameras (user choice) instead of amber
(reserved for server-enforced states).
2026-03-10 02:45:03 +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 3cef9cef32 refactor: unify voice user context menus into single VoiceUserContextMenu
Both the channel sidebar (VoiceChannel.tsx) and voice panel (VoiceUser.tsx)
now share one context menu with volume slider + conditional mod items,
eliminating duplicated portal/positioning/click-outside code from VoiceUser.
2026-03-10 01:42:53 +01:00
Jannis Braun f3b3c249ca fix: dynamically size channel list padding from floating panel height
Replace static pb-[140px] with ResizeObserver-driven padding so the
last channel is never hidden behind the voice control panel.
2026-03-10 00:49:54 +01:00
Jannis Braun 8927d58288 fix: compute Move To flyout max-height dynamically from viewport space 2026-03-10 00:35:40 +01:00
Jannis Braun ea1f2d8adc feat: collapse "Move to" into hover flyout submenu in voice context menu
Replace the flat channel list with a single "Move to" trigger that opens
a cascading flyout on hover, preventing the menu from bloating vertically
when many voice channels exist.
2026-03-10 00:26:58 +01:00
Jannis Braun 4767c445be feat: dynamic voice grid layout with ResizeObserver
Replace hardcoded Tailwind grid-cols breakpoints with a container-aware
layout algorithm that uses ResizeObserver to recompute optimal tile
arrangement. Maximizes tile area while maintaining 16:9 aspect ratio,
automatically adapting when VoiceChatPanel opens/closes, window resizes,
or fullscreen is toggled.
2026-03-10 00:10:25 +01:00
Jannis Braun 2999ca95a0 fix: prevent stale voice sidebar after multi-session identity collision
When a second browser joins voice, LiveKit disconnects the first browser
via identity collision. The first browser's leaveVoice() was optimistically
removing the user from voiceUsers, but the user is still in voice from
the other session. Add handleForceDisconnect() that clears local connection
state without touching voiceUsers, keeping the sidebar accurate.

Also fix deploy.sh to clean up stale renamed containers from failed recreates.
2026-03-09 23:48:44 +01:00
Jannis Braun cfd24c5d58 fix: show mute slash icon when server-deafened and render badges from effective state
Add isServerDeafened to mic icon slash conditions in VoiceControlBar and
ChannelSidebar. Restructure VoiceUser badge rendering to gate on effective
state (intent OR server enforcement) instead of only LiveKit participant
intent, fixing missing badges in the voice grid for server-muted users.
2026-03-09 23:26:51 +01:00
Jannis Braun 651d7edf9d fix: guard voice controls against toggling intent while server muted/deafened 2026-03-09 23:15:47 +01:00
Jannis Braun 018828575c fix: handle legacy JSON array permissions and improve admin toggle UX
- Add migration to convert legacy JSON array permissions to decimal strings
- Add runtime fallback in stringToPermissions for legacy format
- Show all permission toggles as enabled (dimmed) when Administrator is on
2026-03-09 23:01:27 +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