Phase 1: auth, room CRUD, WebSocket chat, PWA frontend

Invite-only FastAPI + SQLAlchemy(async) + Postgres backend (session-cookie
auth via CLI-provisioned accounts, open-room CRUD, single-instance /ws/chat)
and a React + Vite PWA frontend (login, room list, chat view). Backend tests
pass against a local Postgres DB. See README.md and backend/README.md for
setup, and ARCHITECTURE.md for the full phased design.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-13 20:01:17 -06:00
co-authored by Claude Sonnet 5
parent 8ac35062dc
commit 99aa029c0d
77 changed files with 9042 additions and 0 deletions
+52
View File
@@ -0,0 +1,52 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import type { ServerEnvelope } from '../types'
interface UseChatSocketOptions {
roomId: string
onMessage: (envelope: ServerEnvelope) => void
onUnauthenticated: () => void
}
export function useChatSocket({ roomId, onMessage, onUnauthenticated }: UseChatSocketOptions) {
const socketRef = useRef<WebSocket | null>(null)
const [connected, setConnected] = useState(false)
const onMessageRef = useRef(onMessage)
onMessageRef.current = onMessage
const onUnauthenticatedRef = useRef(onUnauthenticated)
onUnauthenticatedRef.current = onUnauthenticated
useEffect(() => {
const protocol = location.protocol === 'https:' ? 'wss' : 'ws'
const ws = new WebSocket(`${protocol}://${location.host}/ws/chat`)
socketRef.current = ws
ws.onopen = () => {
setConnected(true)
ws.send(JSON.stringify({ type: 'join', room_id: roomId }))
}
ws.onmessage = (event) => {
onMessageRef.current(JSON.parse(event.data) as ServerEnvelope)
}
ws.onclose = (event) => {
setConnected(false)
if (event.code === 4401) {
onUnauthenticatedRef.current()
}
}
return () => {
ws.close()
socketRef.current = null
}
}, [roomId])
const send = useCallback((content: string) => {
const ws = socketRef.current
if (!ws || ws.readyState !== WebSocket.OPEN) return
ws.send(JSON.stringify({ type: 'message', room_id: roomId, content }))
}, [roomId])
return { connected, send }
}