Commit Graph
240 Commits
Author SHA1 Message Date
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
jevb 0a194c042c fix: remove invalid active_loopback_prevention from LiveKit config
Field doesn't exist in current LiveKit SFU AudioConfig struct,
causing YAML unmarshal error on startup.
2026-03-25 17:28:14 +01:00
jevb de666d515a docs: sync documentation with codebase — version alignment, config fixes, test scripts
- Fix version misalignment: CLAUDE.md had 1.3.0, README had 1.0.0, actual is 1.2.0
- Fix README config table: upload.max_size_mb 10→100, tls.mode selfsigned→self_signed
- Add missing test scripts to CLAUDE.md (e2e:native, e2e:prod, e2e:ui, watch)
- Add documentation audit report
2026-03-24 21:42:16 +01:00
jevb 7404347a1d fix: address code review — 8 issues across server and client
Server fixes:
- voice_leave: broadcast voice_leave even on DB error so peers don't see ghost users (H1)
- migrate: record migration inside transaction for atomicity (H2)
- password: use init() with panic for dummyHash to catch bcrypt init failures (M1)
- password: replace //nolint:errcheck with explicit _ = discard (M2)
- handlers: clarify edit permission comment, fix error message wording (M3)
- auth_handler: distinguish duplicate username (400) from DB error (500) (M4)

