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).
- Connection Info now shows encoder impl (e.g. libvpx, OpenH264,
ExternalEncoder) on send-side video tracks alongside quality
limitation reason
- Amber warning "VP9 is recommended" when H.264 is selected
Replace inline Save/Discard/Reset buttons with sticky glass-bubble pills
that float at the bottom of scrollable modal content. Make SpaceSettings
tab sidebar sticky with glass material. Convert RoleEditView "Back to
roles" into a sticky glass pill at the top. UserSettings Log Out + Save
always visible in a glass pill with separator. Also includes floating
position hook and popover/tooltip improvements from prior work.
Migrate all 10 voice/video components from Discord visual language to
Aether Drift design system. Zero discord-* classes or hardcoded hex
values remain in components/voice/.
Key changes:
- Status indicators use pastel accents: mint (connected/speaking),
amber (connecting/idle), rose (muted/deafened/error)
- VoiceControlBar converted to frosted glass-bubble material
- ConnectionInfoPopover and ScreenShareSettingsPopover use glass popovers
- Speaking ring glow changed from Discord green to mint (#86efac)
- Slider accents use lavender instead of blurple
- All bg/text/border tokens mapped to surface-*/txt-*/interactive-* system
Extract stats polling into useTrackStats hook with per-sender/receiver
stats via RTCRtpSender.getStats(), delta-based FPS (fixes 25fps overwrite
bug with camera+screenshare), and track-to-source matching via LiveKit
TrackPublication identity. ConnectionInfoPopover becomes a pure display
component with Network/Audio/Video sections. Remove soft-launch diagnostic
loop from useLiveKit.
Eliminate double-state architecture (useState → useEffect bridge → store)
that lost speaking events due to React 18 batching. ActiveSpeakersChanged
now writes speakingParticipantIds directly to voiceStore; 200ms poll
catches missed SDK events. Each VoiceUser subscribes to its own identity
via fine-grained selector for minimal re-renders. Also adds connection
quality indicator and ConnectionInfoPopover.