Commit Graph
126 Commits
Author SHA1 Message Date
J3vb 65002d0d7f h 2026-04-07 10:13:47 +02:00
J3vb 34c8e12d0f fix: preserve isKeyHolder in pendingJoin drain loop
PendingVoiceJoin was missing the isKeyHolder field, so when the drain
loop called connectAndSetup for a queued join it defaulted to false,
entering the "wait for room key from key holder" E2EE path and hanging
indefinitely. Store isKeyHolder in the pending join and forward it.
2026-04-05 20:41:04 +02:00
J3vb e66cb7413b fix: update test mocks for createLogger, ExternalE2EEKeyProvider, and keybinds string
- e2eeCrypto.test: mock createLogger instead of log
- livekit-session.test: add ExternalE2EEKeyProvider to livekit-client mock
- keybinds-tab.test: update expected string to "Press a supported key..."
2026-04-05 19:57:01 +02:00
J3vb 6e4a007b91 refactor: migrate WS handlers to V2 Command/Event architecture
Strangler-fig migration of 15 WebSocket handlers from V1 (Hub method,
*Client) to V2 (pure functions: Command, ClientInfo, deps -> Result).
V2 handlers are testable without a running Hub and produce declarative
Result values that the dispatch loop applies.

New abstractions:
- Command interface + typed constructors with input validation
- 7 Event routing interfaces (Channel, ExcludeSender, SequencedDM,
  UserTargeted, BroadcastAll, VoiceChannel, VoiceChannelGuarded)
- Per-domain deps structs (PingDeps, ChatDeps, PresenceDeps,
  ReactionDeps, VoiceDeps) with interface-based DI
- EmitEvents router matching events to delivery mechanisms
- DispatchV2 with panic recovery and runtime.Stack logging

Security hardening:
- Pre-sanitize byte length guard before bluemonday (DoS prevention)
- GetRoleForUser single-JOIN query avoids password hash on hot path
- channel_id positivity enforced in all command constructors
- Log injection prevention: msgType/reqID capped to 64 chars
- Nil KeyHolder dep returns ErrCodeInternal (not silent bypass)
- VoiceChannelGuardedEvent atomic check-and-send under h.mu.RLock

V1-only (complex state/mutex requirements): voice_join, voice_leave.

All tests pass with -race. No CI regressions expected.
2026-04-05 19:03:22 +02:00
J3vb 5239a75642 Merge pull request #1126 from J3vb/claude/security-audit-full-mmIrL
Force merge: CI issues will be fixed in upcoming Command/Event architecture refactor
2026-04-05 11:32:26 +02:00
J3vb f841ba87d7 fix: resolve tsc errors — logger import, Uint8Array generics, VoiceTokenPayload type, test assertion 2026-04-04 23:40:37 +02:00
J3vb 50bb54510f test: add e2eeCrypto unit tests — wrap/unwrap, fingerprint, entropy 2026-04-04 23:20:09 +02:00
J3vb f962d59cd6 fix: update tests and fix pending-join drain regression for CI
Update LiveKitSession tests to use _state discriminated union instead of
old flat field names (room, currentChannelId, latestToken, etc.) removed
in the state machine refactor. Also fix renderers.test.ts URL resolution
by setting a server host in beforeEach so isSafeUrl can parse relative
attachment URLs in jsdom. Stage all four Go test files so the CI Go job
runs them.

