Commit Graph
274 Commits
Author SHA1 Message Date
jevb 36062d1495 fix: restore previous channel when leaving DM mode via back button 2026-03-27 14:35:17 +01:00
jevb 1a0024ade6 fix: filter DM channels from channel sidebar list 2026-03-27 14:31:00 +01:00
jevb f0ac9a58bb fix: DM header shows @ instead of #, DM welcome message, DM appears in sidebar list
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
2026-03-27 14:25:54 +01:00
jevb 69c8b7be5e fix: add visible class to DM member picker modal overlay 2026-03-27 14:13:52 +01:00
jevb a250d62259 feat(client): add DM store, API methods, dispatcher events, and sidebar wiring
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
2026-03-27 14:08:25 +01:00
jevb 99dd25ec9a feat(server): add DM REST endpoints, WebSocket routing, and ready payload
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.
2026-03-27 13:58:48 +01:00
jevb e938a80ac8 feat(server): add DM schema migration and database query layer
Add migration 008 with dm_participants and dm_open_state tables.
Add dm_queries.go with GetOrCreateDMChannel, GetUserDMChannels,
OpenDM, CloseDM, IsDMParticipant, and GetDMRecipient functions.
2026-03-27 13:46:06 +01:00
jevb 9b4119c327 docs: add DM system design spec 2026-03-27 13:41:58 +01:00
jevb bc474a0119 fix: make neon-glow default theme, accent color picker now overrides theme accent 2026-03-27 13:19:07 +01:00
jevb 2a2e6daed6 chore: stage QuickSwitchOverlay files missed from earlier commit 2026-03-27 13:10:21 +01:00
jevb 9078bce145 fix: compact member items in sidebar, increase max resize height to 65% 2026-03-27 13:04:35 +01:00
jevb af2d1c9516 feat: add member list header/resize, DM section in sidebar, remove member toggle from chat header
- 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
2026-03-27 12:56:40 +01:00
jevb f2d35c8eb9 feat: persist collapsible section state per-server in localStorage
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.
2026-03-27 12:39:46 +01:00
jevb 3686581bf5 feat: wire DM conversations to member data in sidebar
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.
2026-03-27 12:39:34 +01:00
jevb fdb5e0ed85 feat: wire quick-switch overlay to disconnect and navigate to ConnectPage
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.
2026-03-27 12:34:28 +01:00
jevb f4df18d5be feat: relocate MemberList into unified sidebar as collapsible section
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.
2026-03-27 12:29:44 +01:00
jevb 0aeaa50856 fix: address code review — test regression, theme consolidation, validation, dead CSS
- 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
2026-03-27 12:22:22 +01:00
jevb 6fd4779624 feat: restore saved theme on app startup
- Import restoreTheme from @lib/themes in main.ts
- Call restoreTheme() after applyStoredAppearance() so the body
  class (theme-<name>) is applied before first render
2026-03-27 12:09:56 +01:00
jevb 9a9464a8de feat: add neon-glow to theme selector and apply body class on theme switch
- 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"
2026-03-27 12:09:47 +01:00
jevb 4d63368a51 refactor: remove ServerStrip component and CSS (replaced by unified sidebar)
- 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
2026-03-27 12:09:38 +01:00
jevb ba014242e7 feat: update MainPage layout — remove server strip and standalone member list
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).
2026-03-27 12:04:48 +01:00
jevb 99e999e698 feat: rewrite SidebarArea as unified sidebar (removes ServerStrip)
- 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
2026-03-27 12:01:25 +01:00
jevb cf77deed5d feat: add DM mode support to ChatHeader with recipient info
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.
2026-03-27 11:46:49 +01:00
jevb a90018f2c7 feat: add back-to-server header to DmSidebar
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.
2026-03-27 11:46:40 +01:00
jevb a44b89f1fb feat: add disconnect/switch-server button to UserBar
- 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
2026-03-27 11:39:48 +01:00
jevb 11d5de09e0 feat: add theme manager with built-in + custom theme support
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).
2026-03-27 11:37:57 +01:00
jevb b13487dc4f fix: add sidebarMode/activeDmUserId to test UiState mocks 2026-03-27 11:34:45 +01:00
jevb 8ff4077a8b feat: add OC Neon Glow theme CSS with theme contract variables
- Add theme contract variables (--accent-primary, --accent-secondary,
  --accent-gradient, --border-glow) to tokens.css :root block
- Create theme-neon-glow.css with body.theme-neon-glow overrides:
  deeper blacks, OC cyan (#00c8ff) to purple (#7b2fff) brand palette,
  subtle border glow, and semantic color aliases
- Import theme-neon-glow.css in main.ts alongside other style imports
2026-03-27 11:32:36 +01:00
jevb 1eaf9ad72c feat: add sidebarMode and activeDmUserId to UI store
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).
2026-03-27 11:29:23 +01:00
jevb efb3f1364a chore: add .superpowers/ to gitignore 2026-03-27 10:47:06 +01:00
jevb a6bb4e77ec fix: regenerate ICO with proper multi-size PNG entries (7 sizes) 2026-03-27 10:25:40 +01:00
jevb 73ff065026 fix: move status dot out of icon div to fix letter centering 2026-03-27 10:13:31 +01:00
jevb 192d779775 feat: replace app icon with OC neon glow logo 2026-03-27 10:09:53 +01:00
jevb 823f9dedea fix: replace form logo with OC neon glow SVG to match branding 2026-03-27 10:05:18 +01:00
jevb 54ea355501 fix: server icon shows single initial for compact 28px size 2026-03-27 10:04:08 +01:00
jevb 6bc6b66ce4 feat: login screen redesign with OC branding and animations
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
2026-03-27 10:00:03 +01:00
jevb d9c6da2bd1 fix: remember password now actually stores the password
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.
2026-03-27 08:48:54 +01:00
jevb bb2194849a fix: connection stats RTT detection and rate formatting
- 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
2026-03-27 08:41:34 +01:00
jevb f1517566ab feat: voice connection quality indicator with transport stats
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
2026-03-27 08:35:37 +01:00
jevb 0983c34d59 fix: client code review — 27 fixes across 17 files
Dual-model review (Claude + Codex) found 1 CRITICAL, 9 HIGH,
10 MEDIUM, 7 LOW issues. All addressed:

Security:
- SSRF protection: block link previews to private/internal IPs
- Context-menu dismiss listener leak on item click

Type safety:
- Replace `as any` casts with LocalVideoTrack/LocalTrack types
- loadPref runtime typeof validation against corrupted localStorage
- handleLocalTrackPublished typed with LocalTrackPublication

Memory leaks / listener cleanup:
- MessageInput.destroy() closes open pickers before aborting
- ChannelSidebar/VoiceChannel dismiss setTimeout race guard
- Context-menu item click aborts dismiss AbortController

Async correctness:
- attemptAutoReconnect takes AbortSignal; leaveVoice() aborts it
- OG dedup returns shared in-flight Promise (not empty placeholder)
- Clipboard copy handles rejection with user feedback

State management:
- setMembers() clears typing timers on reconnect
- prependMessages() sets hasMore=true when trimming to cap
- setVoiceStates() preserves currentChannelId when user absent

Code quality:
- VideoGrid: createIcon() replaces innerHTML SVG strings
- VideoGrid: single setUserVolume on unmute (no volume spike)
- api.ts: shared doFetch() eliminates request/adminRequest duplication
- livekitSession: __owncord.lkDebug namespace replaces __lkDebug
- console.error replaced with structured logger
- YouTube oembed uses tauriFetch for consistency
2026-03-27 08:05:32 +01:00
jevb 8cf90c916d t 2026-03-26 22:13:47 +01:00
jevb b9343caf8e feat: add RNNoise worklet TypeScript source 2026-03-26 22:10:49 +01:00
jevb 0edab9b711 chore: remaining unstaged changes — rnnoise worklet, tests, renderers
- RNNoise worklet: extract constants, output ring buffer refactor
- ChannelSidebar: video mode controller integration
- Renderers: message list rendering updates
- MainPage/SidebarArea/VideoModeController: video mode wiring
- Tests: expanded coverage for channel-sidebar, renderers, video-grid,
  video-mode-controller
- TODOS.md: updated task tracking
2026-03-26 22:09:56 +01:00
jevb c628a62565 feat: stream quality presets, nuclear mute, sidebar width fix
- 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
2026-03-26 22:07:52 +01:00
jevb 15b779f86b fix: voice mute pipeline, security hardening, and video tile controls
- 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)
2026-03-26 20:53:09 +01:00
jevb c13d5a67a5 docs: add specs for screenshare audio and video focus mode
Two specs for the video/screenshare experience:
1. SCREENSHARE-AUDIO.md — system audio capture + per-tile mute
2. VIDEO-FOCUS-MODE.md — Discord-style opt-in viewing with focus layout
2026-03-26 18:39:50 +01:00
jevb 6ca18762cb feat: full screenshare support — button state, video grid, auto-reconnect
- Add enableScreenshare/disableScreenshare to LiveKitSession with
  proper LiveKit track publishing, error handling, and WS notification
- VoiceWidget screenshare button now shows active state (red highlight,
  icon swap, aria-pressed) matching mute/deafen/camera pattern
- VideoModeController activates video grid for screenshare (not just
  camera), with local self-view tile using ID offset to avoid collision
- MainPage voice store subscription now watches screenshare state
  changes to trigger checkVideoMode automatically
- Reset localScreenshare on leaveVoice to prevent stale button state
- Add auto-reconnect on unexpected LiveKit disconnect (2 attempts with
  3s delay, fresh token request on success)
- Fix pre-existing missing reapplyAudioProcessing mock in settings test
- 8 new tests covering screenshare button, video grid activation,
  tile lifecycle, and state cleanup
2026-03-26 18:07:20 +01:00
jevbandclaude-flow 43a11c7b90 fix: invert sensitivity slider direction to match Discord UX
Drag LEFT = easier for mic to pass (high sensitivity),
drag RIGHT = harder (low sensitivity). Inverts both the
threshold indicator position and pointer-to-value mapping.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-25 19:15:50 +01:00
jevbandclaude-flow a0731cf030 fix: voice audio pipeline — autoplay unlock and GainNode-based VAD
- Add --autoplay-policy=no-user-gesture-required to WebView2 config
  so remote participants' audio plays immediately on join (desktop app
  doesn't need browser autoplay restrictions)
- Add AudioPlaybackStatusChanged handler with click-to-unlock fallback
  for browsers that still block autoplay
- Replace broken VAD implementation that used setMicrophoneEnabled/
  mediaStreamTrack.enabled (both fought LiveKit's track lifecycle) with
  a unified GainNode audio pipeline:
    rawMic → AnalyserNode (VAD) → GainNode (volume × gate) → sender
- VAD now gates by setting gain=0 instead of touching the track —
  analyser always sees real audio, no stale track references
- Merge input volume and VAD into single pipeline (always active)
- Add voice settings UI: draggable sensitivity threshold on mic meter,
  input/output volume sliders (0-200%), audio processing toggles

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-25 19:07:52 +01:00
jevb 2be2c5c23b chore: gitignore .claude-flow/ and .mcp.json 2026-03-25 17:29:34 +01:00