- Remove unused card_revealed broadcast + 1s asyncio.sleep in swap handler
(client never handled this message, causing pure dead wait before game_state)
- Defer swap-out (opacity:0) on hand cards to onStart callback so overlay
covers the card before hiding it — eliminates visual gap for all players
- Defer heldCardFloating visibility hide to onStart — held card stays visible
until animation overlay replaces it
- Thread onStart callback through animateUnifiedSwap → _runUnifiedSwap
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. Fix IndexError in current_player() when player leaves mid-game
- remove_player() now adjusts current_player_index after popping
- current_player() has safety bounds check as defensive fallback
2. Fix AssertionError in StaticFiles catching WebSocket upgrades
- Wrap static file mount to reject non-HTTP requests gracefully
- Starlette's StaticFiles asserts scope["type"] == "http"
Both crashes were observed in production on 2026-02-28 during a
multi-player session. The IndexError cascaded into reconnection
attempts that hit the StaticFiles assertion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reveal face-down cards briefly (1s) before swap completes, using
client-side state diffing instead of a separate server message.
Local player reveals use existing card data; opponent reveals use
server-sent card_revealed as a fallback. Defers incoming game_state
updates during the reveal window to prevent overwrites.
Also update YOUR TURN badge to cyan with suit symbols.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Full-codebase commenting pass focused on the tricky, fragile, and
non-obvious spots: animation coordination flags in app.js, AI decision
safety checks in ai.py, scoring evaluation order in game.py, animation
engine magic numbers in card-animations.js, and server infrastructure
coupling in main.py/handlers.py/room.py. No logic changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Dark green felt background for game screen
- Status bar: dark brown bg with amber text instead of blue
- YOUR TURN badge: green bg with white text instead of bright gold
- 3s delay before hole-complete scoreboard overlay
- Dealer indicator changed from Ⓓ to (D)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace [esc][esc] quit with [q] quit globally (immediate on login,
confirmation prompt elsewhere)
- [esc] is now consistently "back": signup→login, lobby→log out (with
confirm), in-room host→leave (with confirm), in-room guest→leave
- Extract ConfirmScreen to shared screens/confirm.py
- Move dealer Ⓓ indicator to bottom-left corner of player box border
- Scoreboard now tags OUT (went out first) and ⭐ (lowest score)
- Send finisher_id and player id in round_over server message
- Room code moved inside in-room section with amber border
- Lobby title uses branded ⛳🏌️ GolfCards.club ♠♥♣♦
- Amber borders and dark green backgrounds on login/lobby containers
- Deck preview renders actual card-back shapes (▓▒▓/▒▓▒)
- Help/standings panels close only with [esc], hint updated
- Game footer: s[⇥]andings [h]elp on left, [q]uit on right
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Save JWT token to ~/.config/golfcards/session.json after login so
subsequent launches skip the login screen when the session is still
valid. A new splash screen shows the token check status (SUCCESS /
NONE FOUND / EXPIRED) before routing to lobby or login.
Also: move OUT indicator to player box bottom border, remove checkmark,
center scoreboard overlay, use alternating shade blocks (▓▒▓/▒▓▒) for
card backs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Periodic room cleanup now updates games_v2 status to 'abandoned'
- Server startup marks all orphaned active games as abandoned
- Prevents stale games from accumulating in the admin portal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rooms that sit idle (no player actions or CPU turns) for longer than
ROOM_IDLE_TIMEOUT_SECONDS (default 300s) are now automatically cleaned
up: CPU tasks cancelled, players notified with room_expired, WebSockets
closed, and room removed from memory.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Single Esc: goes back one step (signup→login, lobby→connect, room→lobby)
- Double Esc: still quits the app
- Footer bar shows [Esc] Back and [Esc][Esc] Quit hints on all screens
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add signup with invite code support, remove guest login
- Add quit confirmation (q), help screen (h), standings tab
- Unified footer: [h]elp [q]uit | action text | [tab] standings
- Amber card highlighting persists through entire initial flip phase
- Player box border only highlights on turn (green) or knock (red)
- Play area gold border only during player's actual turn
- Game end returns to lobby create/join instead of login screen
- Lobby reset_to_pre_room for replayability without reconnecting
- Dynamic opponent layout fits all in one row when terminal is wide enough
- Hole emoji (⛳) in status bar, branded title with suits on connect screen
- DECK label spacing, Hole terminology in scoreboard
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Lobby: collapsible Game Settings, House Rules, Deck Style sections
- Lobby: CPU profile picker via [+], random CPU via [?], remove via [-]
- Lobby: all settings (rounds, decks, flip mode, house rules, deck colors)
sent to server on start_game instead of hardcoded defaults
- Game: clickable cards (hand positions, deck, discard pile)
- Game: immediate visual feedback on initial card flips
- Game: action bar shows escaped keyboard hints (Keyboard: Choose [d]eck...)
- Game: play area uses fixed-width rounded box instead of horizontal lines
- Game: position numbers on card top-left corner (replacing ┌) on all states
- Game: deck color preview swatches next to style dropdown
- Fix opponent box height mismatch when match connectors present
- Rebrand to GolfCards.club
- Add spacing between status bar/opponents and above local hand
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Enables public beta signup metering: DAILY_OPEN_SIGNUPS env var controls
how many users can register without an invite code per day (0=disabled,
-1=unlimited, N=daily cap). Invite codes always bypass the limit.
Also adds per-IP signup throttling (DAILY_SIGNUPS_PER_IP, default 3/day)
and fail-closed rate limiting on auth endpoints when Redis is down.
Client dynamically fetches /api/auth/signup-info to show invite field
as optional with remaining slots when open signups are enabled.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update version across pyproject.toml, FastAPI app, HTML footers,
and V3.17 doc. Mark kicked-ball bug as resolved in docs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Base .golfer-container rule was after the mobile @media override,
clobbering it. Moved base rule before the media query. Landscape
gets -2px (snug), mobile gets 12px.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reduce golfer-container margin from 10px to 4px in landscape (2-row)
mode while keeping 10px on mobile. Swap bottom suits to checkerboard
pattern: club/diamond on top, heart/spade on bottom.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wrap golfer+ball in a positioned container so the ball is absolutely
anchored to the golfer's front foot, independent of inline flow/viewport.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rearrange golf ball SVG suits from single row to 2x2 grid
- Add even spacing between logo, golfer, and title in mobile view
- Remove row-gap between logo row and title row in landscape view
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The 749px media query was triggering at mid-range widths, collapsing
the two-row logo+title into a single line. Fix by:
- Using inline-grid on h1 for bulletproof two-row layout
- Lowering single-line breakpoint from 749px to 500px
- Widening lobby container to 550px for title to fit naturally
- Constraining game controls to 400px max-width
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Change .logo-row from inline-block to block so the golf ball logo
always left-aligns flush with the G, regardless of viewport width.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The h1 shrinks to its widest child (GolfCards.club), centers via
margin auto, and text-align left aligns both lines within it.
No breakpoint-dependent transforms needed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Logo and title naturally left-align within the centered lobby box.
Mobile (<480px) gets text-align center + inline display for single line.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cut lastPlayPause to 2s and increase shake interval by 80% (3s→5.4s)
so the draw/discard nudge feels less nagging.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>