Commit Graph
79 Commits
Author SHA1 Message Date
cnrd 8456b8f976 fix(web): stop the profile card re-anchoring to its own avatar (#39)
Avatar opened the profile popout whenever it received a user prop. Since user is how every avatar gets its gradient, colour and status dot, all 22 call sites became profile triggers by accident — including the picture inside the profile card itself, which re-anchored the card to that picture on every click and walked it across the screen (120px right, 36px down, until it pinned at the viewport clamp).

Avatar is now presentational. A new ProfileAvatar carries the open-the-profile behaviour at the five call sites that actually want it. The card's own picture escalates to the full profile modal instead of reopening the card.

The card also places itself off its measured size via the shared computeFloatingPosition engine, replacing six call sites that each hand-computed coordinates against a guessed 460px card height.

Closes #37
2026-08-25 15:51:04 +02:00
Jannis Braun f481e1fe9e license: relicense to AGPL-3.0-only with commercial dual-license
- LICENSE -> verbatim GNU AGPL-3.0; add LICENSE-COMMERCIAL.md + SECURITY.md
- CLA -> exclusive-license grant (contributors keep copyright); add README
  anti-rugpull covenant + relicense record
- NOTICE / README / CONTRIBUTING / CLAUDE.md / package.json x5 updated;
  contact routed through GitHub (no email placeholders)
- AGPL section 13 source offer: operator-configurable BACKSPACE_SOURCE_URL +
  build-injected commit; sourceCodeUrl+commit on /api/instance/info;
  SourceCodeLink on login/register/settings/desktop; docs + .env.example updated
2026-07-01 16:38:22 +02:00
Jannis Braun 87ecf0f4f3 fix(ui): AvatarStack — center inner Avatar in each tile (off-center clipping)
Each AvatarTile rendered at `size × size` with a 2px border under
`box-sizing: border-box`, giving a content area of `(size − 4) × (size − 4)`.
The inner `<Avatar size={size}>` exceeded the padding box, and the
`overflow-hidden + rounded-full` clip — centered on the wrapper — combined
with the avatar contents anchored at the padding-edge top-left to displace
photos and especially the centered initials gradient + letter toward the
lower-right of the visible disc, leaving a sliver of background opposite.
Compounding this, `Avatar`'s `inline-flex` root sat on the line-box text
baseline, so any inherited `line-height ≠ 1` (the DM list inherits the
row's line-height) drifted the avatar a further several px vertically.

Two corrections:
  • Size the inner Avatar to `size − 2·TILE_BORDER_WIDTH` (matches the
    padding box) — extracted as a constant so the dependency between
    `border-2` and the inner size is visible.
  • Center geometrically via `flex items-center justify-center` on the
    wrapper, bypassing inline-flow placement so the Avatar is anchored
    regardless of inherited type metrics.

Verified in the live app (DM sidebar, chat header, welcome header) and
with a 4-tile diamond at sizes 32 and 80: visible offset is now exactly
the border width on every tile, letters/photos sit dead-center.

Adds a regression test that pins both invariants (flex centering classes
present + inner Avatar style.width === tileSize − 4) so a future change
that re-introduces the bug fails fast. 12/12 AvatarStack tests, 365/365
web tests, typecheck clean.

design-system.md spec updated with the AvatarTile geometry contract.
2026-05-10 23:18:24 +02:00
Jannis Braun 9279ac78e5 fix(ui): AvatarStack — radial overlap layout for 3-10 members (replaces 2x2 grid)
The old 2x2 grid for 3+ members produced a cramped, misaligned look
(circles cut off inside their own border, no overlap, just four small
disks in a grid). The 2-member overlap aesthetic — equal-size tiles
that overlap diagonally — was the visual gold standard but only
existed for that one count.

Extend the same "huddle of overlapping faces" aesthetic to all
member counts:

  • 3 members → equilateral triangle of 62%-size tiles (top, bl, br)
  • 4 members → diamond of 58%-size tiles (top, right, bottom, left)
  • 5-10 members → diamond with `+N` overflow occupying the bottom
    slot (z-index boosted above neighbors so the digit is never clipped)

Tiles are positioned radially around the box center on a circle of
radius (S − T) / 2 so the farthest edges of each tile graze the
bounding rect — no clipping, no wasted whitespace. Z-index descends
clockwise from the top slot so each tile tucks slightly under its
clockwise neighbor, mirroring the 2-member z-stack.

The 0/1/2-member cases and the icon-override branch are unchanged.
Props interface unchanged — no call-site updates needed.

Tests updated to assert the new layout markers (`triangle`, `diamond`)
and to verify the geometry (top tile in triangle has smallest `top`;
overflow tile in diamond has largest `top`). 11/11 AvatarStack tests
pass; 360/360 web tests pass; typecheck clean.

design-system.md spec updated with the new layout table + geometry
section.
2026-05-10 21:57:38 +02:00
Jannis Braun f01c5aba8c feat(ui): reusable AvatarStack with +N badge and single-member group affordance 2026-05-10 17:49:54 +02:00
Jannis Braun 74ccf20308 feat(mobile): Wave 5 — voice-join camera preview + Electron settings entries + toast positioning
Closes the mobile parity push.

- MobileVoiceJoinSheet: full pre-join camera preview. Dormant-by-default (never auto-fires getUserMedia), explicit user gesture to arm, hard-bound disarm on sheet close (any path). Camera picker popup portaled to document.body so a long device list stays scrollable above the aspect-video preview tile (max-height min(50vh,320px), iOS scroll momentum). iOS Safari-safe: autoPlay playsInline muted + post-await play().
- MobileSettingsScreen: Keybinds + Desktop sections gated on isElectron(); they reuse the existing KeybindsPanel/DesktopPanel which are already mobile-fit. screenMap entries added in MobileShell. (Verify on Electron desktop build at narrow viewport.)
- ToastContainer: real overlap was hiding the voice-fullscreen control bar. Mobile branch now reads isMobile/mobileStack/currentVoiceChannelId and computes the bottom offset across five mobile states (voice-full / pushed+voice / pushed / root+voice / root), all with safe-area-inset-bottom; left-3 right-3 + items-center keeps toasts in the safe-tap zone. Desktop bottom-6 right-6 unchanged.

Specs: docs/systems/mobile-ui.md (Toast Positioning section, screenMap rows, Electron-entry rationale, z-index row); docs/systems/voice.md (Mobile pre-join preview subsection covering lifecycle + camera picker portal).
2026-05-07 23:44:06 +02:00
Jannis Braun 49e9047005 feat(client-federation): user-view cache for cross-instance DM render
Fixes a render bug where a federated user (e.g. axel@nova) appeared with
the federation globe icon and a broken avatar when viewed on his own home
instance. Root cause: `populateFromReady` is first-wins by federatedId and
discards the entire skipped DM payload — including its `members` array —
so when a sibling instance's ready arrived first, the home instance's view
of every shared user was dropped on the floor.

Adds a render-only `userViews` cache that mirrors the `dmAlternatives`
philosophy: information from skipped ready payloads is preserved for
rendering. Every wire surface that delivers a User upserts into the cache
regardless of dedup outcome; render sites read through a Zustand selector
hook to surface the home view when one is loaded. The DM channel ingestion
race is left untouched — the existing no-flapping invariant on origin
reconnect is intentional and load-bearing for failover.

Layered changes:

- `identity.ts`: `normalizeOriginToHost`, `canonicalUserKey`,
  `isDeliveryFromHome`, `isFederationGlobeApplicable` — single helpers
  for origin/host normalization and the home/stub tier decision.
- `spaceStore.ts`: `userViews` Map, `UserViewEntry` type, `upsertUserView`
  action with the home-wins preference rule, prune by `deliveredBy` in
  `removeInstanceSpaces` (mirrors `dmAlternatives` cleanup), `reset`
  clears.
- `userViewLookup.ts`: `useCanonicalUserView` (Zustand selector hook for
  React) + `getCanonicalUserView` (sync getter for non-React paths).
  Render reactivity is structural via the selector, not coincidence on
  legacy update paths.
- `populateFromReady` upsert pass runs BEFORE the federatedId dedup so
  members of skipped DMs still reach the cache.
- WS handlers (dm_message_*, message_*, user_updated, member_joined,
  friend_request_*, dm_channel_created, dm_member_added) and REST
  hydrators (socialStore, discoverStore, mutuals) feed the cache with
  their delivering origin.
- Render-site routing through `useCanonicalUserView` at every audited
  user-rendering site (sidebar, header, search, message bubble, reply
  chips, profile popout/modal, group settings, voice tiles, mention
  chips, member lists, friends, invites). Self-rendering sites compose
  alongside via existing `isSelf`/`resolveDisplayIdentity`.
- Globe predicate hoisted to `isFederationGlobeApplicable` and applied
  at three sites, gating on `domain !== window.location.host` so we
  never show the globe for users whose home IS our own.

Tests: 31 new unit tests across `identity`, `userViews` store, and
`userViewLookup`. Full suite 276/276.

Docs: `client-federation.md` §3 gains a "User View Cache" section
parallel to "DM Origin Failover"; `dm-system.md` notes the new store
action and WS handler upserts.

Bug 3 (federation profile-sync gap — orbit's stale profile data on
nova-Axel after a clear/color-change on nova never propagated)
remains open. The user-view cache routes around it for the common case
(home instance is connected), but the underlying S2S relay gap is its
own diagnosis and follows in a separate branch.
2026-05-05 01:59:05 +02:00
Jannis Braun fb96b1457a fix(ui): paint avatar fallback gradient before broken-img onError
The fallback <div>'s inline style only carried `background` when src was
falsy, so an <img> that 404'd flipped to display:flex via onError but with
no gradient applied — surfacing a colorless letter for any broken avatar
URL. Always paint the gradient + fontSize; keep `display:none` as the only
src-conditional override.
2026-05-05 01:58:29 +02:00
Jannis Braun e0861597bc fix(web): portal overlays into fullscreenElement so they render in voice fullscreen
requestFullscreen() on the voice container puts only its descendants in the
browser's top layer; overlays portaled to document.body were rendered outside
that layer and stayed invisible — most visibly the right-click context menu on
stream tiles and voice user panels.

Add usePortalContainer() hook returning document.fullscreenElement ?? document.body
and re-rendering on fullscreenchange. Migrate every overlay reachable during a
call: ContextMenuRenderer (desktop, submenu, mobile sheet), Tooltip,
ConfirmDialog, ConnectionInfoPopover, ScreenShareSettingsPopover, and
ScreenSharePicker (which previously rendered inline at App root).
2026-04-29 23:32:14 +02:00
Jannis Braun 5b51980278 fix(web): make ConfirmDialog Escape capture-phase to prevent leaking to ancestor modals 2026-04-29 01:26:38 +02:00
Jannis Braun 113efcf583 fix(web): drop optimistic updates and disable inert Start-minimized toggle
- AutoLaunchSettings: remove pre-await state writes; backend response is
  now the sole writer to openAtLogin/startMinimized state, eliminating
  the flicker caused when optimistic values diverged from OS truth
- Add busy flag across IPC round-trip; both Toggles receive disabled={busy}
- Start-minimized Toggle additionally receives disabled={!openAtLogin},
  preventing interaction when it would have no effect
- Toggle.tsx: add optional disabled prop (forwarded to <button disabled>)
  with cursor-not-allowed opacity-50 visual feedback; non-breaking for
  all existing callers
2026-04-27 12:57:34 +02:00
Jannis Braun a07f2314ed fix(web): standardize modal button layout across all dialog modals
Apply symmetrical full-width button pattern to:
- Delete Identity dialog (ConnectedInstances)
- Transfer Ownership confirmation
- Image Crop modal
2026-04-03 01:15:14 +02:00
Jannis Braun 5b9b7fc107 fix(web): redesign ConfirmDialog with symmetrical full-width buttons
- Remove tinted description box, use plain text
- Buttons are now flex-1 equal width side by side
- Cancel gets visible bg-interactive-hover background
- Larger padding, rounded-xl, wider max-w
2026-04-03 01:07:31 +02:00
Jannis Braun 7f3ca4e6d1 feat(web): route DM creation to home instance with federated identity
All DM creation and add-member call sites now use the home api client
and pass homeUserId/homeInstance instead of routing to the remote instance.
Also updates addMember in the API client to accept AddDmMemberRequest.
2026-04-01 12:45:13 +02:00
Jannis Braun 1607a8569c Reapply "fix(federation): normalize federated username display across UI"
This reverts commit 5c3185b69f.
2026-03-27 02:35:55 +01:00
Jannis Braun 5c3185b69f Revert "fix(federation): normalize federated username display across UI"
This reverts commit a9d330f013.
2026-03-27 02:31:42 +01:00
Jannis Braun a9d330f013 fix(federation): normalize federated username display across UI
Use parseFederatedUsername() consistently to show base username instead
of raw user@domain format. Replace inline domain text in Username
component with compact globe icon + tooltip. Add globe icons to DM
header and sidebar for federated users.
2026-03-27 02:23:04 +01:00
Jannis Braun 74939f045f feat: add data-avatar attribute for image context menu exclusion 2026-03-25 03:10:37 +01:00
Jannis Braun 38ffc214f0 test(context-menu): add tests for reactive checkbox behavior 2026-03-24 21:53:46 +01:00
Jannis Braun 7a25a2df7b feat(context-menu): add ReactiveCheckboxItem with useSyncExternalStore 2026-03-24 21:47:52 +01:00
Jannis Braun cc30080ba8 chore: sync repo with deployed state — clean up old specs/plans, desktop tweaks 2026-03-24 04:33:23 +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 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 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 977983eed1 feat: add useMascotAnimation hook with WAAPI state machine 2026-03-22 23:26:53 +01:00
Jannis Braun e5495ac0d8 fix: resolve TS2322 ref type error and Tailwind class conflict in Mascot
- Change SvgProps.svgRef to React.Ref<SVGSVGElement> for React 18 compat
- className now replaces default w-32/h-32 instead of appending (avoids
  Tailwind specificity conflicts)
- Replace inline style={{ position: 'relative' }} with Tailwind 'relative'
2026-03-22 23:22:06 +01:00
Jannis Braun 386bd85c8d fix: attach svgRef to SVG elements in Mascot sub-components
The svgRef was created but never passed to the sub-component SVG
elements, meaning it would always be null when the animation hook
tries to use it. Now passed via SvgProps interface.
2026-03-22 23:18:37 +01:00
Jannis Braun 7d5b9c1eee feat: add Mascot component with state-driven SVG rendering
Implement the <Mascot> component that renders Nori in four emotional
states (idle, sleeping, excited, lonely) with distinct SVG geometry,
color palettes, and facial expressions. Includes useMascotAnimation
hook stub for Task 2 and full test coverage (15 tests).
2026-03-22 23:15:46 +01:00
Jannis Braun 9bacc8fb3d feat: add size='settings' variant to Modal component 2026-03-22 02:13:49 +01:00
Jannis Braun c43afc23a8 feat: extract TriStateToggle, OverrideEntry, PermissionsEditor shared components 2026-03-21 18:35:35 +01:00
Jannis Braun 156189dfa2 fix: redesign activity cards with glass-pill material and accent borders
Replace bg-surface-elevated cards and inline text badges with glass-pill
rows featuring colored left borders (mint=playing, sky=listening,
lavender=watching, rose=streaming). Identical treatment in both
ActivityPanel and MemberSidebar — no more compact/full distinction.
Members with rich activities get the frosted glass row; others stay plain.
2026-03-21 03:50:09 +01:00
Jannis Braun a9fcdb72a8 feat(desktop): add manual download fallback to UpdateToast
Extends UpdateToast to handle update-error events from the preload bridge,
showing a "Download" link to the GitHub release URL when auto-update fails.
2026-03-21 02:31:20 +01:00
Jannis Braun 1f4e149ee1 feat: add frontend Rich Presence — activityStore, ActivityCard, sidebar upgrades, settings toggle
- Task 7: Create activityStore (Zustand) with Map<userId, Activity[]>, showActivity toggle,
  debounced pushActivities, and reset on logout
- Task 8: Wire WS integration — presence_update propagates activities to store,
  ready payload initializes userActivities and showActivity (via setState to avoid side effects)
- Task 9: Create ActivityCard component with compact/full modes, type-colored labels,
  elapsed time display, asset images, and fallback to customStatus
- Task 10: Upgrade ActivityPanel to three groups — active friends (full cards, no header),
  online friends (compact), offline friends — using getPrimaryActivity for grouping
- Task 11: Upgrade MemberSidebar to show compact ActivityCards instead of raw customStatus
- Task 12: Add showActivity toggle in AccountPanel settings (Activity Status section)
  with API persistence and store sync
2026-03-21 01:55:17 +01:00
Jannis Braun 443259a527 chore: delete old context menu components replaced by centralized system 2026-03-20 18:48:40 +01:00
Jannis Braun 9e93734719 feat: add centralized ContextMenuRenderer with submenu and mobile support 2026-03-20 18:28:10 +01:00
Jannis Braun ed70099963 fix(mobile): resolve all TypeScript errors from integration
- Fix voiceStore method names (toggleMic, leaveVoice, setCurrentVoiceChannel)
- Fix MemberWithUser nested user property access
- Pass required channelId props to MessageList, TypingIndicator, MessageInput
- Fix ConfirmDialog props (isOpen, description, variant, onClose)
- Add mobileStyle prop to Modal component (fullscreen + sheet modes)
- Fix fromId/isAdmin camelCase property names
- Guard possibly undefined touch events and array accesses
- Fix notification channelId resolution
2026-03-17 15:54:39 +01:00
Jannis Braun a073613b43 feat(mobile): disable tooltips, render context menus as bottom sheets 2026-03-17 15:44:46 +01:00
Jannis Braun 95ec3ff952 feat: Electron screen share picker, instance selector, and system audio loopback
- Custom screen share picker for Electron (ScreenSharePicker.tsx) with
  Aether Drift design: glass-bubble footer, adaptive grid, pill tabs,
  border-based selection (avoids overflow clipping), hover brightness
- Instance URL picker so Electron connects to any Backspace server
- System audio loopback on Windows/Linux via desktopCapturer callback
- macOS: video-only callback (OS blocks system audio capture)
- IPC bridge for screen source enumeration and selection
- Purge stale service worker caches on Electron launch
2026-03-16 03:50:07 +01:00
Jannis Braun 8ae3ffc912 feat: Electron desktop app — hardening, IPC bridge, and dev launch fixes
- Dev/prod URL auto-detection (Vite 5173 in dev, server 3000 in prod)
- Typed IPC bridge via preload (notifications, badge, window controls, updates, deep links)
- Native OS notifications via NotificationController with window focus suppression
- Auto-update via electron-updater with UpdateToast UI
- Deep linking (backspace:// protocol) for macOS and Windows/Linux
- Window state persistence (position, size, maximize across restarts)
- Tray icon with graceful fallback when icon asset missing
- Suppress PWA service worker polling/reloads inside Electron
- Platform detection layer (isElectron, getElectronAPI)
- Root workspace scripts (dev:desktop, build:desktop)
- Document BACKSPACE_URL and BACKSPACE_UPDATE_URL env vars
2026-03-16 00:10:47 +01:00
Jannis Braun 2e6fa3cdc6 feat: optimize profile image sizes, silent PWA updates, storage cleanup fixes
- Resize avatars/icons to 256px and banners to 1280px (client crop + server safety net)
- Add server-side resizeProfileImage() for federation/API uploads without crop modal
- Fix unconstrained crop on RegisterPage and CreateSpace (was missing maxOutputDimension)
- PWA: switch to autoUpdate with skipWaiting/clientsClaim for seamless deploys
- Storage janitor: exclude profile images from unlinked cleanup, delete stale thumbnails
- Add deleteAttachmentByFilename() to clean orphaned attachment records for profile images
- Migration: one-time cleanup of stale profile image attachment records
- GeneralPanel: wrap in <form> to prevent implicit submission
2026-03-15 19:16:48 +01:00
Jannis Braun 4d230711fc feat: launch readiness — PWA, API hardening, memory leak fixes, sticker removal
- Add PWA infrastructure: vite-plugin-pwa, manifest, service worker,
  SW update prompt component, placeholder icons, Apple meta tags
- Harden API client: 401 auto-logout, AbortController timeouts
  (30s standard, 120s uploads), onUnauthorized callback
- Fix memory leaks: clear voice user status on leave, clean up all
  Maps (channelToSpaceMap, permissions, etc.) on removeSpace
- Upgrade error boundary to Aether Drift design with Try Again button,
  collapsible stack trace, and componentDidCatch logging
- Configure desktop icon paths in electron-builder.yml
- Remove sticker feature (server routes, schema, types, UI components)
- Fix Docker build: use **/node_modules in .dockerignore to prevent
  COPY from clobbering pnpm-installed workspace dependencies
- Add vite-env.d.ts declarations for noise suppressor wasm imports
- Exclude test files from tsc build via tsconfig
2026-03-15 15:41:22 +01:00
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 836f0acef6 feat: standardize input styling with tier system, add depth and admin features
- Define 4 input tier CSS classes (input-standard, input-search, input-embedded, input-danger)
  in globals.css, migrating ~50 inputs across ~28 component files to use them
- Add subtle border and inset shadow to solid input tiers for resting-state visibility
- Fix focus ring clipping in settings panel scroll container
- Fix phantom Tailwind tokens (border-border-primary, placeholder-txt-muted)
- Add admin user management panel, storage management, and account deletion utilities
2026-03-14 13:49:32 +01:00
Jannis Braun 3e97c2b0f5 feat: image optimization — client-side resize + server-side thumbnails
Avatars/banners now resize to max 512px/1920px and convert to WebP before
upload (zero server cost). Chat image uploads generate an 800px-wide WebP
thumbnail via Sharp; the feed shows the thumbnail, click opens the full-res
original. Adds lazy loading to avatars. Federation-compatible: remote
instances without this feature fall back gracefully.
2026-03-13 16:44:14 +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 5193d79c39 fix: floating panel ResizeObserver broken by fragment index shift
Context menu insertion before floatingPanel shifted its fragment index,
causing React to unmount/recreate the DOM element on DM↔space transitions.
The ResizeObserver was left observing the old detached element, so
paddingBottom never adjusted and channels scrolled behind the panel.

- Reorder fragment children so floatingPanel is at index 1 in both views
- Replace useRef with callback ref so ResizeObserver reattaches on remount
- Add stopPropagation to ContextMenu to prevent sidebar menu conflicts
- Restyle sidebar create channel/category buttons (smaller, separated)
2026-03-12 02:23:34 +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 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 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