- Document full 1-on-1 DM implementation with REST endpoints, WebSocket events
- Add unified sidebar architecture replacing 4-column Discord layout
- Document theme system (neon-glow default, custom theme support)
- Add quick-switch server overlay feature (door button)
- Update PROTOCOL.md with dm_create, dm_channel_open/close events
- Add ready payload dm_channels field for initial DM state
- Expand CLAUDE.md key features section with all new implementations
Changes reflect completed session work on sidebar redesign and DM system.
Three DM display fixes:
- Chat header now shows @ prefix and username for DM channels instead of #
- Empty message state shows DM-appropriate welcome text instead of channel welcome
- New DMs are added to dmStore on creation so they appear in the sidebar immediately
Implements the complete client-side DM system:
- DM store with channel list, unread tracking, and CRUD actions
- REST API methods for GET/POST/DELETE /api/v1/dms
- WebSocket dispatcher handlers for ready dm_channels, dm_channel_open,
dm_channel_close, and chat_message DM updates
- Sidebar DM section wired to real dmStore data with unread badges
- DM mode sidebar renders actual conversations from store
- Member picker modal for creating new DMs via API
- DM channel selection adds to channelsStore for ChannelController loading
- Extended ChannelType to include "dm" variant
Task A: REST API endpoints in api/dm_handler.go — POST /api/v1/dms
(create/get DM channel), GET /api/v1/dms (list open DMs), DELETE
/api/v1/dms/{channelId} (close DM). Routes registered in router.go.
Task B: WebSocket DM routing — handleChatSend, handleChatEdit,
handleChatDelete, and handleReaction all check channel type and use
participant-based auth for DM channels instead of role permissions.
DM messages delivered via SendToUser to each participant (bypassing
channel-subscription model). Auto-reopens DM for recipient on new
message with dm_channel_open event. New broadcastToDMParticipants
helper. New WS event types: dm_channel_open, dm_channel_close.
Task C: Ready payload includes dm_channels from GetUserDMChannels.
Also adds GetDMParticipantIDs helper to db/dm_queries.go.
Add migration 008 with dm_participants and dm_open_state tables.
Add dm_queries.go with GetOrCreateDMChannel, GetUserDMChannels,
OpenDM, CloseDM, IsDMParticipant, and GetDMRecipient functions.
- Add "MEMBERS" category header and drag-to-resize handle on the member
list section with height persisted to localStorage (min 80px, max 40vh)
- Add collapsible "DIRECT MESSAGES" section between channels and members
showing up to 5 online users as DM targets
- Remove members toggle button from ChatHeader (members are always in sidebar)
- Clean up onToggleMembers from ChatHeaderOptions and ChatArea
- Update unit and E2E tests to reflect the removed toggle
Add loadCollapsedCategories/saveCollapsedCategories persistence layer
keyed by server name. toggleCategory now auto-saves to localStorage.
On sidebar mount, collapsed state is loaded for the current server so
each server remembers its own collapsed/expanded sections across sessions.
Build DM conversation list from online members instead of empty array.
Each online member (excluding current user) appears as a DM target with
status indicator and "No messages yet" placeholder. Selection sets the
active DM user and keeps sidebar in DM mode. Sidebar re-renders on
member presence changes and active DM user switches.
Also loads per-server collapsed category state on sidebar mount.
The onSwitch callback now stores the target host in sessionStorage and
calls clearAuth(), which triggers the existing logout flow (voice leave,
WS disconnect, navigate to ConnectPage). On the ConnectPage side, a new
selectServer() method auto-fills the host and loads saved credentials.
The onAddServer callback similarly disconnects and lands on ConnectPage
so the user can add a new server profile.
Mount the MemberList component below the channel list in channel mode,
with admin callbacks (kick, ban, role change) and visibility toggled
via the existing ChatHeader member list button / uiStore subscription.
Tracked as channelModeExtras for proper cleanup on mode switch.
- Fix test regression: update theme option count 3→4 and midnight index [1]→[2] for neon-glow insertion
- Fix stale Voice & Audio tests: update select count 3→4 (video quality + device added), replace sensitivity slider test with meter bar render check
- Consolidate theme systems: applyTheme() in helpers.ts now delegates body class + persistence to applyThemeByName() so both paths write to owncord:theme:active
- Add input validation to loadCustomTheme(): reject non-object payloads and entries missing name/colors fields
- Remove dead CSS tokens --strip-width and --members-width from tokens.css
- Extend 800px responsive breakpoint to cover .unified-sidebar alongside .channel-sidebar
- Import restoreTheme from @lib/themes in main.ts
- Call restoreTheme() after applyStoredAppearance() so the body
class (theme-<name>) is applied before first render
- Add "neon-glow" entry to THEMES in settings/helpers.ts
- Update applyTheme() to also toggle theme-<name> class on document.body
- Update UiState and setTheme() in ui.store.ts to include "neon-glow"
- Delete ServerStrip.ts and its unit test
- Remove all .server-strip / .server-icon / .server-separator CSS rules
- Remove @media (max-width: 600px) responsive rule for .server-strip
Remove serverStripSlot and memberListSlot from the app layout in
MainPage.ts. Remove standalone MemberList creation, mounting, and
visibility subscription from ChatArea.ts. The member list is now
part of the unified sidebar (Task 8).
- Remove ServerStrip import and server strip slot from sidebar layout
- Add unified sidebar header with OC icon, server name, and online count
- Subscribe to uiStore.sidebarMode to swap between ChannelSidebar and
DmSidebar content views within the same DOM slot
- Wire UserBar onDisconnect to open QuickSwitchOverlay with saved profiles
- Add openQuickSwitch to SidebarAreaResult for external disconnect flow
- Add unified sidebar CSS classes to app.css
- Voice widget and user bar remain visible in both sidebar modes
Adds dmMode option (hides member-list toggle), exposes hashEl in refs,
and adds updateChatHeaderForDm helper that swaps # for @, sets username
and status when a DM recipient is active.
Adds optional onBack/serverName fields to DmSidebarOptions. When onBack
is provided, a clickable back header is rendered above the search bar
with arrow, title and subtitle. Supporting CSS added to app.css.
- Change UserBarOptions from Record<string,never> to interface with optional onDisconnect callback
- Render a "Switch server" button in the controls area when onDisconnect is provided
- Add log-out icon (Lucide path data) to icons.ts for the button
Implements lib/themes.ts with listThemeNames, applyThemeByName,
getActiveThemeName, saveCustomTheme, loadCustomTheme, deleteCustomTheme,
exportTheme, and restoreTheme. Built-in themes applied via body CSS
classes; custom themes applied via inline CSS variable overrides.
Active theme persisted to localStorage. All 7 unit tests pass (TDD).
Extends UiState with sidebarMode ("channels" | "dms") and
activeDmUserId (number | null). Adds setSidebarMode (which auto-clears
activeDmUserId when switching back to channels) and setActiveDmUser
actions. All three new tests pass (TDD: RED → GREEN).
Split layout redesign with deep indigo gradient left panel,
OC neon glow logo (orange→pink→purple→cyan), accent stripe
server cards, and full motion animations.
- Left panel: gradient background with ambient glow spots
- OC logo: SVG lettermark with Gaussian blur glow pulse
- Server cards: compact rows with purple accent stripe
- Inputs: 12px border-radius with focus glow ring
- Button: gradient with box-shadow and shimmer on hover
- Animations: panel slide-in, logo pulse, card stagger,
button shimmer, all respecting prefers-reduced-motion
The Rust save_credential command was accepting but ignoring the
password parameter (_password). Now stores it in the Windows
Credential Manager JSON blob alongside username and token.
DPAPI encrypts the blob at rest, tied to the Windows user —
plaintext is never written to disk.
Also wire the checkbox state to the profile's rememberPassword
field so it persists across sessions. Previously it was
hardcoded to false on every login.
- Collect stats from both publisher AND subscriber PeerConnections
(RTT is typically on the subscriber PC in LiveKit's SFU model)
- Accept candidate-pair with valid RTT regardless of state
(not just "succeeded" — subscriber may report "in-progress")
- Round raw byte values in formatBytes to avoid 15-digit floats
- Use max instead of sum for totalUp/totalDown across candidate
pairs to avoid double-counting across PCs
Add a signal-bars icon + ping text to the voice widget header
that shows real-time connection quality. Clicking it expands a
transport statistics pane with outgoing/incoming rates, packet
counts, RTT, and session totals.
- New lib/connectionStats.ts: polls WebRTC RTCPeerConnection
stats every 2s, computes quality level from RTT thresholds
- Signal icon with 4-tier coloring: green (<100ms), yellow
(100-200ms), red (>200ms), with 1-4 bars lit
- Expandable stats pane between header and controls
- Auto-starts/stops poller on voice connect/disconnect
- New createSignalIcon() in icons.ts for per-bar coloring
- getRoomForStats() export from livekitSession.ts
- Add stream quality selector (Low/Medium/High/Source) in Voice & Audio
settings with per-preset bitrate and resolution for camera + screenshare
- Use createLocalVideoTrack/createLocalScreenTracks + publishTrack for
explicit encoding control (bypasses LiveKit conservative defaults)
- Source quality: 8Mbps camera, 10Mbps screenshare, no adaptive/dynacast
- Nuclear mute: fully unpublish mic track when muting, re-publish on
unmute — guarantees SFU has no audio to forward
- Listen for LocalTrackPublished to re-enforce mute on renegotiation
- Store manually published tracks for explicit unpublish on disable
- VAD updatePipelineGain respects mute/deafen state
- Widen channel sidebar from 240px to 260px, add overflow handling
so voice user icons don't clip and cause horizontal scrollbar
- Remove invalid LiveKit server-side room config fields
- Deafen now also mutes the local microphone (privacy fix)
- Mute/deafen zero the audio pipeline GainNode to guarantee silence
when the replaced sender track bypasses LiveKit's track disable
- Send voice_leave on failed auto-reconnect to prevent ghost states
- Gate voice_join on LiveKit availability (reject if h.livekit == nil)
- IP-restrict /api/v1/livekit/webhook to admin CIDRs
- Warn at startup when LiveKit API keys are auto-generated (ephemeral)
- Fix connecting guard: move pending-join dispatch outside finally block
- Add volume slider + mute button overlay on remote video tiles
- Document token refresh limitation for 4h+ sessions
- Fix TS2306 in rnnoise-worklet test (ts-expect-error for worklet import)