Commit Graph
67 Commits
Author SHA1 Message Date
jevb bf3fada16b fix: proxy LiveKit through HTTPS to fix mixed-content block
- Add reverse proxy at /livekit/* that forwards to LiveKit server
- Server sends relative URL "/livekit" in voice_token; client
  resolves to wss://server:port/livekit using known server host
- Fix API secret minimum length (32 chars required by LiveKit)
- Pass TLS config to LiveKit process manager for TURN certs
2026-03-20 06:11:09 +01:00
jevb fed85a0d3a feat: add LiveKit webhook handler (Phase 1.5)
- livekit_webhook.go: HTTP handler for LiveKit participant_joined/left
  events with JWT verification; syncs stale voice state on crash recovery
- Mount webhook at POST /api/v1/livekit/webhook in router
- Speaker detection handled client-side via LiveKit SDK events
2026-03-20 05:34:38 +01:00
jevb 72d5412ba7 feat: rewrite server voice handlers for LiveKit (Phase 1)
Replace custom Pion WebRTC SFU with LiveKit token-based flow.

Server changes:
- client.go: remove PeerConnection, voiceDone, negoMu; add
  setVoiceChID/clearVoiceChID
- voice_handlers.go: 961 -> ~295 lines; voice_join now generates
  LiveKit token, voice_leave calls RemoveParticipant; delete
  SDP/ICE/RTP/soundboard handlers
- hub.go: replace SFU + VoiceRooms with LiveKitClient +
  LiveKitProcess; remove speaker broadcast goroutine
- messages.go: add buildVoiceToken, remove buildVoiceOffer/
  Answer/ICE; simplify buildVoiceConfig
- handlers.go: remove voice_offer/answer/ice/soundboard dispatch
- router.go: replace NewSFU with NewLiveKitClient, add optional
  LiveKit process auto-start

Deleted files (14):
- sfu.go, voice_room.go, speaker_detector.go, rtp_audio_level.go,
  speaker_broadcast.go, api/voice_handler.go
- All corresponding test files

All server tests pass (go test ./...).
2026-03-20 05:30:27 +01:00
jevb 80e880d536 feat: add LiveKit infrastructure (Phase 0)
- Replace VoiceConfig STUN/TURN/MediaPort fields with LiveKit
  API key, secret, URL, and binary path
- Add livekit_process.go: companion process manager with
  auto-restart and config generation
- Add livekit.go: token generation, room service client,
  participant management, video track counting
- Add LiveKit server SDK dependency (server-sdk-go/v2)
- Update config tests for new VoiceConfig fields
2026-03-20 05:14:52 +01:00
jevb f3734bf827 fix: voice rejoin failure, SDP race, deafen bypass + add server voice logging
- Fix SDP signaling race condition: add per-client negoMu to serialize
  renegotiateParticipant / handleVoiceOffer / handleVoiceAnswer so
  concurrent OnTrack goroutines don't race through rollback
- Fix handleVoiceLeave triple-fire: early return when clearVoice()
  returns zeros so ICE callbacks don't re-enter and corrupt state
- Fix SQLite SQLITE_BUSY errors: add busy_timeout=5000 pragma and
  SetMaxOpenConns(1) for file-based databases
- Fix deafen bypass: new remote audio elements now respect localDeafened
  state so late-arriving streams are muted immediately
- Add debug-level logging for SDP negotiation, track fan-out, ICE
  candidates, voice state changes, room lifecycle, and participant
  add/remove
- Bump version to 1.1.1
2026-03-19 21:35:18 +01:00
jevb 3c1cef00d6 fix: add USE_VIDEO permission to Member role and fix single-user video mode
Two fixes:
1. Member role (0x663) was missing USE_VIDEO (0x800) and SHARE_SCREEN (0x1000)
   bits, causing "permission denied" when non-owner users tried to enable camera.
   Migration 006 updates Member permissions to 0x1E63.
2. checkVideoMode() now checks voice.localCamera immediately instead of waiting
   for the server's voice_state broadcast, so video grid shows instantly when
   a single user enables their camera.
2026-03-19 17:19:56 +01:00
jevb b79af83bda fix: use unique stream IDs for audio and video tracks to prevent dedup collision
Audio and video tracks from the same user were both using stream ID "user-{id}",
causing the client's duplicate stream check to silently drop the video track.
Changed to "user-{id}-audio" and "user-{id}-video" so each gets its own stream.
2026-03-19 17:13:59 +01:00
jevb 8c4734fe81 test: add video track coexistence integration test
Verify multi-user composite track key behavior: audio+video coexistence
per user, GetTracks total count, TrackUserIDs deduplication, and
independent track removal across users.
2026-03-19 16:52:15 +01:00
jevb b802532af3 feat: extend SFU to forward video tracks and enforce MaxVideo limit
- setupOnTrack now handles both audio and video tracks via kind branch
  instead of returning early on non-audio tracks
- RTP forwarding works identically for both kinds; speaker detection
  only runs for audio tracks
- handleVoiceCamera enforces MaxVideo limit before DB update, rejecting
  with VIDEO_LIMIT error when the cap is reached
- Added TestHandleVoiceCamera_MaxVideoEnforced test
2026-03-19 16:33:47 +01:00
jevb 51033dab6d refactor: use composite track keys in VoiceRoom for multi-track support 2026-03-19 16:25:12 +01:00
jevb b2038cecdb feat: settings overhaul, GIF picker, notifications, PTT, scroll fixes
Client:
- Wire up all 4 notification toggles (desktop, taskbar flash, sounds, @everyone)
- Add compact mode CSS with visible layout differences
- Add GIF picker with Tenor API (trending + search)
- Render inline images/GIFs instead of link previews for direct URLs
- Add push-to-talk via Rust GetAsyncKeyState polling (non-consuming)
- Add key capture UI for PTT keybinds (supports mouse buttons)
- Add error/success feedback on account settings (password, username)
- Fix mic stream cleanup on tab switch (VoiceAudioTab factory pattern)
- Fix message timestamps using UTC with proper timezone conversion
- Fix emoji reaction picker (was returning early on empty emoji)
- Fix chat scroll jumpiness with Discord-style overflow-anchor + ResizeObserver
- Pre-measure all message heights on load to prevent first-scroll jump
- Improve scrollbar visibility with semi-transparent white thumb
- Show client version on Logs tab

Server:
- Add admin_allowed_cidrs config to restrict /admin to private networks
- Fix voice config defaults lost when YAML section has omitted fields
- Add negative caching to updater (5min error cache)
2026-03-19 12:23:24 +01:00
jevb 3e6d36a47b feat: add negative caching to updater to avoid API spam
Failed GitHub API calls are now cached for 5 minutes before retrying,
preventing repeated 502 errors in the logs when the API is unreachable.
2026-03-19 09:49:21 +01:00
jevb 4945bc9ee9 fix: resolve CI failures in server lint and client tests
Server lint (golangci-lint):
- Add package comment to client_update.go (ST1000)
- Remove unnecessary fmt.Sprintf in updater_test.go (S1039)
- Remove redundant |0x00 in rtp_audio_level_test.go (SA4016)

Client tests (vitest):
- Fix messages.store.test.ts: account for .reverse() in
  setMessages/prependMessages (API returns newest-first)
- Fix logs-tab.test.ts: use .log-entry pre selector to avoid
  matching diagnostics <pre> placeholders; add createLogger
  to @lib/logger mock
- Fix settings-overlay.test.ts: add setVoiceSensitivity to
  @lib/voiceSession mock
2026-03-19 06:20:00 +01:00
jevb df998386d9 feat: redesign admin panel, add live server logs and audit log filters
Admin panel redesign:
- Rebuild frontend from mockup with Discord-style dark theme
- Stat cards, section cards, role badges, modal system, toast notifications
- All 7 sections: Dashboard, Users, Channels, Audit Log, Settings, Backups, Updates
- Modals replace confirm()/prompt() for all destructive actions

Live server logs (new):
- RingBuffer + MultiHandler tees slog to stdout AND in-memory buffer
- SSE endpoint at /admin/api/logs/stream streams logs in real-time
- Log viewer with level filters (DEBUG/INFO/WARN/ERROR), search,
  auto-scroll, pause/resume, copy all, clear
- Color-coded lines by level, source categorization from file paths

Audit log improvements:
- Search filter (actor, action, target, detail)
- Action type dropdown filter
- Copy All and Export CSV buttons
- Instant client-side re-filtering

Console output:
- Switch from JSON to human-readable text format (slog.TextHandler)
- Move startup banner before init logs so it appears first
2026-03-19 05:32:40 +01:00
jevb 851a8b8b21 fix: add rows.Err() check in GetAttachmentsByMessageIDs
Prevents silently returning truncated attachment maps when the DB
cursor errors mid-iteration. Matches the pattern used in all other
query loops across the codebase.
2026-03-19 04:27:11 +01:00
jevb a7df9c2b3c fix: resolve 5 remaining medium/low issues from third-pass go-review
- NEW-1: Add rows.Err() check in ListMembers to catch cursor errors
- NEW-2: Add minVal parameter to queryInt so offset=0 is not rejected
- NEW-3: Fix copyFile double-close by removing defer, using explicit
  close on both success and error paths
- NEW-4: Add GetAllChannelPermissionsForRole batch query, eliminating
  N+1 GetChannelPermissions calls in channel list and search handlers
- NEW-5: Cap fetchBody with io.LimitReader(1 MiB) to prevent memory
  exhaustion from malformed release assets
2026-03-19 04:19:03 +01:00
jevb 13797e7075 fix: resolve 6 medium issues from full go-review
- MED-1: Document soundboard channelID=0 server-wide permission intent
- MED-2: Document os.Exit(0) in update handler skipping deferred cleanup
- MED-3: Replace os.ReadFile/WriteFile with streaming io.Copy in backup
  restore to avoid loading entire DB into memory
- MED-4: Add GetAllVoiceStates bulk query, eliminating N+1 per-channel
  queries in collectAllVoiceStates
- MED-5: Wrap handlePatchSettings updates in a transaction for atomicity
- MED-6: Add rows.Err() check after scan loop in getReactionsBatch
- MED-9: Replace manual port-stripping in serverHost with net.SplitHostPort
  for correct IPv6 handling
2026-03-19 04:04:53 +01:00
jevb 65a8403a92 fix: resolve 1 critical and 5 high security/reliability issues from go-review
- CRIT-2: Prevent HTTP header injection in Content-Disposition via mime.FormatMediaType
- HIGH-1: Call hub.GracefulStop() on server shutdown to clean up WebSocket/voice state
- HIGH-2: Remove double send on serveErr channel that caused goroutine leak
- HIGH-3: Handle GetUserByID error after registration to prevent nil panic
- HIGH-4: Return nil,nil on sql.ErrNoRows in GetAttachmentByID (matching codebase convention)
- HIGH-5: Add voiceDone channel to bound RTP goroutine lifetime on voice teardown
- Fix handleVoiceICE to return VOICE_ERROR when no PC (consistent with other voice handlers)
- Update tests to match corrected behavior
2026-03-19 03:53:40 +01:00
jevb 5311d0a7e3 feat: fix voice chat over NAT, audio pipeline, and add comprehensive debugging
Voice was broken over NAT due to multiple issues across the audio pipeline:

- Fix GainNode silence: WebView2 silences remote WebRTC streams routed through
  Web Audio createMediaStreamSource→GainNode→createMediaStreamDestination.
  Replaced with direct HTMLAudioElement playback for remote audio.
- Fix NAT traversal: Add Google public STUN server (stun.l.google.com:19302)
  so remote clients can discover their public IP for ICE connectivity.
- Fix signaling race: Catch createOffer InvalidStateError when server
  renegotiation offer arrives before client's initial offer is sent.
- Fix device switch: Use replaceTrack() instead of removeTrack+addTrack
  to avoid SDP renegotiation. Safe rollback on failure (stop old last).
- Fix speaking flicker: setSpeakers skips local user (VAD is sole authority).
- Fix VAD sample rate: Force 48kHz AudioContext instead of system default
  (192kHz) which spread FFT bins too wide for voice frequency detection.
- Fix CSP for WASM: Add wasm-unsafe-eval to script-src for RNNoise.
- Fix clearAuth leak: leaveVoice() called before resetVoiceStore().
- Fix ICE rate limit: Separate limit for ICE candidates (50/s vs 20/s).
- Fix stale ICE errors: Silently drop voice_ice with no PeerConnection.
- Fix audio play() race: Deferred to queueMicrotask after DOM attachment.

Debugging infrastructure:
- Logs tab: Copy All button, Voice Diagnostics panel with live session
  state, Probe Audio Levels (measures actual signal at 3 pipeline points),
  Test Direct Playback button, Copy Diagnostics button.
- Client logging: WebRTC (PeerConnection lifecycle, ICE candidates with
  type/address, track events, negotiation), VAD (start/threshold/destroy),
  Audio (device acquisition with settings, device changes), noise suppression
  (WASM load timing, worklet vs fallback path), voiceSession (remote stream
  parsing failures, deafen state, audio element playback events).
- Server logging: SFU init config, voice room mode transitions/track
  lifecycle/close, RTP forwarding with packet counts and first-packet
  detection, 5s no-packet warning, track fan-out counts, subscriber
  transceiver state, ICE candidate details, voice credentials issued.
- Logger: Error objects now serialize .message and .stack instead of {}.

Per-user volume right-click now works on voice user rows in sidebar.
RNNoise ML noise suppression with AudioWorklet + ScriptProcessor fallback.
Tests: 7 new test cases (replaceTrack, setSpeakers skip-local, clearAuth).
2026-03-18 23:02:06 +01:00
jevb 01e4d4bec3 feat: client auto-update with Ed25519 signing and dynamic server URL
- Add tauri-plugin-updater and tauri-plugin-process for in-app updates
- Rust commands (check_client_update, download_and_install_update) build
  updater with dynamic endpoint at runtime for self-hosted compatibility
- Server endpoint GET /api/v1/client-update/{target}/{version} translates
  GitHub Releases into Tauri updater JSON format with .sig content
- UpdateNotifier banner component with install/dismiss controls
- CI workflow produces signed .nsis.zip + .sig updater artifacts
- Self-signed TLS support via dangerousAcceptInvalidCerts config
2026-03-18 17:47:59 +01:00
jevb 750a7af052 feat: native file downloads, upload size fix, native E2E tests
- Add file download with native save dialog (Tauri dialog + fs plugins)
- Make attachment filename clickable as additional download trigger
- Add download button with hover styling to file attachments
- Exempt /api/v1/uploads from global 1MB body size limit (MaxBodySizeUnless)
- Add native E2E test suite (8 specs) with Playwright CDP fixture
2026-03-18 17:10:16 +01:00
jevb 13be0fd6d3 feat: file uploads, URL previews, emoji search, voice mute fixes, UX improvements
Server:
- Add POST /api/v1/uploads and GET /api/v1/files/{id} endpoints
- Add CreateAttachment DB method for file upload records
- Allow empty message content when attachments are present
- CORS headers on file serving for WebView2 compatibility

Client — File uploads & attachments:
- Clipboard paste (Ctrl+V) and attach button (+) for file uploads
- Preview bar above input with thumbnail, spinner, and remove button
- Images fetched via Tauri HTTP plugin as base64 data URIs (bypasses
  self-signed cert rejection in WebView2)
- Three-layer image cache: memory → IndexedDB → network
- In-flight deduplication prevents duplicate concurrent fetches
- Image lightbox with click-to-zoom, scroll wheel zoom, pan, keyboard shortcuts

Client — URL previews & embeds:
- URLs in messages rendered as clickable links
- YouTube embeds with thumbnail, play button, video title via oEmbed API
- Generic link previews with OG metadata (title, description, image)
- Fetched via Tauri HTTP plugin with Facebook crawler User-Agent
- YouTube title cache and OG metadata cache prevent re-fetch on re-render
- Links open in default browser via tauri-plugin-opener

Client — Voice & audio fixes:
- Mute uses replaceTrack(null) for reliable RTP-level muting in WebView2
- Deafen also mutes mic; undeafen/unmute unmutes both
- Muted users show crossed mic icon, deafened show crossed mic + headphone
- Re-apply mute state after input device switch

Client — UX improvements:
- Disable browser context menu globally (only custom menus show)
- Emoji search now matches by keyword names (smile, heart, fire, etc.)
- Emoji picker closes on click outside
- User bar status text moved below username
- Messages sorted chronologically (oldest first, newest at bottom)
- Scroll to bottom on initial load with deferred retries for layout shifts
- Image attachments constrained to 400x350px with click-to-lightbox
2026-03-18 14:13:52 +01:00
jevb f36fb1ffdc feat: channel management — create, edit, delete, reorder with category-type enforcement
Server:
- Enforce category-type validation: text/announcement only under text categories,
  voice only under voice categories (400 on mismatch)
- Admin panel category field changed to dropdown with auto-filtered type options
- Default setup creates both Text Channels and Voice Channels categories

Client:
- Add create/edit/delete channel modals (admin/owner only)
- "+" button on category headers to create channels with pre-filled category
- Right-click context menu on channels for edit/delete
- Mouse-based drag-and-drop reordering within categories
- Admin API methods: adminCreateChannel, adminUpdateChannel, adminDeleteChannel
- Immediate local store update on reorder for instant feedback

Tests: 7 server integration tests, 31 client unit tests (create/edit/delete modals)
2026-03-18 11:28:13 +01:00
jevb 32c39e95aa fix: prevent stale PeerConnection callbacks from killing new voice sessions
When switching voice channels, the old PC's OnICEConnectionStateChange(closed)
fires asynchronously after the new PC is set. Both server and client had the
same race: the stale callback saw "voice is active" and called handleVoiceLeave,
closing the new session.

Server: setupICEMonitor now compares the PC reference before acting on events
Client: joinVoice now calls leaveVoice(false) to clean up old session first
2026-03-18 07:24:19 +01:00
jevb e7f53000f3 fix: add nil hub tests for PatchUser ban and role change paths (BUG-001)
Closes the last untested nil-pointer panic path in admin API handlers.
BUG-002 (window-state.ts any) confirmed already resolved.
2026-03-18 05:15:54 +01:00
jevb fca1b0cc0c test: boost server test coverage to 80%+ across all packages
Add comprehensive tests for db, storage, updater, and ws packages
covering edge cases, error paths, and voice handler functions.
New test files for attachment queries and WebSocket coverage boost.
2026-03-18 05:03:38 +01:00
jevb b78c9319fa fix: resolve CI failures — coverage exclusion and stale test removal
- Exclude voiceSession.ts from coverage (browser API dependency, same
  pattern as audio.ts/vad.ts/webrtc.ts)
- Remove stale TestHub_Register_CleansUpOldVoiceState test that tested
  old duplicate-login behavior removed in b53c729
2026-03-18 04:30:38 +01:00
jevb 6ca8449fc9 fix: guard ICE close handler to prevent double voice_leave and SQLITE_BUSY 2026-03-18 04:16:49 +01:00
jevb 66abbed49d fix: voice session cleanup on ICE close and connection failure
- Server: handle ICEConnectionStateClosed in setupICEMonitor to clean up
  phantom participants when client PC is destroyed
- Server: skip TURN config when turn_secret is empty (suppresses noisy
  "password is empty" errors)
- Client: voiceSession.leaveVoice() now sends voice_leave to server by
  default, fixing the case where WebRTC failure triggers local cleanup
  but server never learns the user left
- Client: explicit leave paths (UI button, logout, beforeunload) pass
  sendWs=false to avoid double-sending voice_leave
2026-03-18 04:13:12 +01:00
jevb 64b5f43176 feat: wire SFU track forwarding and renegotiation in voice handlers
Rewrites setupOnTrack to create TrackLocalStaticRTP for fan-out,
store tracks on VoiceRoom, add them to other participants' PCs,
and forward RTP while parsing audio levels. Updates handleVoiceJoin
to subscribe new joiners to existing tracks and handleVoiceLeave
to remove departing users' tracks from all subscribers.
2026-03-18 03:53:22 +01:00
jevb db6cd01afe feat: add renegotiateParticipant with Perfect Negotiation
Adds Hub.renegotiateParticipant to ws/voice_handlers.go. The function
creates a new SDP offer for a client's PeerConnection and sends it as
voice_offer, implementing the impolite side of Perfect Negotiation by
skipping renegotiation when the PC is in have-remote-offer state.
2026-03-18 03:49:23 +01:00
jevb 6034bb1929 feat: add GetClient helper and ICE candidate callback 2026-03-18 03:47:30 +01:00
jevb 8889faddef feat: add buildVoiceOffer and buildVoiceICE message builders
Add two new server-to-client WebSocket message builders following the
existing buildVoiceAnswer pattern. Expose them via export_test.go
wrappers and cover with ws_test package tests (TDD: RED → GREEN).
2026-03-18 03:44:38 +01:00
jevb b3e4dd7adb feat: add VoiceTrack struct and track CRUD to VoiceRoom
Introduces VoiceTrack (Remote/Local track pair + per-subscriber RTPSender map)
and five thread-safe methods on VoiceRoom (SetTrack, RemoveTrack, GetTracks,
TrackUserIDs, GetTrack) needed for SFU audio forwarding in Phase 3.
Close() now also resets the tracks map. Five new tests cover all paths (TDD).
2026-03-18 03:41:48 +01:00
jevb b53c729f89 fix: reject duplicate WebSocket logins to prevent reconnect ping-pong
Server now checks IsUserConnected before accepting a new WebSocket and
returns an auth_error with a clear message instead of silently replacing
the old session. Client dispatcher surfaces the error via transient UI
state so the ConnectPage can display it.
2026-03-18 02:41:55 +01:00
jevb 6f35973c1b feat: TOFU cert pinning, voice channel sidebar, scroll-to-message, profiles, and review fixes
- Add cert mismatch modal for TOFU certificate pinning
- Separate voice channels from text in sidebar with user lists
- Implement scrollToMessage and jump-to-pinned-message in overlay
- Add server profiles with credential auto-fill on connect page
- Fix credential auto-fill race condition on rapid profile clicks
- Add channel_focus event for channel-scoped message delivery
- Fix member list case-insensitive role filtering
- Server normalizes role names to lowercase for protocol consistency
- Remove redundant permission-denied log in handleChannelFocus
- Voice store: bulk set states from ready payload, leave cleanup
- WebSocket reconnect and structured logging improvements
- Add tests for cert modal, overlay managers, voice sidebar,
  message list scroll, quick switcher, and profile management
2026-03-17 20:25:15 +01:00
jevb aa2410e9ea fix: voice channel join/leave visibility and disconnect bugs
- Fix VoiceWidget disconnect not sending voice_leave to server
- Add voice cleanup on logout (send voice_leave before ws.disconnect)
- Add beforeunload handler for best-effort voice_leave on app close
- Broadcast voice_state/voice_leave to ALL clients (not just channel
  members) so every sidebar updates when users join/leave voice
- Fix CleanupVoiceForChannel using BroadcastToChannel instead of
  BroadcastToAll
- Remove user list from VoiceWidget (users only shown in sidebar)
- Guard VoiceWidget disconnect against double-click
- Add 6 tests for voice disconnect behavior
2026-03-17 20:03:34 +01:00
claude[bot]andJ3vb 404764ce97 fix: resolve CI failures — errcheck lint and TS noUncheckedIndexedAccess errors
- ws_integration_test.go:42: wrap resp.Body.Close() to handle errcheck
- dispatcher.ts:183: add non-null assertion on sorted[0] array access
- keybinds-tab.test.ts: add non-null assertions on NodeList index accesses
- logs-tab.test.ts: add non-null assertions on querySelectorAll index accesses

Co-authored-by: J3vb <J3vb@users.noreply.github.com>
2026-03-17 14:01:26 +00:00
jevb 45b720811e fix: address PR #15 review issues (#16-#23)
- #16: Fix golangci-lint issues (unchecked Close(), unused funcs, naming)
- #17: Add KeybindsTab and LogsTab unit tests (19 tests, 81%+ coverage)
- #18: Add rate limiting to chat_edit and chat_delete handlers
- #19: Fix cert mismatch handling via event listener instead of string match
- #20: Validate SHA-256 colon-hex fingerprint format in Rust ws_proxy
- #21: Optimize session+ban check with single JOIN query
- #22: Sort channels by position when redirecting after deletion
- #23: Rename admin test files for clarity

Also fixes ban expiry regression (H-1 from code review) by using
auth.IsEffectivelyBanned() to properly respect temporary ban expiry.
2026-03-17 14:54:50 +01:00
jevb 4d1a1676c7 feat: TOFU cert pinning, settings cache refactor, ban enforcement, and 80%+ test coverage
- Implement TOFU certificate pinning in Rust WS proxy with accept_cert_fingerprint command
- Refactor settings cache from package-level globals to Hub methods (eliminates global state)
- Add runtime ban check on WS message handling (kicks banned users mid-session)
- Sanitize reaction error messages to prevent IDOR information leaks
- Add slog error logging to REST handlers (channel, invite, search)
- Handle channel_delete for active channel in client dispatcher
- Add certMismatchBlock to prevent auto-reconnect on TOFU mismatch
- Consolidate root-level spec docs into docs/brain/06-Specs/ vault
- Add 80%+ test coverage for ws (80.9%) and admin (81.7%) packages
- Delete completed TODOS.md (all items resolved)
2026-03-17 11:05:52 +01:00
jevb ce4326766a fix: resolve all golangci-lint issues blocking CI server build
Fix 70+ errcheck violations by adding explicit error discards (`_ =`)
for unchecked return values across test helpers and deferred Close()
calls. Remove unused `senderID` field from broadcastMsg and unused
`defaultCleanupMaxWindow` const. Apply De Morgan's law, remove empty
branch, and simplify redundant type declaration per staticcheck.
2026-03-17 08:09:52 +01:00
jevb 9c1d99683c fix: address PR review findings (issues #9-#14)
- Fix capacity over-allocation and use strings.Builder in getReactionsBatch (#9)
- Replace `any` types and cache Tauri invoke in window-state.ts (#10)
- Remove custom `contains` helper, fix NilHub tests to pass nil (#11)
- Add nil guards before hub method calls in admin handlers (#12)
- Run golangci-lint v2: modernize interface{}/any, range-over-int loops,
  remove dead code, fix errcheck, add .golangci.yml config (#13)
- Add 23 client unit test suites (694 tests), exclude Tauri-coupled
  files from coverage, achieve 80%+ threshold (#14)

Closes #9, closes #10, closes #11, closes #12, closes #13, closes #14
2026-03-17 04:11:04 +01:00
jevb 1b596367c4 fix: address PR review findings (issues #3-#8)
- Fix double-close panic in Hub.Stop/GracefulStop using sync.Once (#3)
- Bump golangci-lint action to v9 with v2.11.3 for Go 1.25 support (#4)
- Add input validation guards to SearchMessages (#5)
- Handle promise rejections in InviteManager with error toasts (#6)
- Add missing reply_to and edited_at columns to admin test schema (#7)
- Add ClientCount to HubBroadcaster interface and wire into stats endpoint (#8)
2026-03-17 03:20:37 +01:00
jevb 8f4349ba42 feat: server enhancements, client test selectors, and UI polish
Server:
- Add message search and pinned messages support
- Add admin hub integration and live connection stats
- Update admin test mocks for hub interface

Client:
- Add data-testid attributes to components for E2E testing
- Add window management capabilities (position, size, maximize)
- Add prod E2E test config and script
- Fix CSS imports (use vite bundling instead of HTML link tags)
- Add inline styles to InviteManager overlay for reliability
- Update CHATSERVER.md references from WPF to Tauri

Docs:
- Update quick-start guide
2026-03-17 02:56:19 +01:00
jevb 79ea3ab42b refactor: split oversized files + add store notification batching
- Split Server/admin/api.go (788→281 lines) into handlers_users.go,
  handlers_channels.go, handlers_settings.go, handlers_backup.go
- Split Client SettingsOverlay.ts (~685→173 lines) into 7 per-tab
  modules under components/settings/
- Add queueMicrotask-based notification batching to createStore with
  flush() for synchronous test assertions
- Update 8 test files with flush() calls for batched store updates

Addresses TODOS.md #9 (split oversized files) for 2 of 3 targets.
2026-03-17 02:17:26 +01:00
jevb 4bdc83a368 fix: resolve 15 post-review issues across server and client
Server fixes:
- Move ATTACH_FILES permission check before CreateMessage to prevent
  orphaned messages on permission denial
- Fix hardcoded /api/files/ URL to /api/v1/files/ per spec
- Add error logging for GetAttachmentsByMessageIDs failure
- Set 1MB WebSocket read limit to match client-side limit
- Extract requireChannelPerm helper, replacing 8 repeated patterns

Client fixes:
- Wire onUnauthorized callback to clear auth on 401 responses
- Store auth token in authStore before WS connect
- Reset WS state to disconnected when Tauri APIs unavailable
- Add connectivity guard and 200ms send debounce on message send
- Add toast container to MainPage with error feedback on 5 API failures
- Clear voice currentChannelId on server-driven voice_leave for current user
- Apply stored theme/font/compact preferences at app startup
- Fix infinite scroll throttle to use store subscription instead of fixed timer

Tests:
- Add TestChatSend_AttachmentsDeniedNoMessageCreated
- Add attachments table to handler test schema
2026-03-17 01:59:34 +01:00
jevb 95c5b2d3b9 test: add authorization and contract tests for channel access
- REST authorization: 6 tests verifying READ_MESSAGES enforcement
  on GET /channels, GET /channels/{id}/messages, and GET /search
  with channel override deny and admin bypass
- WS authorization: 4 tests verifying channel_focus and chat_send
  permission checks with deny overrides and admin bypass
- Contract tests: 3 tests asserting response shapes match API.md
  (message fields, user object, attachments, reactions with me flag,
  search result fields)

Closes test gaps identified in CODE_REVIEW.md.
2026-03-16 17:17:00 +01:00
jevb 53d78feef6 feat: add attachment persistence and link on chat_send (High #3)
- Add attachment_queries.go with GetAttachmentByID, LinkAttachmentsToMessage,
  and GetAttachmentsByMessageIDs
- Wire attachment linking in handleChatSend with ATTACH_FILES permission check
- Include linked attachments in chat_message WS broadcast payload
- Wire attachment batch-fetch into GetMessagesForAPI for REST responses
- Add attachments table to channel handler test schema
2026-03-16 17:00:09 +01:00
jevb b2bfe5593c feat: align REST responses with API.md spec (High #1)
- Add MessageAPIResponse, UserPublic, AttachmentInfo, ReactionInfo types
- Add GetMessagesForAPI query with user object, reactions (with me flag),
  and attachments array matching API.md shape
- Update SearchMessages to return user object {id, username, avatar}
  instead of flat username field
- Update GET /messages handler to use new API-shaped query
- Batch-fetch reactions for all messages in a single query for performance
2026-03-16 16:57:01 +01:00
jevb 54221e8c07 fix: resolve 4 Critical + 2 High + 1 Medium server protocol violations
- Critical #1: Add READ_MESSAGES permission checks to channel_focus,
  GET /channels, GET /messages, and GET /search
- Critical #2: Send type "auth_error" instead of "error" with AUTH_ERROR
  code, preventing infinite client reconnect loops
- Critical #3: Replace role_id (number) with role (string name) in
  member_join, auth_ok, and ready payloads via JOIN on roles table
- Critical #4: Always include attachments field (empty array) in
  chat_message broadcasts to prevent client crash
- High #2: Add /api/v1/health endpoint alongside /health
- Medium #1: Handle ping WS messages with pong response
2026-03-16 16:54:56 +01:00