Additionally fix a regression in connectAndSetup's finally block: when a
pendingJoin is queued during a stale-join abort, preserve the connecting
state so handleVoiceToken's drain loop can pick it up rather than losing
it by resetting to idle.
2026-04-04 21:50:57 +02:00
J3vb 321428c7f4 feat: security hardening - restrict PTT capture keys, gate devtools to DEV, fix race conditions and DM sequencing
- Restrict PTT key capture to non-text keys only (function, navigation, mouse buttons) via allowlist (BUG-136)
- Gate DevTools button and F12/Ctrl+Shift+I shortcut behind import.meta.env.DEV
- Disable devtools Tauri feature in production (Cargo.toml default feature removed)
- Remove overly broad http:default capability, replace with scoped http:allow-fetch
- Set withGlobalTauri to false to avoid global __TAURI__ surface exposure
- Fix reconnect race: add abort checks after room creation, URL resolve, and connect (BUG-070)
- Fix ws.ts reconnect guard: bail out safely when config is null after disconnect
- Fix DM broadcast double-send and add monotonic seq + replay buffer support via sendSequencedToUsers
- Add seqMu mutex to serialize seq assignment across broadcastDM and deliverBroadcast paths
- Fix handleFreshConnect to unregister client and close connection on buildReady failure
- Add tests for PTT allowlist, ws reconnect config-null guard, livekit abort-after-connect, and DM sequencing
2026-04-04 09:08:01 +02:00
J3vb 1fccb1cb9f fix: format gif-provider.test.ts with Prettier 2026-04-03 16:59:10 +02:00
J3vb ae6ee0b06d fix: correct docker/setup-buildx-action SHA and update TenorGif to GifResult in tests
- Fix typo in setup-buildx-action pin (d8db...→d36ec...) that caused CI to fail
- Replace stale TenorGif type with GifResult in gif-picker.test.ts lines 531-532
2026-04-03 16:49:07 +02:00
J3vb 0a08fd72bd feat: migrate GIF picker from Tenor to Klipy
Tenor shuts down June 30, 2026. Klipy is a drop-in replacement built
by the ex-Tenor team, free for production use.

- Replace tenor.ts with gifProvider.ts (api.klipy.com/v2)
- CDN allowlist updated to *.klipy.com (static.klipy.com is the real CDN)
- Add Klipy watermark logo to sent GIFs in chat (bottom-left)
- Update attribution text to "Powered by Klipy"
- Wire VITE_KLIPY_API_KEY secret into release workflow
- Add .env to .gitignore to protect local API key
2026-04-03 13:58:40 +02:00
J3vb 4393bf1349 fix: remove duplicate setScreenshareAudioVolume in video-grid mock 2026-04-03 12:43:24 +02:00
J3vb 5b5fb708f3 feat: add server Linux support (#105)
PR#88 Added server linux support and changed workflow
2026-04-03 12:42:30 +02:00
J3vb 0b1d178d31 fix: wire parseChecksumFileAny for Linux checksum lookup and add missing mock export
- updater.go: DownloadAndVerify now uses parseChecksumFileAny with
  checksumEntryNamesForGOOS so the linux/ path prefix produced by the
  release workflow's sha256sum is found correctly (fixes
  TestDownloadAndVerify_Success on ubuntu-latest)
- video-grid.test.ts: add setScreenshareAudioVolume to the
  @lib/livekitSession mock so vitest does not throw on the export
  that VideoGrid.ts imports
2026-04-03 12:27:33 +02:00
J3vb f1eee8dfc3 fix: add setScreenshareAudioVolume to livekitSession mocks in video tests 2026-04-03 09:05:18 +02:00
J3vb 2762e9a4ef chore: remove soundboard feature entirely
Soundboard was never implemented — remove USE_SOUNDBOARD permission bit,
rate limiter, protocol entry, admin mockup reference, TODOS entry, and
all related test assertions.
2026-04-02 17:00:27 +02:00
J3vb 7cdc2ef1ca feat: broadcast username changes to all connected clients via WebSocket
Add user_update event so other clients see profile changes in real-time
without needing to reconnect. Also updates saved credentials in Windows
Credential Manager when the current user changes their username.

Fixes: livekit-session test mock missing unpublishTrack property.
2026-04-02 16:33:11 +02:00
J3vb 9f74710d91 fix: send invite code instead of numeric ID for revoke endpoint
The client was sending DELETE /api/v1/invites/{id} with a numeric database
ID, but the server expects the invite code string. This caused a 404 since
no invite has a code matching a numeric ID.
2026-04-02 15:41:25 +02:00
J3vb e9543ce6d6 fix: video grid auto-opens only for local camera/screenshare (BUG-105)
Refine auto-open to match Discord behavior: grid opens automatically
when the local user enables camera or screenshare, but NOT when remote
users do. Remote video requires clicking the user row to open.
2026-04-02 13:42:00 +02:00
J3vb 1622e04cee fix: auto-open video grid on camera/screenshare and harden CI supply chain (BUG-105, BUG-139)
BUG-105: checkVideoMode now auto-opens the video grid when any video
stream (local or remote camera/screenshare) becomes active. Previously
tiles were added to a hidden grid container.

