Private
Public Access
Root cause (found earlier): the server only pushes to users it believes are "connected" to a room, but that was a raw WebSocket- connection check with no concept of whether the tab was actually foregrounded -- a backgrounded-but-still-connected tab looked exactly like someone actively watching, so the push got suppressed even though nothing could surface on a hidden page. No backend change needed: the server's presence tracking (and the push-suppression logic built on it) was already correct for "not joined to this room's channel" -- the gap was purely that the client never told it about backgrounding. useChatSocket.ts now tracks document.visibilityState and sends "leave" for every desired room when hidden (without forgetting the app still wants them joined), and "join" again when visible -- reusing the exact join/leave path a real room switch already goes through, no new WS message type or backend logic required. Rejoining also triggers a message-history refetch in ChatPane (keyed off the server's existing "joined" ack), so anything sent while backgrounded gets backfilled instead of silently missing -- as a side effect, this also fixes reconnect-after-a-dropped-connection never backfilling either, which had the same gap. Verified end-to-end: simulated backgrounding in the browser and confirmed via direct Redis inspection that the room's presence hash (what push-suppression actually reads) goes empty on hide and repopulates with a message resync on show. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
DS Chat frontend (Phase 1)
React + Vite PWA. Login, room list, and chat views wired to the backend's
REST API and /ws/chat WebSocket endpoint. See ../README.md
and ../backend/README.md for full local setup.
Dev
npm install
npm run dev
The dev server proxies /api and /ws to http://localhost:8000 (see
vite.config.ts), so the backend must be running for anything beyond the
login page to work.
Build
npm run build
Generates the PWA manifest and service worker via vite-plugin-pwa into dist/.
Layout
src/
main.tsx, App.tsx routes: /login, /rooms, /rooms/:roomId
api/ fetch wrappers (client, auth, rooms)
ws/useChatSocket.ts WebSocket hook (join/send/receive)
context/AuthContext.tsx current-user state, hydrated via GET /api/auth/me
components/ ProtectedRoute, RoomListItem, MessageList, MessageInput
pages/ LoginPage, RoomListPage, ChatRoomPage