Add overflow: 'hidden' to card container to properly clip the abacus
SVG content within the rounded border radius. This prevents the SVG
from bleeding through at the corners.
Also remove temporary debug logging that was added in previous commit.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
## [4.68.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.67.1...v4.68.0) (2025-10-23)
### Features
* **arcade:** auto-create room when user has none ([ff88c3a](ff88c3a1b8))
* **card-sorting:** add CardPosition type and position syncing ([656f5a7](656f5a7838))
* **card-sorting:** add react-spring animations for real-time sync ([c367e0c](c367e0ceec))
* **card-sorting:** add updateCardPositions action to Provider ([f6ed4a2](f6ed4a27a2))
* **create-room:** replace hardcoded game grid with dynamic Radix Select dropdown ([83d0ba2](83d0ba26f5))
* **room-share:** add QR code button for easy mobile joining ([349290a](349290ac6a))
### Bug Fixes
* **arcade:** add automatic retry for version conflict rejections ([fbcde25](fbcde2505f))
* **arcade:** implement optimistic locking in session manager ([71fd66d](71fd66d96a))
* **card-sorting:** preserve card positions on pause/resume ([0d8af09](0d8af09517))
* **card-sorting:** prevent duplicate START_GAME moves on Play Again ([a0b14f8](a0b14f87e9))
* **complement-race:** prevent delivery move thrashing in steam sprint mode ([e1258ee](e1258ee041))
* **qr-button:** improve layout and z-index ([646a422](646a4228d0))
* **qr-button:** increase mini QR code size to 80px ([61ac737](61ac7378bd))
* **qr-button:** increase mini QR code to 84px ([3fae5ea](3fae5ea6fa))
* **qr-button:** make button square and increase QR size ([dc2d466](dc2d46663b))
* **qr-button:** match height of stacked buttons ([81f202d](81f202d215))
* **room-info:** hide Leave Room button when user is alone ([5927f61](5927f61c3c))
### Code Refactoring
* **card-sorting:** send complete card sequence instead of individual moves ([e4df843](e4df8432b9))
### Documentation
* add deployment verification guidelines to prevent false positives ([3d8da23](3d8da2348b))
* update workflow to require manual testing before commits ([0991796](0991796f1e))
Add temporary logging to diagnose why card positions aren't being
properly restored when pausing and resuming the game.
This will be reverted after diagnosing the issue.
## [4.68.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.67.1...v4.68.0) (2025-10-23)
### Features
* **arcade:** auto-create room when user has none ([ff88c3a](ff88c3a1b8))
* **card-sorting:** add CardPosition type and position syncing ([656f5a7](656f5a7838))
* **card-sorting:** add react-spring animations for real-time sync ([c367e0c](c367e0ceec))
* **card-sorting:** add updateCardPositions action to Provider ([f6ed4a2](f6ed4a27a2))
* **create-room:** replace hardcoded game grid with dynamic Radix Select dropdown ([83d0ba2](83d0ba26f5))
* **room-share:** add QR code button for easy mobile joining ([349290a](349290ac6a))
### Bug Fixes
* **arcade:** add automatic retry for version conflict rejections ([fbcde25](fbcde2505f))
* **arcade:** implement optimistic locking in session manager ([71fd66d](71fd66d96a))
* **card-sorting:** preserve card positions on pause/resume ([0d8af09](0d8af09517))
* **card-sorting:** prevent duplicate START_GAME moves on Play Again ([a0b14f8](a0b14f87e9))
* **complement-race:** prevent delivery move thrashing in steam sprint mode ([e1258ee](e1258ee041))
* **qr-button:** improve layout and z-index ([646a422](646a4228d0))
* **qr-button:** increase mini QR code size to 80px ([61ac737](61ac7378bd))
* **qr-button:** increase mini QR code to 84px ([3fae5ea](3fae5ea6fa))
* **qr-button:** make button square and increase QR size ([dc2d466](dc2d46663b))
* **qr-button:** match height of stacked buttons ([81f202d](81f202d215))
* **room-info:** hide Leave Room button when user is alone ([5927f61](5927f61c3c))
### Code Refactoring
* **card-sorting:** send complete card sequence instead of individual moves ([e4df843](e4df8432b9))
### Documentation
* add deployment verification guidelines to prevent false positives ([3d8da23](3d8da2348b))
* update workflow to require manual testing before commits ([0991796](0991796f1e))
Fix issue where clicking "Play Again" simultaneously on multiple windows
would generate different card sets on each window:
- Add guard in startGame() to prevent sending START_GAME if already in playing phase
- Add 500ms debounce check using gameStartTime to prevent rapid duplicate sends
- Add state.gamePhase and state.gameStartTime to startGame dependencies
Now only the first client to click "Play Again" will generate cards, and
all other clients will be blocked from sending duplicate START_GAME moves
within the debounce window, ensuring all windows see the same cards.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fix card positions getting randomized when pausing and resuming game:
- Add cardPositions to pausedGameState type in types.ts
- Save cardPositions in GO_TO_SETUP validator and Provider optimistic update
- Restore cardPositions in RESUME_GAME validator and Provider optimistic update
- Fix React hooks rule violation: move useSpring call before early return
Now when you go to setup and resume the game, all cards return to their
exact positions instead of being randomly scattered.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add smooth physics-based animations for card and arrow movements:
- Add @react-spring/web dependency
- Create AnimatedCard component with spring animations
- Create AnimatedArrow component with spring animations
- Add viewport resize handling with debounced isResizing flag
- Conditionally disable animations during drag and resize (immediate mode)
- Enable spring animations only for socket position updates
- Add throttled position syncing (100ms) during drag operations
- Convert all positioning to viewport percentages (0-100)
- Track viewport dimensions and reposition cards on resize
Cards and arrows now smoothly animate when positions update from other
connected windows, while maintaining instant feedback during local
drag operations and viewport resizes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add updateCardPositions action and optimistic update handler:
- Add updateCardPositions callback that sends UPDATE_CARD_POSITIONS moves
- Add optimistic update case for position updates
- Fix card shuffle bug: shuffle on client before sending to server
- Remove double-shuffle in optimistic START_GAME handler
- Clean up unnecessary dependencies in useCallback hooks
This enables throttled real-time position updates during drag operations
that sync across all connected browser windows.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add viewport-relative position tracking for card-sorting game:
- Add CardPosition interface (x, y as % of viewport, rotation, zIndex)
- Add UPDATE_CARD_POSITIONS move type
- Add validateUpdateCardPositions validator method
- Add cardPositions array to CardSortingState
This enables real-time card position syncing across browser windows
using percentage-based coordinates that work across different viewport sizes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Simplified architecture: instead of sending individual INSERT_CARD moves
sequentially (which caused race conditions), the client now owns the card
arrangement entirely and sends the complete final sequence in CHECK_SOLUTION.
Changes:
- PlayingPhaseDrag: Send full sequence when clicking "Done"
- Provider: Update checkSolution to accept optional finalSequence parameter
- Validator: Accept and use finalSequence in CHECK_SOLUTION validation
- Validator: Save finalSequence to placedCards for results display
- Remove sequential INSERT_CARD flow complexity
This eliminates:
- Sequential move processing
- Race conditions between INSERT_CARD moves
- Need for complex optimistic locking in card placement
- Version conflicts during rapid card insertion
The client-side spatial arrangement is the source of truth, sent once
to the server for validation and scoring.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
When a move is rejected due to a version conflict (optimistic locking),
automatically retry the move after a brief delay. This handles the case
where moves arrive faster than the database can commit them.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add version-based optimistic locking to prevent race conditions when
multiple moves arrive rapidly. The database update now checks that the
version hasn't changed since the move was validated, preventing moves
from overwriting each other with stale state.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add critical guidelines for verifying NAS deployments to .claude/CLAUDE.md
to prevent assuming deployment is complete just because the website is online.
**Key additions:**
- GitHub Actions success does not equal NAS deployment
- Always verify deployed commit SHA matches origin/main HEAD
- Commands for checking actual deployed commit
- Checklist for confirming deployment completion
- How to report deployment gaps clearly
**Prevents common mistake:**
Seeing https://abaci.one online and assuming new code is deployed
without verifying the commit SHA.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Change from width-based to height-based sizing (height: 100%)
- Button now takes full height of parent container
- Increase QR code from 52px to 72px to fill larger button
- Button remains square with aspectRatio: 1
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Set button width to 100% with aspectRatio 1 for perfect square
- Reduce padding to 4px for tighter fit
- Increase mini QR code from 40px to 52px for better visibility
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Stack room code and share link vertically on left
- Place square QR button on right, spanning both rows
- Show mini QR code (40px) in button instead of emoji
- Fix popover z-index to appear above dropdown menu (z: 10000)
- Reduce button padding for more compact appearance
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add a QR code sharing option alongside existing copy buttons.
When clicked, opens a popover with:
- QR code encoding the room's share URL
- "Scan to Join" heading
- Clickable copy button for the URL
Uses qrcode.react library with Radix UI popover component.
Button styled with orange gradient to differentiate from existing
blue link and purple code copy buttons.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Hide the "Leave Room" button when the user is the only member in the
room. Leaving when you're alone doesn't make conceptual sense - it
would just auto-create another empty room.
The button now only appears when roomData.members.length > 1.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
When users visit /arcade without an active room, automatically create
a new room named "My Room" with open access. This prevents the
navigation loop where users were stuck seeing "No active room found"
with a link back to /arcade.
This auto-creation happens in two scenarios:
- First time visiting /arcade
- After leaving a room
The room is created with:
- Name: "My Room" (neutral, not implying solo-only)
- Access mode: open (user can invite others)
- No game selected (user chooses from game selection screen)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added critical workflow change: Claude must STOP after passing pre-commit
checks and WAIT for user to manually test before committing/pushing.
This prevents auto-commits of code that passes linting but doesn't work.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Replaced the hardcoded 3-game grid with a beautiful, dynamic dropdown that:
- Automatically shows all games from getAvailableGames() registry
- No more manual updates needed when adding new games
- Card Sorting now appears in the modal (was missing before)
UI Improvements:
- Fancy Radix UI Select component with rich game cards
- Each game shows: large emoji icon, title, description, player count, difficulty
- Color-coded selection highlights matching game's brand color
- Optional game selection - users can "choose later" on game page
UX Enhancements:
- Smooth CSS scrolling with scroll-behavior: smooth
- Absolutely positioned scroll indicators (no jitter)
- Green ▲▼ arrows show when more content available
- Smart positioning with collision detection (never clips viewport)
- maxHeight: 50vh, collisionPadding: 30px for small screens
- Hover effects on scroll arrows and game cards
Technical:
- Uses react-spring animated components for polish
- Radix Select for accessibility and keyboard navigation
- Single source of truth: game registry manifest data
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed race condition where duplicate DELIVER_PASSENGER moves were sent
to the server when a car remained at a station across multiple frames.
Root cause: The cleanup effect was removing passengers from
pendingDeliveryRef before the optimistic state update propagated,
allowing the same delivery to be attempted multiple times.
Solution: Keep passengers in pendingDeliveryRef for the entire route
duration. Only clear on route change, not on state updates.
Changes:
- Add pendingDeliveryRef to track pending deliveries across frames
- Skip delivery if passenger already has pending request
- Remove premature cleanup of pendingDeliveryRef
- Add unit test to document the race condition
Fixes the console spam of "Move rejected: Player does not have this passenger"
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed TypeScript errors in transform interpolation by using correct react-spring
syntax: to([spring1, spring2, spring3], (a, b, c) => ...) instead of the
incorrect spring1.to((a, b, c) => ..., spring2, spring3) syntax.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Ghost trains now use react-spring animations to smoothly interpolate between
position updates (100ms intervals), eliminating the jerky/discrete movement.
Changes:
- Import useSpring, useSprings, and animated from @react-spring/web
- Convert locomotive and car positions to animated springs
- Use animated.g components for smooth transform interpolation
- Configure springs with tension:280, friction:60 for responsive smoothness
This provides buttery-smooth ghost train movement while receiving position
updates at 100ms intervals, fixing the "low resolution" appearance.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed three critical multiplayer issues:
1. **Fixed interval restart bug**: Position broadcast interval was constantly restarting
because useEffect depended on `compatibleState`, which changed on every position
update. Now uses stable dependencies (`multiplayerState.gamePhase`, etc.)
2. **Increased broadcast frequency**: Changed from 200ms (5 Hz) to 100ms (10 Hz)
for smoother ghost train movement during multiplayer races
3. **Fixed position reset on reload**: Client position now syncs from server's
authoritative position when browser reloads, preventing trains from resetting
to start of track
Additional fixes:
- Used refs for `sendMove` to prevent interval recreation
- Removed unused imports (useEffect from GhostTrain, SteamTrainJourney)
- Added strategic logging for position broadcast and reception
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Removed all existing debug logs and added focused logging to identify why ghost
trains only update when players stop moving.
Strategic logging added:
- [POS_BROADCAST] Logs when position broadcast interval starts/stops
- [POS_BROADCAST] Throttled logging of position broadcasts (>2% change or 5s interval)
- [POS_RECEIVED] Logs when position updates are received from other players (>2% change)
This will help identify if:
1. Position broadcasts are being sent continuously during movement
2. Position updates are being received from the server
3. Updates are being processed and applied to ghost train positions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed an issue where ghost trains only updated when players stopped moving.
Root cause: clientPosition in useEffect dependency array caused the
position broadcasting interval to restart on every position change,
creating gaps in broadcasts during continuous movement.
Solution:
- Use useRef to track latest clientPosition without triggering effect
- Keep ref synced with position via separate useEffect
- Read position from ref inside interval callback
- Remove clientPosition from broadcasting useEffect dependencies
Now positions broadcast smoothly every 200ms regardless of movement state.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Ghost train cars now individually adjust opacity based on proximity to local
train, reducing visual clutter when overlapping while maintaining clarity
when separated.
Changes:
- Calculate local train car positions array in SteamTrainJourney
- Pass positions to GhostTrain for overlap detection
- Rewrite GhostTrain to render locomotive and each car separately
- Each car calculates opacity independently (0.35 when <20% from any local car, 1.0 otherwise)
- Smooth 0.3s CSS transitions between opacity states
- Overlap threshold: 20% of track length
Benefits:
- Reduced clutter when trains overlap
- Clear visibility when trains separated
- Per-car granularity for mixed scenarios
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed ReferenceError where makeMove was undefined. The correct function
is sendMove from useArcadeSession, which requires playerId and userId
parameters in addition to the move type and data.
Changes:
- Changed makeMove to sendMove
- Added playerId and userId to the move object
- Added localPlayerId guard to prevent updates before player is identified
- Updated dependency array to include localPlayerId, viewerId, and sendMove
This fixes the runtime error preventing ghost trains from working.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Clients now broadcast their train position to server every 200ms,
allowing other clients to render ghost trains at correct locations.
Added UPDATE_POSITION move type and server-side validation to sync
client-calculated positions (from momentum physics) to server state.
This fixes the issue where ghost trains were rendering but invisible
because they all had position=0 from server.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Previously all ghost trains used the local player's trainPosition,
causing them to render at the same location (hidden behind local train).
Now each ghost train uses its own player.position from multiplayer state,
allowing them to be visible at different positions on the track.
Also added logging to show ghost train positions for debugging.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Previously used `firstActivePlayer` which could show the wrong player's
name/emoji on the local train in multiplayer sessions. Now explicitly
finds the local player using `isLocal` flag.
Also updated passenger filtering to only show passengers claimed by
the local player, not the first player in the list.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>