BUG-139: All GitHub Actions pinned to commit SHAs instead of mutable
tags. Tool installs (govulncheck, tauri-typegen, cargo-audit) pinned
to specific versions instead of @latest.
2026-04-02 13:39:13 +02:00
J3vb f62d7e318b fix: stop leaked camera/screen tracks on reconnect (BUG-098)
teardownForReconnect only cleaned up audio pipeline and token timer,
leaving manual camera/screenshare MediaStreamTracks capturing
indefinitely after unexpected disconnect. Added stopManualCameraTrack
and stopManualScreenTracks calls before room is nulled, plus store
flag resets so the UI reflects the actual state.
2026-04-02 12:55:51 +02:00
J3vb 839b07e8c6 fix: show notification banner on TOFU first-use cert trust (BUG-133)
The cert-tofu event listener now handles "trusted_first_use" status
and shows a visible notification banner with the server hostname and
SHA-256 fingerprint. Adds onCertFirstTrust callback to the WS client
API. First-use certificate trust is no longer silent.
2026-04-02 12:47:07 +02:00
jevb 7924070531 fix: remediate low-signal test assertions in client tests (BUG-061, BUG-064)
- audio-pipeline.test.ts: replace no-op assertions with state checks
- device-manager.test.ts: replace toBeDefined/not.toThrow with actual
  value and behavior assertions
- livekit-session.test.ts: replace not.toHaveBeenCalled with state
  verification and return value checks
2026-04-01 18:22:09 +02:00
jevb 09da7db0d3 fix: improve native E2E reliability (BUG-059)
- Increase CDP bootstrap timeout to 60s with exponential backoff
- Replace waitForLoadState("networkidle") with element-based readiness
- Replace fixed waitForTimeout sleeps with waitForSelector/toBeVisible
- Convert data-dependent skips to conditional test.skip with messages
- Add shared helpers for countTextChannels/countVoiceChannels
- Increase login timeout to 60s for rate-limited servers
2026-04-01 18:20:44 +02:00
jevb 6f59fb85af fix: avoid removing video tile on temporary track mute
Track mute events can fire during network blips or SFU layer switching.
Previously this removed the tile entirely, requiring a new TrackSubscribed
event to restore it. Now mute adds a CSS class (track-muted) and unmute
removes it, while only the ended event triggers tile removal.
2026-04-01 17:12:13 +02:00
jevb 8f45307ec5 feat: add stream preview, video grid track lifecycle, and build cleanup
- Integrate stream preview into VoiceChannel sidebar for remote users
  with active camera/screenshare
- Add track lifecycle listeners (ended/mute) to VideoGrid to auto-remove
  stale black tiles
- Call video.play() explicitly for WebView2 autoplay compatibility
- Prevent redundant voice join when already in channel (ChannelSidebar)
- Add attachScrollCollapse for preview cleanup on scroll
- Remove tauri_typegen from build.rs
- Add Server/server.exe to gitignore
- Add stream-preview and video-mode-controller test coverage
2026-04-01 17:07:02 +02:00
jevb b1d633029c fix: resolve remote video streams not displaying due to identity format mismatch
Server generates LiveKit participant identities as "user-{id}:{voiceJoinToken}"
but parseUserId regex required exact "user-{id}" (with $ anchor), returning 0
for all remote participants. This caused the userId > 0 guard in
handleTrackSubscribed to silently drop all remote video callbacks.

- Update parseUserId regex to accept both "user-{id}" and "user-{id}:{token}"
- Fix getRemoteVideoStream to iterate remoteParticipants instead of exact
  identity lookup (which also failed due to the token suffix)
- Add test cases for token-suffixed identities
- Fix pre-existing noUncheckedIndexedAccess TS errors in test files
2026-04-01 17:05:57 +02:00
jevb 39f544f8f1 fix: block audio from new participants when locally deafened
The deafen guard in applyRemoteAudioSubscriptionState only unsubscribed
participants already in the room. Participants joining after deafen had
their audio unconditionally attached. Added a guard at the top of
handleTrackSubscribedAudio that checks localDeafened and calls
publication.setSubscribed(false) before any audio element is created.
2026-04-01 16:38:36 +02:00
jevb ade414bedd fix: handle IPv6 loopback (::1) in LiveKit URL resolution
The previous implementation used serverHost.split(":")[0] to extract
the hostname, which fails for IPv6 addresses — "::1:7880".split(":")[0]
yields "" instead of "::1". Now handles three formats:

- Bracketed: [::1]:7880 → host = "::1"
- Bare IPv6: ::1 → host = "::1" (multiple colons detected)
- IPv4/hostname: example.com:443 → host = "example.com"

