Replace string-based role checks (role === 'admin') with a bitwise BigInt
permission system. Adds computePermissions() resolution engine following
Discord's model: @everyone base → role union → admin shortcut → channel
overrides (role deny/allow → member deny/allow). Ready payload now filters
channels by VIEW_CHANNEL and attaches per-user myPermissions to each
server and channel. Includes channel_overrides table, @everyone role
auto-creation, migration for existing servers, and override CRUD API.
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.
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
Camera and LIVE badges in the channel sidebar were only visible to users
who had joined the same LiveKit room. Widen the voice_status WS event
from {isMuted, isDeafened} to {isMuted, isDeafened, isCameraOn, isScreenSharing}
so all server members see camera/screenshare indicators without joining voice.
30s ping/pong keepalive prevents proxy/NAT from killing idle connections.
Reconnect sound now only plays if downtime exceeds 3s, suppressing phantom
audio from brief network blips.
- Add WebSocket voice_status/voice_status_update events so mute/deafen
icons are visible in the sidebar without joining the voice channel
- Server tracks voiceUserStates and includes them in the ready payload
- Re-register voice channel on WebSocket reconnect to prevent sidebar
users from disappearing after idle timeout
- Re-broadcast deafen state to late joiners via LiveKit data channel
- Fix black grid tile when video stops (enabled-flag guards)
- Remove duplicate mute/deafen from VoiceControls (replaced with
Video Quality + Noise Suppression)
- Fix missing users in sidebar voice list (identity matching + fallback)
- Implemented 'Overdrive' logic to force high bitrates on Chrome
- Fixed 'Auto' preset to default to stable 720p60
- Added persistent 'Triple-Kick' hammer to prevent bitrate throttling
- Fixed sidebar connection status sync
- Added comprehensive diagnostic logger
- Fix stale message cache: add force param to loadMessages, clearAllMessages action
- Fix reload race condition: URL-based isDmChannel fallback before WS ready
- Add read_states DB table for persistent unread tracking
- Add channel_ack WS event (client→server→echo) with BigInt comparison
- Wire up unread state in chatStore (readStates, unreadChannels, ackChannel)
- Auto-ack channels on MessageList view (200ms debounced)
- Unread pill indicators on server icons in ServerSidebar
- Bold text + white dot on unread channels/DMs in ChannelSidebar
- Replace all showDms reads with data-driven isDmChannel() across 8 files
- Design system, UI polish, and component fixes from previous sessions
- Guard Disconnected/ConnectionStateChanged event handlers against stale rooms:
old room events no longer nuke the new room's state (root cause of buttons
failing, mute getting stuck, DUPLICATE_IDENTITY cascades)
- Reset media state (isMuted/isCameraOn/isScreenSharing) on connect to prevent
desync after reconnects
- Add voiceStates to WS ready payload so users see who's in voice on page load
- Wire VoiceControls buttons to check getActiveRoom() before SDK calls
- Guard ChannelSidebar against re-joining the same voice channel
- Switch LIVEKIT_URL to wss://nova.ddns.net/livekit for HTTPS secure context
(required for getUserMedia in Safari)
Phase 1 - Feature Repair:
- Fix member kick/leave: add missing db.delete() call in servers.ts
- Stabilize invite codes: return existing code instead of regenerating
- Fix user search: use LIKE instead of exact match in social.ts
- Wire DM button on FriendsPage to create/navigate to DM channels
- Add cancel outgoing friend request (DELETE endpoint + frontend)
- Add accept/decline friend request actions with WS real-time events
- Fix replyToId persistence in message creation
- Hydrate reactions and replyTo in message queries
- Add joinByCode to API client and serverStore
- Add friend_request_received/accepted WebSocket events
Phase 2 - Discord UI Overhaul:
- Remove stray borders between layout columns
- Replace shadow-sm with shadow-header on content headers
- Replace all bg-gray-*/text-gray-* with Discord color tokens
- Ensure flat color contrast (#1E1F22, #2B2D31, #313338)
Testing:
- Set up vitest + @testing-library/react + jsdom
- Add 17 tests across InviteModal, JoinServer, FriendsPage (all passing)
- Fix vite resolve.extensions to prefer .tsx over stale .js files