All notable changes to RushTalk are documented here. Format follows Keep a Changelog. Versioning follows Semantic Versioning.
- Steam OAuth (OpenID 2.0) — full handshake with
check_authenticationround-trip, SteamID64 extraction fromclaimed_id, profile lookup viaISteamUser/GetPlayerSummaries - Google OAuth 2.0 + PKCE (S256) — confidential-client flow with backend-mediated token exchange, OIDC nonce validation
- Tauri deep-link callback (
rushtalk://auth/callback) viatauri-plugin-deep-link; tokens land in the desktop app via custom URL scheme - Redis-backed OAuth state store with single-use semantics (GETDEL) and 10-minute TTL — prevents CSRF and replay
- Username derivation from provider display name with sanitisation + collision suffix;
provider_*fallback for guaranteed uniqueness - Email-collision guard: OAuth users don't silently merge with existing password accounts
OAUTH_DESKTOP_CALLBACK_URLandOAUTH_STEAM_CALLBACK_URLenv vars (with sane localhost defaults)- OAuth
initandcallbackHTTP handlers under/auth/oauth/:provider/{init,callback} - Frontend: "Continue with Google" / "Continue with Steam" buttons on login page;
/oauth/callbackroute handles deep-link tokens
- Friendships — full stack: domain entity (
Friendship,FriendshipStatus), Postgres repo with unique-violation mapping, application service with auto-accept on mutual request, HTTP handlers (POST/GET/DELETE /api/v1/friends,/accept,/block), WS events (friend_request,friend_accept,friend_remove), Svelte store with derivedacceptedFriends/incomingRequests/outgoingRequests,FriendsPanel.sveltemodal with tabs (All/Pending/Add), badge onUserPanelfor incoming requests - Message reactions — full stack: migration
004_reactionswith(message_id, user_id, emoji)PK,Reactionentity,ReactionRepositorywithAddIfMissing(ON CONFLICT DO NOTHING + RowsAffected dedup), HTTP handlers (GET/POST/DELETE /api/v1/messages/:id/reactions[/...]), WS events (reaction_add,reaction_remove),reactionsStorewith optimistic toggle + revert on API failure,MessageReactions.sveltesub-component with chips + inline picker (👍 ❤️ 😂 🎉 🚀 👀) - Typing indicators — frontend store with 5s TTL + GC tick, throttled
notify()(3s), animated label inChatPanel("X is typing…", "X and Y are typing…", "Several people are typing…")
- LiveKit Admin API integration — replaced stub
CreateRoom/DeleteRoomwith real Twirp HTTP calls tolivekit.RoomService, idempotent room creation,ws://→http://URL conversion for the admin endpoint - Admin token minting with
roomCreate/roomAdmin/roomListgrants - Voice room
Open/ClosePrometheus gauge wired inVoiceHandler
- Linear-interpolation resampler (
crates/rushtalk-audio/src/resampler.rs) — stateful, zero-alloc on hot path,is_identity()short-circuit at 48kHz; engages automatically when device's native rate isn't 48kHz (44.1k/96k/16k/etc.) build_input_configrewrite: 4-tier fallback (i16@48k → i16@any → format@48k → device default) — every supported rate now produces correct 48kHz mono i16 downstream- Device hot-swap watchdog — Rust thread polls
AudioEngine::capture_stream_errored()every 500ms; on stream error (mic unplugged, driver crash) emitsaudio:stream-error, callsrestart_capture()to rebuild with current default device, emitsaudio:stream-recoveredon success cpal::StreamErrorpropagation via sharedArc<AtomicBool>from err_fn intoCaptureEngine::stream_error- New events:
EVENT_AUDIO_STREAM_ERROR,EVENT_AUDIO_STREAM_RECOVEREDinrushtalk-protocol/events.rs
- Custom Prometheus metrics (
pkg/metrics):rushtalk_http_requests_total{method,route,status}— uses route template (not URL) to bound cardinalityrushtalk_http_request_duration_secondshistogramrushtalk_ws_connections_activegauge — wired to hub register/unregisterrushtalk_ws_events_published_total{op}counterrushtalk_voice_rooms_activegauge
- Structured audit logging (
pkg/audit) — fixed-schemaslogoutput withaudit:truetag for downstream filtering. Wired inKickMember,CreateRole,DeleteRole,AssignRoles,ChannelDelete,MessageDelete
- OpenAPI 3.0 spec (
apps/api/docs/openapi.yaml) — hand-written, 21 endpoints, schemas for all entities; served at/openapi.yamlvia//go:embed - Swagger UI at
/docs(HTML loadsswagger-ui-dist@5from unpkg — no Go dep added) - CONTRIBUTING.md — local setup, repo layout, conventions per language, test commands, commit/PR style, security disclosure note
- svelte-i18n integration with
enandpt-PTlocales (~150 keys acrossauth,oauth,voice,friends,chat,errors,sidebar,settings,audioToast,common) - Auto-detect: persisted choice in
localStorage→ browser locale (pt*→pt-PT) → English fallback setLocale()exposed for future language-picker UI- Components translated:
+layout.svelte,login,register,oauth/callback,FriendsPanel,ChatPanel(typing labels with interpolation/pluralisation),UserPanel,ChannelSidebar,SettingsModal(all 5 tabs) - Error boundary in root layout via
<svelte:boundary>with i18n-localised fallback UI and retry button - Toast notification system (
stores/toasts.ts+ToastContainer.svelte) —info/success/errorkinds, per-kind TTLs (errors sticky),update()for in-place morph without re-stack,fly+fliptransitions - Audio stream error → toast lifecycle: "reconnecting…" (info, sticky) → "reconnected" (success, auto-dismiss) on recovery, or escalates to "Microphone is unavailable. Plug a mic in or pick one in Settings." (error) after 8s without recovery — single toast morphs through states, no stack churn
- Go:
friendship/service_test.go(8 tests),auth/oauth/service_test.go(9 tests),infrastructure/livekit/client_test.go(URL conversion),interface/http/handler/reaction_test.go(validEmoji boundaries) - Frontend:
stores/friends.test.ts(6 tests),stores/typing.test.ts(4 tests),stores/reactions.test.ts(6 tests) - Rust:
resampler::tests(3 tests — identity, 44100→48000 upsample, multi-callback continuity) - Total test count: 25 Rust (was 22), 28 frontend (was 12), Go suites green across
auth,oauth,friendship,server,livekit,handler
auth.Service.issueTokenPair→ exportedIssueTokenPairso OAuth flow can finalise login through the same session/JWT bookkeeping pathHandlersstruct ininterface/http/router.goextended withFriendshipandReactionhandlersAudioEngine: new fieldscapture_stream_error(Arc) and methodscurrent_input_device(),capture_stream_errored(),restart_capture()CaptureEngine::into_stream→into_parts()returning bothStreamand the shared error flagtauri.conf.jsondeclaresdeep-linkplugin with schemerushtalktauri-plugin-deep-link = "2"added tosrc-tauri/Cargo.toml;deep-link:defaultcapability added
- 004_reactions —
message_reactionstable with(message_id, user_id, emoji)PK,(message_id, emoji)covering index, FK cascade tomessages/users
- Tauri auto-updater via
tauri-plugin-updater(minisign-signed GitHub releases) - GitHub Actions CI: Go / Rust / Vitest test pipelines
- GitHub Actions Release: multi-platform Tauri builds + Docker image push to GHCR
- Multi-stage
Dockerfilefor the Go API (distroless runtime, ~20 MB image) docker-compose.ymlfor full local dev stack (API + Postgres + Redis + MinIO + LiveKit + Prometheus + Grafana)- MinIO bucket auto-init service in docker-compose (
rushtalk-uploads) - Kubernetes manifests: Namespace, ConfigMap, Deployment + Service + HPA, Ingress (nginx + cert-manager TLS)
- Prometheus scrape config with K8s pod annotation discovery
deploy/k8s/secrets.example.yaml— template for all required K8s secrets- Root
Makefilewithdev-up/down,test,build,k8s-*,keys,migrate-*targets /metricsendpoint (Prometheus) on the Go API- Audio diagnostic counters: underrun events + PLC (Packet Loss Concealment) frames
- Diagnostics tab in Settings modal with 2-second auto-refresh
- WebSocket Hub context cancellation for clean shutdown
- SQL migrations (golang-migrate): all 16 tables across 3 migration files
ws.Hub.Run()now acceptscontext.Contextto support graceful shutdown- Voice command signature updated:
join_channelnow takeslivekit_urlandtoken
- Adaptive jitter buffer with EMA-based target depth (10–80 ms)
- Time-based audio mixer: 10 ms tick, per-user silence/PLC fallback, stale-user GC
- Thread priority boost for capture and playback audio threads
- Spin-yield capture loop (64-iter spin budget before sleep fallback)
- 5 ms cpal buffer requests (Fixed(240) samples) for both capture and playback
- Adaptive Opus FEC:
report_network_stats(loss_pct)wired to encoderset_packet_loss_perc - LiveKit NativeAudioSource queue reduced from 100 ms to 20 ms
- Tauri 2.0 desktop app with Svelte 5 frontend
- Rust audio engine: cpal capture, Opus encode/decode, LiveKit PCM pipeline, cpal playback
- LiveKit SFU integration (
livekit = "0.7") for voice rooms - Go backend (Echo framework): auth (JWT RS256), servers, channels, messages, voice, file uploads
- WebSocket Gateway (gorilla/websocket): presence, typing, message, voice-state events
- Discord-style UI: ServerSidebar, ChannelSidebar, ChatPanel, VoicePanel, UserPanel
- Gaming overlay: always-on-top transparent Tauri window, Shift+` global shortcut, drag-to-reposition
- Settings modal: Audio / Voice / Overlay tabs + Diagnostics
- Context menus: right-click channels and voice users
- Prometheus metrics via
/metrics - PostgreSQL repositories for all domain entities
- Redis session blocklist (JWT revocation)
- Rich presence / game detection via
sysinfo