Also fixes ensureLiveKitProxy to wrap bare IPv6 in brackets and
correctly detect port presence in bracketed notation.
2026-04-01 15:17:09 +02:00
jevb 74b189fc49 test: add 76 mutation-killing tests for livekitSession (T-451)
Target the #1 weakest file (25.88% mutation score, 170 survivors).
New tests cover connection lifecycle and state/facade methods:

- resolveLiveKitUrl: 8 tests (localhost/remote/proxy/directUrl paths)
- ensureLiveKitProxy: 3 tests (caching, port appending, null guard)
- connectAndSetup: 5 tests (retry logic, stale join, device fallback)
- handleVoiceToken: 2 tests (refresh shortcut, pending drain)
- attemptAutoReconnect: 5 tests (abort, channel change, retry, cleanup)
- Token refresh: 5 tests (timer fire, null guards, clear prevention)
- leaveVoice: 8 tests (cleanup assertions for every field/resource)
- cleanupAll: 3 tests (proxy stop, field nulling)
- setMuted/setDeafened: 5 tests (room interaction, deafen+mute combos)
- retryMicPermission: 5 tests (no-op, success, noise suppressor, failure)
- restoreLocalVoiceState: 7 tests (reconnect vs join error paths)
- Delegation + singleton: 12 tests (facade verification)

Notable: IPv6 ::1 detection bug found in resolveLiveKitUrl (documented).
2026-04-01 15:14:20 +02:00
jevb 6518c5b426 fix: resolve Stryker TypeScript checker errors in test files
Add non-null assertions to mock .calls[0] access in notifications tests
and mockListen implementation reference in ws tests. Required by
Stryker's stricter TS checker vs vitest runtime.
2026-04-01 14:59:04 +02:00
jevb bf2b6f23d0 test: add 112 mutation-killing tests for ws, notifications, audioPipeline
Stryker mutation testing identified 262 surviving mutants across these
three files. New tests target boundary conditions, boolean negations,
arithmetic operators, guard clauses, and state transitions to kill
surviving mutants and improve mutation score.

- ws.ts: 50 new tests (dedup, reconnect delay, cert TOFU, generation guards)
- notifications.ts: 32 new tests (sanitization, @everyone, sound params, permissions)
- audioPipeline.ts: 61 new tests (VAD thresholds, gain smoothing, frame counters)
2026-04-01 14:32:38 +02:00
jevb c53035a4aa fix: resolve TypeScript strict mode type errors
Fix 5 typecheck errors caught by tsc strict mode:
- Remove soundboard_play reference from types.test.ts
- Use optional chaining on MountableComponent.destroy
- Add string fallback for ROLE_COLORS lookup
- Add missing Channel fields in screen-share test mock
- Add non-null assertion on mock.calls index
2026-04-01 11:51:07 +02:00
jevb 867041b094 test: client test updates — fix failures + align with security hardening
Update 111 test files to match security hardening changes:
- acceptInvalidCerts now conditional on allowSelfSigned
- Credential store no longer returns passwords over IPC
- File type validation uses strict MIME allowlist
- Search rate limiter timing adjustments
- Dispatcher cleanup mock additions
- Audio elements screenshare mute preservation

2962 tests passing across 110 test files.
2026-04-01 11:40:31 +02:00
jevb 4485c4f021 feat: wire UI features — status picker, screen share, profile popup, DM sidebar
Status Picker (T-061): Wire existing StatusPicker to UserBar with WS
presence updates. Disabled when offline.

Screen Share (T-024): Add share button to VoiceWidget controls with
cyan glow active state and aria-pressed toggle.

User Profile Popup (T-059): New 300px anchored popover with edge-flip
positioning, focus trap, and deleted user handling.

DM Profile Sidebar (T-062): New 340px right panel with slide-in
animation, local Note field, toggle via DM header click.

24 new unit tests across 4 test files.
2026-04-01 11:39:30 +02:00
jevb 6c9d436797 refactor: split large files under 800-line limit
livekitSession.ts 1190→799: extract screenShare.ts, livekitDiagnostics.ts,
roomEventHandlers.ts. Deduplicate connect+setup logic, remove dead methods.

SidebarArea.ts 919→747: extract DM rendering to SidebarDmSection.ts.

ChannelSidebar.ts 864→501: extract logical sections inline.

