handleVoiceDropZoneDrop called stopPropagation and clearState
unconditionally, preventing container's handleContainerDrop from
firing when dropping a channel onto a voice channel. Add early
return for non-voiceUser drags so channel/category drops bubble
through to the container handler correctly.
Port the SpaceSidebar normalization pattern: convert 'before B'
to 'after A' so only one drop indicator renders at any gap.
Fixes double-line visual glitch and unreliable drops when cursor
is between two channels. Also adjust indicator offsets from
top-0/bottom-0 to -top-[1px]/-bottom-[1px] to center in the gap.
Remove activeDrag from drag-start dependency arrays to prevent
unnecessary re-renders during drag. Wrap containerHandlers in
useMemo for consistent reference stability.
When a user joins a voice channel while already connected from another
tab, the server now sends a 'displaced' reason on voice_disconnected.
The client tears down LiveKit and shows an informational toast.
- Extract VolumeSliderItem to voiceMenuItems.tsx (was duplicated in
VoiceUser.tsx and VoiceChannel.tsx)
- Add e.stopPropagation() to all imperative context menu handlers
for consistency with useContextMenu hook behavior
Add auto-launch settings (start at boot + start minimized) to the
Desktop section in Account settings, using Electron's built-in
setLoginItemSettings API across macOS, Windows, and Linux.
Replace the fallback colored-circle tray icon with proper B logo assets:
template images for macOS (adapts to light/dark menu bar) and colored
icons for Windows/Linux. Fix BGRA channel order bug in fallback generator
and move tray icons to resources/ so they're packaged into the app.
- Save the top-visible message ID on channel leave, restore via
scrollIntoView on return (immune to lazy-loaded image reflow)
- Add floating glass-bubble "Jump to Present" button when scrolled
5000px+ from bottom
- Clear stale scroll anchors when user returns to bottom
- Evict scroll positions alongside channel cache eviction
ResizeObserver alone misses scroll adjustments when multiple images load
in quick succession due to browser layout-loop suppression. A capture-phase
load event listener on the content wrapper reliably catches all descendant
image loads and re-scrolls to bottom.
profileSync stored avatar/banner paths with /api/uploads/ prefix on remote
instances, causing resolveAssetUrl to produce double-path URLs like
https://remote/api/uploads//api/uploads/file.jpg that 404'd. Store bare
filenames instead, strip prefix defensively in resolveAssetUrl and server-side
for existing data self-healing.
Voice channels rendered with VoiceChannel component have no text reading/acking
UI, so messages in them created phantom unread indicators on the space sidebar
that users could never clear. Root cause was a message in the counter-strike
voice channel with no read state.
Three-layer fix:
- spaceStore: track voiceChannelIds set, exclude voice channels from
channelLastMessageIds so setReadStates never marks them unread
- useWebSocket: skip markChannelUnread for voice channels on message_created,
prune orphaned unreads on every ready event
- chatStore: validate preserved unreads against channelToSpaceMap to drop
orphans that don't map to any known channel