Client fixes:
- media: revert YouTube oEmbed to browser fetch — no need to disable cert verification (C1)
- messages.store: fix prependMessages cap to keep newest messages, not oldest (H5)
- ChannelSidebar: ref-count globalDragAc to prevent multi-instance teardown race (H6)
- attachments: replace console.error with project logger (M6)
- ws: clarify lastSeq reset comment to match actual behavior (M5)
2026-03-24 21:35:40 +01:00
jevb e0437d4d8d feat: LiveKit migration — permissions, auth hardening, voice improvements
Pre-review snapshot of LiveKit migration changes including:
- Permission computation fix (allow-wins semantics)
- Timing-safe password comparison with dummy hash
- Rate limiter window fix
- Dev credential clearing for LiveKit
- Voice leave/join broadcast improvements
- Migration transaction wrapping
- Chat edit/delete permission guards
- TOTP verification endpoint
- Embed regex injection fix
2026-03-24 21:30:23 +01:00
jevb 3f58345e6c fix: address code review — security hardening, leak fixes, credential safety
CRITICAL:
- Add AuthMiddleware + rate limiting to /livekit/* proxy route (was unauthenticated)
- Remove well-known default LiveKit credentials from source; auto-generate unique
  random keys on first run so voice works out of the box securely
- Reject the old "devkey"/"owncord-dev-secret-key-min-32chars" in NewLiveKitClient

HIGH:
- Add 5s timeouts to RemoveParticipant/ListParticipants SDK calls (goroutine leak)
- Fix config.yaml default file permissions from 0644 to 0600
- Fix voice store desync on unexpected LiveKit disconnect (phantom UI state)
- Add in-flight guard to handleVoiceToken (race on rapid channel switch)
- Fix lightbox listener leak on rapid reopen (orphaned mousemove/mouseup/keydown)
- Fix allTracked WeakRef set unbounded growth in media-visibility
- Replace debug console.log with createLogger in embeds.ts
- Stop persisting password in Windows credential blob (only token needed)
2026-03-24 20:23:40 +01:00
jevb e03456527b fix: resolve LiveKit voice issues — duplicate audio, tunnel effect with 5+ users
- Detach existing audio elements before attaching to prevent double playback on reconnects
- Remove webAudioMix to eliminate Web Audio overhead compounding with multiple participants
- Use participant.setVolume() for full 0-200% per-user volume range
- Add pli_throttle and active_loopback_prevention to LiveKit server config
- Bump version to 1.3.0
2026-03-22 21:10:02 +01:00
jevb eacee4f780 fix: resolve TS2556 spread argument error in livekit-session test 2026-03-22 20:14:27 +01:00
jevb a87824f2a5 fix: resolve CI failures — lint errors and coverage threshold
Server: suppress errcheck on deferred Close() calls, discard
resp.Body.Close error, remove unused voiceSpeakersPayload type
and buildVoiceSpeakers func.

Client: add unit tests for os-motion, livekitSession, and
safe-render to bring coverage from 74.16% to 76.06% (threshold 75%).
2026-03-22 20:06:59 +01:00
jevb eb08c792aa docs: add voice metrics and DESIGN.md TODOs from review
- Add voice session metrics TODO (P3)
- Add DESIGN.md creation TODO (post-merge)
2026-03-22 19:30:11 +01:00
jevb 0ee1339582 test: fix 4 pre-existing test failures for updated settings UI
- Update settings-overlay test for 9 tabs (was 6)
- Fix keybinds-tab test for new section structure
- Fix logs-tab test for voice diagnostics header
- Fix notifications test mock hoisting
2026-03-22 19:30:02 +01:00
jevb 2ccb207985 fix: address design review findings — empty states, ARIA, token refresh
- Add empty state for ChannelSidebar ("No channels yet")
- Add empty state for MemberList ("No members online")
- Add ARIA role/aria-selected to settings tab buttons
- Add aria-pressed to voice widget mute/deafen/camera buttons
- Add aria-label and title to member status dots
- Cache refreshed LiveKit token for reconnection
2026-03-22 19:29:53 +01:00
jevb 6a5d3d53cb fix: address CEO review findings — health check, error logging, camera guard, metrics, tests
- Add LiveKit health check guard to voice_join (reject if process not running)
- Log webhook DB cleanup errors instead of swallowing
- Reject camera enable if CountActiveCameras query fails
- Add LiveKit health status to /metrics endpoint
- Add 13 unit tests for livekit.go, livekit_process.go, livekit_webhook.go
2026-03-22 19:29:42 +01:00
jevb ce8613e685 Merge remote-tracking branch 'origin/main' into feature/livekit-migration 2026-03-22 19:29:29 +01:00
jevb 7a16182f5b fix: restore audio track attachment for remote playback
The refactored LiveKit session dropped track.attach() for remote audio,
so no <audio> element was created and remote participants were silent.
Also refactors noise suppression to use LiveKit TrackProcessor API,
adds input volume gain node bypass at 100%, and exposes __lkDebug()
on window for DevTools diagnostics.
2026-03-22 15:34:26 +01:00
jevb a653eb33ed fix: address code review issues in settings and message rendering
- Wire animateGifs pref to file attachments (observeMedia startFrozen)
- Fix stale user closure in AccountTab edit form
- Cache loadPref reads in hot render paths with custom event invalidation
- Extract syncOsMotion listener to lib/os-motion.ts with AbortController cleanup
- Add sideEffect to syncOsMotion toggle for in-session activation
- Use static import for invoke in AdvancedTab, add error logging
- Fix password success timer race in AccountTab
- Extract keybind section header to CSS class
- Handle clipboard.writeText rejection in Copy ID button
- Label hardware acceleration as requiring restart
2026-03-22 12:07:24 +01:00
jevb 09fca14422 fix: use Rust invoke for Open DevTools button
The button was calling a nonexistent openDevtools() method on the
JS WebviewWindow API. Use invoke("open_devtools") to call the
registered Rust command instead.
2026-03-22 11:16:21 +01:00
jevb 4c84ff6024 feat: wire up settings preferences to their consuming features
Connect stored-but-unused settings to their actual functionality:

- Text & Images: showLinkPreviews, showEmbeds, inlineMedia now
  conditionally render embeds/previews/images in messages
- Text & Images: animateGifs controls GIF auto-play via new
  startFrozen parameter on observeMedia()
- Accessibility: roleColors toggles role-based username coloring
- Accessibility: syncOsMotion listens to OS prefers-reduced-motion
  and auto-toggles reduced-motion class (fix default to false)
- Advanced: developerMode shows Copy ID button on messages
2026-03-22 11:02:38 +01:00
jevb 0c4acbb569 fix: scope settings CSS classes and use correct separator class
- Rename status-option → settings-status-option in AccountTab to avoid
  CSS collision with existing status picker popup
- Replace non-existent settings-divider class with settings-separator
  in KeybindsTab
2026-03-22 10:31:00 +01:00
jevb ca2b1e2f2b feat: add Navigation, Communication, and Messages sections to Keybinds tab
Expand the Keybinds tab beyond Push-to-Talk and Quick Switcher by adding
three new read-only sections with HR separators and uppercase section headers:
- Navigation: Quick Switcher (Ctrl+K), Mark as Read (Escape), Search Messages (Ctrl+F)
- Communication: Toggle Mute (Ctrl+M), Toggle Deafen (Ctrl+D), Toggle Camera (Ctrl+Shift+V)
- Messages: Upload File (Ctrl+U), Edit Last Message (Arrow Up)
2026-03-22 10:29:46 +01:00
jevb 414d14fe4f feat: add Advanced settings tab with Developer Mode, Hardware Acceleration, and DevTools
Adds a new "Advanced" settings tab as the second-to-last entry in App Settings
(before Logs). Includes toggles for developerMode and hardwareAcceleration
preferences (persisted via savePref/loadPref), plus a debug section with an
Open DevTools button that calls the Tauri webviewWindow API.
2026-03-22 10:26:41 +01:00
jevb 76e91b5ac6 feat: add Accessibility settings tab with reduced motion, high contrast, and large font
Adds a new Accessibility tab to the Settings overlay (positioned between
Text & Images and Voice & Audio) with five toggles: Reduce Motion,
High Contrast, Role Colors, Sync with OS, and Large Font. Toggles with
side effects apply/remove CSS classes (reduced-motion, high-contrast,
large-font) on documentElement immediately and are also restored at
startup via applyStoredAppearance(). Corresponding CSS rules added to
app.css; eye icon was already present in icons.ts.
2026-03-22 10:18:18 +01:00
jevb 0999f83fd9 feat: add Text & Images settings tab
Adds a new settings tab with 6 toggles for controlling media and text
display: link previews, rich embeds, inline attachment preview, GIF
animation, emoji animation, and spoiler reveal behaviour. Also registers
the "image" Lucide icon used by the tab's sidebar entry.
2026-03-22 10:09:41 +01:00
jevb fe4a286449 feat: add user status selector to Account settings tab
Adds a status selector section to the Account tab with four status options
(Online, Idle, Do Not Disturb, Invisible). Clicking a status updates the
local members store, sends a presence_update WebSocket message, and persists
the choice via savePref. Adds onStatusChange callback to SettingsOverlayOptions
so the main page handles WS dispatch, keeping the settings components
decoupled from transport.
2026-03-22 10:04:49 +01:00
jevb f5c63195b9 feat: add input/output volume sliders to Voice & Audio settings tab
- Added setInputVolume and setOutputVolume methods to LiveKitSession class
- setInputVolume saves mic gain preference (0-200%, persisted only for now)
- setOutputVolume saves pref and immediately applies to all remote audio elements (capped at 1.0 / 100%)
- Both methods exported as bound functions alongside existing session exports
- Added Input Volume slider (0-200%, default 100%) between input device selector and Output Device section
- Added Output Volume slider (0-200%, default 100%) between output device selector and Video Device section
- Both sliders use existing .slider-row / .settings-slider / .slider-val CSS classes
2026-03-22 09:54:07 +01:00
jevb 54f0b35d33 feat: add accent color picker to Appearance settings tab
Adds a full accent color customization section below Compact Mode:
- 10 preset color swatches (Discord blurple default, plus green, yellow,
  fuchsia, red, salmon, orange, dark green, cyan, grey)
- Custom hex input (#RRGGBB) with live validation and swatch sync
- Live preview via --accent CSS custom property on documentElement
- Persisted via savePref/loadPref("accentColor") in localStorage
- Restored at app startup via applyStoredAppearance() in SettingsOverlay.ts
- Accessible: role=radio, aria-checked, keyboard nav (Enter/Space)
2026-03-22 09:50:32 +01:00
jevb 02dd4ad579 fix: remove duplicate h1 headers in settings tabs and guard camera auto-start
- Remove redundant <h1> header creation and append from NotificationsTab,
  VoiceAudioTab, and LogsTab — SettingsOverlay already renders the page title
- Only call startCameraPreview() when a saved video device ID is non-empty,
  preventing uninvited camera access on settings open
2026-03-22 09:44:46 +01:00
jevb 9654afcaee fix: address review issues in settings redesign
- Fix stale .ac-name selector in tests → .account-header-name
- Remove misleading "Display Name" field (no displayName in data model)
- Extract buildProfileCard and buildPasswordSection helpers
- Use var(--green) instead of hardcoded #43b581 for status dot
- Remove conflicting position:absolute from .settings-close-btn CSS
- Remove inline style override on close button
2026-03-22 09:30:03 +01:00
jevb 71c47e5fca feat: Discord-style account profile card redesign (Phase 2 + 3)
- Replace flat account card with Discord-style layout: colored accent
  banner, 80px avatar overlapping banner with status dot, username
  header with "Edit User Profile" button
- Add separated Display Name / Username field rows with individual Edit
  buttons; all three trigger the same inline edit form and update all
  name display elements on save
- Phase 3: rename "Change Password" h3 to a .settings-section-title
  "Password and Authentication" div, increase password input gap to 12px
- Update .account-card CSS to use overflow:hidden instead of padding/flex
- Add new CSS classes: .account-banner, .account-avatar-wrap,
  .account-avatar-large, .account-status-dot, .account-header,
  .account-header-name, .account-fields, .account-field,
  .account-field-label, .account-field-value, .account-field-edit,
  .settings-separator, .settings-section-title
- Minor SettingsOverlay.ts cleanup: early return guard and remove
  redundant inline style on close button
2026-03-22 09:18:52 +01:00
jevb 4b0adc96f7 feat: Discord-style settings sidebar with icons, profile section, and categories
Phase 1 sidebar enhancement:
- Add 5 new Lucide icons to icons.ts: user, palette, bell, keyboard, scroll-text
- Add TAB_ICONS mapping so each settings tab has its own icon (mic already existed)
- Add user profile section at top of sidebar (avatar initial + username + Edit Profile link)
- Split nav into "User Settings" (Account) and "App Settings" (Appearance–Logs) categories
- Move Log Out button from AccountTab to sidebar bottom with separator line above it
- Wrap close button in .settings-close-wrap container with "ESC" label below it
- Add h1 page title at top of content area, updates on tab switch
- Make sidebar a flex column so logout section sticks to the bottom via margin-top:auto
2026-03-22 09:14:55 +01:00
jevb c5d867c9da feat: Discord-style timestamps, reply avatars, virtual scroll fixes
- Use formatMessageTimestamp for "Today at H:MM AM/PM" in headers
- Add mini avatar to reply references
- Fix virtual scroll height estimates for new spacing (61/22px)
- Include CSS margins in height measurement via getComputedStyle
- Fix 0px → 0 in --message-inline-spacing token
2026-03-22 09:00:48 +01:00
jevb 62e5eebd7a style: update compact mode spacing for Discord layout 2026-03-22 08:58:15 +01:00
jevbandClaude Opus 4.6 3d128b5357 feat: add Discord-style timestamp formatting and message layout tokens
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 08:47:09 +01:00
jevb 92c78f0ab5 test: update invite manager selectors for modal redesign
Update unit and E2E test selectors to match the new modal class names
(.modal-overlay, .modal-close) used after the invite UI redesign.
2026-03-22 08:26:21 +01:00
jevb b7a906320d style: redesign server invites modal with card layout and icons 2026-03-22 08:19:22 +01:00