All existing tests updated and passing.
2026-04-01 11:39:01 +02:00
jevb 246127e911 security: gate acceptInvalidCerts on allowSelfSigned config
Fix 3 functions (verifyTotp, uploadFile, getHealth) that hardcoded
danger.acceptInvalidCerts: true unconditionally, bypassing the user's
allowSelfSigned setting. Now conditional like doFetch.

TOTP codes, file uploads, and health checks no longer skip TLS
verification unless the user explicitly opts into self-signed certs.
2026-04-01 11:38:46 +02:00
jevb dc35f8ea4b fix: client security hardening (19 fixes across Rust + TypeScript)
Addresses findings from comprehensive security review of the Tauri client:

Critical:
- Scope fs:allow-write-file from ** to $APPDATA/**,$APPLOG/**
- Validate server_url scheme (https://) in update_commands.rs

High:
- Change CRED_PERSIST_LOCAL_MACHINE to CRED_PERSIST_ENTERPRISE (per-user)
- Remove password from IPC response (#[serde(skip)] on CredentialData)
- Auto-login uses stored token instead of password
- Gate open_devtools behind #[cfg(feature = "devtools")] at registration
- Validate remote_host for CRLF/null in livekit_proxy
- Guard icons.ts innerHTML with runtime check
- Add file upload MIME type allowlist
- Clear pendingTotpPartialToken after use

Medium:
- Add sandbox attribute to YouTube iframes
- Remove image/svg+xml from SAFE_MIME_TYPES
- Strip trailing punctuation from linkified URLs
- Validate host format in api.ts setConfig
- Cap error messages at 200 chars (anti-phishing)
- Rate limit search requests (500ms interval)
- Validate Tenor GIF URLs against trusted origins
- Sanitize notification titles (control chars + length cap)
- Validate ptt_set_key vk_code range (1-254)
- Add host validation to store_cert_fingerprint

Docs:
- Add "Client Security Hardening" section to docs/security.md
2026-03-31 19:11:36 +02:00
jevbandClaude Opus 4.6 b36c030cac feat: LiveKit video grid improvements, voice state cleanup, and internal tooling
- Video grid: sync stream type attribute on updates, add screenshare data attribute
- Dispatcher: handle voice_token messages, improve video track event handling
- LiveKit session: add video track publication support
- Hub: stale client timeout cleanup, improved voice state management
- Voice join/leave: context propagation, better error handling
- Livekit webhook: structured event handling with room/participant data
- Server DB: voice query improvements, new test coverage
- WS integration tests: expanded coverage for voice and LiveKit flows
- Gitignore: add internal dev tools directory, owncord-server.exe

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 11:41:59 +02:00
jevb 4b28f0e28d feat: Discord-style video grid with fixed 16:9 aspect ratio
Replace CSS grid-template-columns with a JS layout calculator that
tries every column count and picks the arrangement maximising tile
area while preserving exact 16:9 ratio. ResizeObserver triggers
recalculation on container resize. Tests updated to exercise the
pure computeGridLayout function directly.
2026-03-30 20:13:34 +02:00
jevb 5d0af7c03e feat: sidebar stream preview + screenshare focus fix
- Add hover/focus preview for remote voice users' camera/screenshare
  streams in the voice channel sidebar. Preview reuses already-subscribed
  LiveKit tracks (zero bandwidth cost). Full-width 16:9 preview with
  neon border-glow treatment matching DESIGN.md.

- Fix screenshare focus bug: clicking a screensharing user now correctly
  focuses the screenshare tile (userId + SCREENSHARE_TILE_ID_OFFSET)
  instead of the camera tile.

- Extract SCREENSHARE_TILE_ID_OFFSET to shared lib/constants.ts (was
  duplicated in VideoModeController.ts and MainPage.ts).

- New lib/streamPreview.ts module: attachStreamPreview() with 300ms
  debounce, track renegotiation detection, autoplay failure handling,
  scroll collapse, keyboard accessibility (focusin/focusout), ARIA
  labels, and full AbortSignal cleanup.

- Placeholder shows "Join to preview" with click-to-join behavior.
  Live video preview is also clickable to watch the stream.

- 22 new tests (16 stream-preview + 6 channel-sidebar).
2026-03-30 19:12:36 +02:00
jevb 5c616d53fe test: fix 10 test quality bugs (BUG-058–067) and resolve 115 TS type errors
BUG-058: Unblock prod-build E2E — created tsconfig.build.json excluding
tests from the production build. Added typecheck/typecheck:build scripts.

BUG-059: Harden native E2E — CDP timeout 30→60s with exponential backoff,
config timeouts doubled (test 120s, action 30s, nav 45s, expect 15s).

BUG-060: Add 25 Rust unit tests across commands.rs, ws_proxy.rs,
livekit_proxy.rs, credentials.rs (was zero behavioral tests).

BUG-061/067: Add behavioral assertions to server coverage_boost_test.go —
GracefulStop verifies client count, channel_focus verifies no error sent.

BUG-062: Upgrade low-signal test assertions in livekit-session,
device-manager, channel-controller (no-op checks → state checks).

BUG-063: Consolidate native E2E skip gates into beforeEach blocks
(voice-controls 7→1 skip, channel-navigation 4→1 skip).

BUG-064: Add 9 integration tests for channel CRUD, member lifecycle,
DM open/close, and presence events.

BUG-065: Replace 3 fixed sleeps with condition-based waits in E2E specs.

BUG-066: Verified toast/audio tests already cleaned in prior session.

TypeScript: Fix 115 type errors across 21 test files — add non-null
assertions for strict indexing, fix mock typing (vi.fn<any>()), add
missing fields (color, version, deleted) to test fixtures.
2026-03-30 16:35:02 +02:00
jevb dc832b90a9 fix: add missing getFloatTimeDomainData mock to silence VAD timer errors
Two AnalyserNode mocks in audio-pipeline tests were missing the method,
causing unhandled TypeError when the VAD poll timer fired after test cleanup.
2026-03-30 14:23:31 +02:00
jevb 7203aad5b3 test: Phase 4 coverage — meaningful behavior tests push client to 95%
Add 231 behavior-verifying tests across 21 files. Every test validates
real user-facing behavior: message send/edit/reply, file upload with
size validation, voice join/leave/mute, DM interactions, keyboard
navigation, error handling. No padding tests.
Client coverage: 90.66% → 95.09% (2898 tests, 104 test files).
2026-03-30 14:20:05 +02:00
jevb 1ea21a325a test: Phase 3 coverage — 22 files bumped from 70-92% to 95-100%
Expand 19 existing test files with 434 new tests. All 22 target files
now at 95%+ coverage: dispatcher, ws, store, permissions, ConnectPage,
LoginForm, KeybindsTab, AdvancedTab, AppearanceTab, ChannelController,
messages.store, channels.store, notifications, embeds, context-menu,
renderers, content-parser, formatting, profiles, audioElements,
ui.store, dm.store. Client coverage: 87% → 90.66%.
2026-03-30 13:59:47 +02:00
jevb b2545c50e7 test: Phase 2 coverage — below-70% files now at 95-100%
Add 5 new test files and expand 5 existing ones (446 new tests).
Coverage jumps: api.ts 51→100%, connectionStats 35→100%,
deviceManager 47→100%, audioPipeline 47→99.6%, media.ts 66→99.4%,
ServerPanel 43→100%, AccessibilityTab 59→100%, OverlayManagers
56→100%, ChatHeader 65→100%, VoiceAudioTab 63→95.5%.
Client coverage: 81% → 87%.
2026-03-30 13:38:20 +02:00
jevb b9ab51014f test: Phase 1 coverage — 10 zero-coverage files now at 95-100%
Add 13 new test files (337 tests) covering all previously untested
client modules: toast, preferences, roles.store, logPersistence,
ChatArea, MemberPickerModal, SidebarArea, SidebarDmHelpers,
SidebarDmSection, SidebarMemberSection. Client coverage: 72% → 81%.
2026-03-30 13:21:29 +02:00
jevb b4f7ce9098 fix: settings tab bug fixes, expanded tests, and coverage improvements
Fix multiple bugs across settings tabs (AppearanceTab theme restoration,
AdvancedTab testability, LogsTab refresh, VoiceAudioTab device listing),
harden embeds/media/attachments with cache validation, add logPersistence
rotation logic, and add 8 new test files with expanded test cases for
existing tests. Brings client test coverage from ~68% to ~72%.
2026-03-30 12:42:31 +02:00
jevb e52b62482c fix: TypeScript build errors in embeds.ts and totp-settings test
- embeds.ts: non-null assert on validated octets array access
- embeds.ts: cast fetchOpts for Tauri-specific `danger` property
- totp-settings.test.ts: cast closest() result to HTMLElement for style access
2026-03-29 21:34:54 +02:00