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.
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.
- 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
- 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
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
- 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
Soundboard was never implemented — remove USE_SOUNDBOARD permission bit,
rate limiter, protocol entry, admin mockup reference, TODOS entry, and
all related test assertions.
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.
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.
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.
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.
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.
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.
- 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
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.
- 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
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
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.
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.
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.
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
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.
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.
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.
- 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>
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.
- 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).
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%.