Repository navigation
feat(frontend): Phase 10 — lobby, live game, focus view, admin - #20
Merged
Merged
Conversation
Replace the template's generic shadcn palette with a cardstock/ink/felt/ suit-red color system and a Fraunces+IBM Plex Sans type pairing, pulled from the actual subject (a card table with human and AI players) rather than a default accent. Every shadcn token name is preserved so no component needs touching. All text/background pairs verified against real WCAG contrast math; two dark-mode pairs that failed the first pass were corrected before landing. Claude-Session: https://claude.ai/code/session_016JtkVrWvSwm6k6wjA7Um3f
Add WS /rooms/{room_id}/ws, a real-time transport over the identical
subscription_guard/filter_for_subscriber pipeline GET /events already
uses -- visibility can never diverge between the two transports since
both call the same functions. Auth travels as a ?token= query param
(browsers can't set a websocket Authorization header); every refusal
(bad token, unknown room, denied subscription) closes with the same
code, preserving the "never leak existence" guarantee on the new
transport too. ?since= resumes exactly like the polling route's cursor.
9 new tests cover auth/admission, REST parity, both security-boundary
cases from the existing live-play suite, a genuine live-push case, and
reconnect/resume -- all using the real app and a real GameRunner, not
mocks.
Claude-Session: https://claude.ai/code/session_016JtkVrWvSwm6k6wjA7Um3f
…e 10) Build the real play/spectate/admin screens on top of the websocket transport: a room lobby (list/create/join-by-code), the live game table (real card faces for both card families, legal-move-driven play, Hearts' pass_cards multi-select), the privileged click-any-seat focus view with live AI reasoning, and a reducer-based live-state hook that folds the event stream into compact derived state instead of a raw growing array. Removes the template's Items demo entirely (route, components, spec, nav entry) -- not botpit's domain. Chat is deliberately not built here: it's Phase 11's job per PROGRESS.md, and there is no backend endpoint to send a chat message yet. A real two-human Playwright run against the real backend surfaced three genuine bugs, all fixed properly rather than worked around: - POST /rooms/join never returned the room's own id -- added a typed RoomSeatJoinPublic response_model. - The dev-only /private/users/ endpoint (used by every Playwright test) bypassed ensure_default_role, so every test account had zero permissions -- latent since Phase 6, only surfaced once a test exercised a permission-gated route through it. - CreateRoomDialog never seated the creator into any seat, so the first join-by-code claimed the creator's own intended seat instead. One deliberate motion moment each on the turn indicator and the reasoning-reveal (motion-safe:-gated, existing tw-animate-css vocabulary, no new dependency); two real accessibility fixes (unlabeled seat-kind/persona selects, placeholder-only join-code input). Claude-Session: https://claude.ai/code/session_01UbiPa1rKCyqhqufgwJWkeW
… PR #20 - Add GET /rooms/{room_id}/ws-ticket (30s Bearer-authed ticket) and wire useRoomSocket.ts to use it instead of putting the real, long-lived access_token in the websocket connection URL. - Reset RoomLiveState and the resume cursor whenever roomId itself changes, not just on unmount -- room-to-room navigation without a full remount previously leaked the old room's state and cursor. - Clamp CreateRoomDialog's seat count to the new game's maxSeats too, not just minSeats, when switching games. - Fix Hearts' pass-cards selection to key by card content and re-derive indices from the current hand at submit time, instead of trusting array indices that could point at different cards by the time of submission. - Rate limit WS /rooms/{room_id}/ws by client host (the CurrentUser-based rate_limit_from_setting dependency doesn't apply to this route's query-param auth). - Log a warning when ensure_default_role's role-missing no-op fires, so a real misconfiguration doesn't silently grant zero permissions to every new account with nothing pointing at the cause. - Widen since= validation to accept the bus's own bare "0" convention, not just a hyphenated stream_id. - Make join_room's reveal=True explicit. Both review agents also independently reported a SyntaxError in _current_user_ws, backed by claims of cross-verification via a fresh clone and GitHub's content API -- not acted on. GitHub's own CI log for this PR's actual head commit shows 771 tests collected and passed, which a real SyntaxError cannot produce; documented in PROGRESS.md. Claude-Session: https://claude.ai/code/session_01UbiPa1rKCyqhqufgwJWkeW
5 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 10: the real React frontend — play, spectate, admin/focus view — replacing the fastapi-template's Items demo scaffolding, built on top of a new websocket push transport.
WS /rooms/{room_id}/ws, a websocket push transport over the exact samesubscription_guard/filter_for_subscriberpipelineGET /eventsalready used — visibility can never diverge between the two transports.pass_cardsmulti-select), and the privileged click-any-seat focus view with live AI reasoning (DECISIONS.md §10). A reducer-based live-state hook folds the event stream into compact derived state rather than a raw growing array every consumer re-scans.PROGRESS.md, and there's no backend send endpoint yet.Real bugs found and fixed (via an actual two-human Playwright run against the real backend)
POST /rooms/joinnever returned the room's own id — added a typedRoomSeatJoinPublicresponse model instead of adictworkaround./private/users/endpoint (used by every Playwright test) bypassedensure_default_role— every Playwright-created test account had zero permissions, latent since Phase 6, only surfaced once a test exercised a permission-gated route through it.seatsVersionto the live-state reducer and an invalidation effect.CreateRoomDialognever seated the creator into any seat — fixed to seat them into the first human seat, perRoomService.create's own documented rule.Motion and accessibility
One deliberate motion moment each (turn-indicator re-announcement, reasoning-reveal),
motion-safe:-gated, using the project's existingtw-animate-cssvocabulary — no new dependency. Two real a11y fixes found by inspection: unlabeled seat-kind/personaSelecttriggers, and a placeholder-only join-code input (a known anti-pattern).Full detail, including the tool-output-integrity anomaly encountered and deliberately not acted on:
PROGRESS.md's Phase 10 section.Test plan
ruff check/ruff format --check/mypy app/ty check appcleanbun run build/bun run lintcleanGET /events)prek run --all-filescleanhttps://claude.ai/code/session_01UbiPa1rKCyqhqufgwJWkeW