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)
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
- BUG-039: switchOutputDevice continues loop on partial failure instead of early return
- BUG-040: clearOnError() prevents stale callback after MainPage destroy
- BUG-041: voice store tests cover localCamera, localScreenshare, setLocalSpeaking
- BUG-042: auth store updateUser tests and UserBar mute/deafen callback tests
- BUG-043: switchInputDevice guards against no active WebRTC session
- BUG-044: replace synchronous confirm() with double-click-to-delete via toast
- BUG-045: isSafeUrl() blocks javascript: URLs in image attachment src
- BUG-031: Add switchInputDevice/switchOutputDevice to voiceSession;
VoiceAudioTab now applies device changes to active WebRTC session.
- BUG-032: Closed as false positive — channel WS handlers already wired
in dispatcher.ts:173-200.
- BUG-033: Closed as false positive — member WS handlers already wired
in dispatcher.ts:219-229.
- BUG-034: Closed as false positive — InviteManager filter runs inside
.then(), not before promise resolves.
- BUG-035: DmSidebar click handler now toggles .active class on items.
- BUG-036: Add setOnError callback to voiceSession; MainPage wires it
to toast for WebRTC failure feedback.
- BUG-026: Replace placeholder div with <img src=att.url> element in
renderAttachment, with lazy loading and error fallback.
- BUG-030: Delete orphaned MessageActionsBar.ts and ReactionBar.ts
components (never imported) along with their tests.
- BUG-024: Toggle reaction_add/reaction_remove based on me field.
- BUG-028: Add confirm() guard before chat_delete.
- BUG-029: Validate edit content is non-empty and changed.
- BUG-037: Show error toast when reaction rate limited.
- BUG-038: Add success toasts for delete and edit operations.
- BUG-021: Camera toggle reads actual localCamera state from voice store
instead of hardcoded false.
- BUG-022: Screenshare toggle sends voice_screenshare WS message with
localScreenshare state tracking.
- BUG-023: UserBar mute/deafen buttons wired via UserBarOptions callbacks
passed from MainPage, with voice channel guard and rate limiting.
- BUG-027: VAD onSpeakingChange callback wired to setLocalSpeaking in
voice store for local speaking indicator feedback.
- Added localCamera, localScreenshare, setLocalSpeaking to voice store.
- BUG-020: Wire api.changePassword() and api.updateProfile() into
MainPage settings overlay callbacks. Add updateUser() to auth store
for username sync. Add toast feedback for success/error.
- BUG-025: Call setTheme() in AppearanceTab click handler so uiStore
stays in sync with localStorage and applied CSS.
- Update test mock to include setTheme export.
Add actionTimeout, navigationTimeout, local retry, video capture, and
reducedMotion to both Playwright configs. Introduce waitForWsReady(),
navigateToMainPageReady(), and emitWsMessageAndWait() helpers. Fix
voice-widget selectors to match actual DOM structure. Update
E2E-ISSUES.md to reflect 209/209 passing.
- 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
- 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
The candidate field was typed as string but the server's handleVoiceICE
parses it as webrtc.ICECandidateInit (an object with candidate, sdpMid,
sdpMLineIndex, usernameFragment fields). Corrected to RTCIceCandidateInit.
PROTOCOL.md voice_ice example updated locally (gitignored vault).
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.
- 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
- 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
- #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.
- 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)
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
Implement DOM windowing so only visible messages (plus 10-item overscan
buffer) are rendered. Uses estimated heights with measured-height cache,
top/bottom spacer elements, and rAF-debounced scroll updates.
- Extract rendering helpers to components/message-list/renderers.ts
- MessageList uses VirtualItem abstraction for messages + day dividers
- Height cache keyed by message ID for accurate scroll positioning
- Update test assertion for new virtual scroll DOM structure
Resolves TODOS.md #11.
- 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.
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
- Fix CSS classes across 8 components to match ui-mockup.html
(ReactionBar, VoiceChannel, EmojiPicker, DmSidebar, Toast,
ServerBanner, MessageActionsBar, MessageList)
- Rewrite MainPage to compose standalone components instead of
inline builders, with reactive channel switching
- Wire all outbound WS handlers: chat send/edit/delete, typing,
reactions, voice mute/deafen/disconnect
- Wire REST message loading with infinite scroll and abort on
channel switch
- Wire reconnect banner to WS state and server_restart events
- Add reaction_update, chat_send_ok, member_ban, voice_config,
voice_speakers dispatcher handlers
- Add updateReaction action in messages store
- Fix MessageList double-render bug when no code blocks present
- Fix membersStore subscription to skip re-render on typing events
- Add scroll-top debounce to prevent duplicate API calls
- Replace dead More button with functional Delete button
- Clear unread count on channel switch in channels store
- Add midnight theme, connectionStatus, error fields to UI store
- Add voiceConfigs state and setSpeakers action to voice store
- Update tests: 369 passing across 21 test files
Root cause: server's db.Role and db.VoiceState structs had no JSON tags,
causing Go to serialize field names as PascalCase while the C# client
expected snake_case. Every role deserialized with Id=0, crashing
ToDictionary with "duplicate key: 0".
- Add json tags to Role and VoiceState in Server/db/models.go
- Change Disconnected event to carry reason string for diagnostics
- Wire ErrorReceived in MainViewModel to show server-side WS errors
- Fix MainWindow to surface WebSocket errors on MainPage (not ConnectPage)
- Use _reconnectCts.Token for receive loop instead of caller's token
- Make ToDictionary calls safe with TryAdd to prevent future crashes
- Replace 5x ToList().FindIndex() with direct for-loops in MainViewModel
- UpdateUnreadCount now updates ChannelGroup in-place instead of full rebuild
- Remove redundant RebuildChannelGroups() call in OnReady
- Freeze all SolidColorBrush instances in converters for thread safety
- EmojiPicker search shows empty state instead of fallback to all categories
- MainViewModel implements IDisposable for _typingTimer cleanup
- ApiMessage.Username changed to string? to match server reality
- Redesign ConnectPage with modern dark theme, profile cards with delete buttons, login/register toggle
- Add DPAPI-encrypted password saving with "Remember my password" checkbox
- Fix permission bit constants to match SCHEMA.md (Member role 0x663)
- Add migration 004 to fix existing Member role permissions
- Add comprehensive audit logging across all server packages (auth, admin, ws, setup)
- Add member_join WebSocket broadcast so new users appear in members list in real-time
- Add host URL normalization (strip scheme prefix) for reverse proxy compatibility
- Add REST API client, ChatService orchestrator, WebSocket service with reconnection
- Add model types (WsEnvelope payloads, API responses), converters, tests