Push a live signal when a user is added to a room (#26)

Previously GET /api/rooms/mine was only ever fetched once at app mount,
so a room added mid-session stayed invisible until a full page reload
-- add_member had no way to reach an already-open client at all.

Backend: ConnectionManager and Broadcaster (renamed from RoomBroadcaster)
now support per-user channels alongside the existing per-room ones, so a
signal can reach a user's socket even for a room they haven't joined
(and by definition can't have, until this fires). add_member publishes
a room_added event on the target user's channel.

Frontend: the WebSocket connection is no longer scoped to whichever
room is open -- ChatShellPage now owns one persistent connection for
the whole session (including while no room is open, which is exactly
when this bug showed), and ChatPane joins/leaves rooms on top of it.
A room_added event triggers a room-list refetch with no reload needed.

Verified end-to-end in the browser: a user sitting on the empty room
list saw a newly-added room appear live, then chatted in it normally.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-16 10:29:06 -06:00
co-authored by Claude Sonnet 5
parent c9d61c3d12
commit 7dcc7104df
11 changed files with 222 additions and 64 deletions
+2
View File
@@ -34,6 +34,7 @@ from app.schemas.webhook import (
WebhookIncomingCreate,
WebhookIncomingRead,
)
from app.services.message_events import broadcast_room_added
from app.services.message_service import get_reactions_for_messages, list_recent_messages
from app.services.upload_settings_service import format_mb, get_upload_settings
from app.services.room_service import (
@@ -466,6 +467,7 @@ async def add_member_endpoint(
raise HTTPException(status_code=404, detail="No user with that ID")
except AlreadyMemberError:
raise HTTPException(status_code=409, detail="That user is already a member")
await broadcast_room_added(request.app.state.broadcaster, data.user_id, room)
return RoomMemberRead(
user_id=membership.user_id,
username=membership.user.username,