- Add dual-stream calibration: phone sends both raw and cropped preview
frames during calibration so users can see what practice will look like
- Add "Adjust" button to modify existing manual calibration without
resetting to auto-detection first
- Hide calibration quad editor overlay when not in calibration mode
- Fix rotation buttons to update cropped preview immediately
- Add rate limiting (10fps) for cropped preview frames during calibration
- Fix multiple bugs preventing dual-stream mode from working:
- Don't mark calibration as complete during preview mode
- Don't stop detection loop when receiving preview calibration
- Sync refs properly in frame mode change effects
Also includes accumulated formatting and cleanup changes.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Apply code formatting across codebase
- Add new vision training boundary frames
- Update model configurations
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Major refactoring of worksheet parsing to use centralized state management:
New architecture:
- WorksheetParsingContext: React context provider for parsing state
- state-machine.ts: Typed reducer with actions for streaming lifecycle
- sse-parser.ts: Shared SSE parsing utility for OpenAI Responses API
- usePartialJsonParser.ts: Progressive JSON extraction during streaming
Streaming UI improvements:
- ParsingProgressOverlay: Dark overlay on photo tile during parsing
- ParsingProgressPanel: Collapsible reasoning text panel
- ProgressiveHighlightOverlay: Problem boxes light up as LLM parses
- New streaming API routes: /parse/stream and /parse-selected/stream
Bug fixes during testing:
- Fix TypeScript error: cast event.response for id access in sse-parser
- Fix reparse reasoning display: preserve "processing" status for reparse
- Fix concurrent parsing: revert previous attachment status when switching
- Fix problem count: track dispatched problems to prevent duplicates
Components updated to use context:
- SummaryClient: Wrapped with WorksheetParsingProvider
- OfflineWorkSection: Uses context instead of local streaming state
- PhotoViewerEditor: Uses context for coordinated parsing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add cancel button to gallery thumbnails when parsing in progress
- Add cancel button to fullscreen PhotoViewerEditor when parsing
- Add cancel button for re-parsing in progress (fullscreen view)
- Track reparsingPhotoId to show correct status per-photo in both views
- Gallery shows "Re-parsing..." badge on specific photo being re-parsed
- DELETE endpoint resets parsing status for immediate retry
Also includes codebase-wide formatting from biome.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove per-problem Exclude/Restore buttons from EditableProblemRow
- Add bulk "Exclude Selected" and "Restore Selected" buttons to selection toolbar
- Add toast notifications for approve success/failure
- Close viewer and refresh page after successful approve to show updated session
- Fix mutation to properly await res.json() before returning
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add active session item at top of history tab that opens observation modal
- Create useLiveSessionTimeEstimate hook for real-time WebSocket updates
- Extract shared time estimation logic to useSessionTimeEstimate hook
- Add subscribe-session-stats socket event for lightweight session updates
- Display live progress, accuracy, idle time, and estimated time remaining
- Add corner ribbon "In Progress" indicator with two-line layout
- Use inset box-shadow for border to avoid overlapping ribbon
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixed two critical bugs preventing ten-frames from rendering:
1. **Mastery mode not handled** (typstGenerator.ts:61)
- Code only checked for 'smart' | 'manual' modes
- Mastery mode fell into manual path, tried to use boolean flags that don't exist
- Resulted in all display options being `undefined`
- Fix: Check for both 'smart' OR 'mastery' modes (both use displayRules)
2. **Typst array membership syntax** (already fixed in previous commit)
- Used `(i in array)` which doesn't work in Typst
- Changed to `array.contains(i)`
Added comprehensive unit tests (tenFrames.test.ts):
- Problem analysis tests (regrouping detection)
- Display rule evaluation tests
- Full Typst template generation tests
- Mastery mode specific tests
- All 14 tests now passing
Added debug logging to trace display rules resolution:
- displayRules.ts: Shows rule evaluation per problem
- typstGenerator.ts: Shows enriched problems and Typst data
- Helps diagnose future issues
The issue was that mastery mode (which uses displayRules like smart mode)
was being treated as manual mode (which uses boolean flags), resulting in
undefined display options.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Recover all changes from stash including:
- Linter/formatter updates across codebase
- Settings permission updates for git checkout
This commit captures the complete state of work that was
stashed during the previous session's git operations.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Use full viewport (100%) with absolute positioning
- Deep purple gradient background with floating math symbols
- Medieval manuscript-style title card with gold ornaments
- Clickable setting cards with fancy active indicators (golden glow)
- All sizing in vh units for true responsiveness
- No scrolling, no clipping on any viewport size
- Add data attributes to all elements
- Add data attributes requirement to CLAUDE.md
Fixes checkbox clipping by making entire cards clickable.
Active state shown with golden background, border, shadow, checkmark, and corner decoration.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implements "Option A: Single Source of Truth" from type audit recommendations.
**Phase 1: Consolidate GameValidator**
- Remove redundant GameValidator re-declaration from SDK types
- SDK now properly re-exports GameValidator from validation types
- Eliminates confusion about which validator interface to use
**Phase 2: Eliminate Move Type Duplication**
- Remove duplicate game-specific move interfaces from validation/types.ts
- Add re-exports of game move types from their source modules
- Maintains single source of truth (game types) while providing convenient access
**Changes:**
- `src/lib/arcade/game-sdk/types.ts`: Import & re-export GameValidator instead of re-declaring
- `src/lib/arcade/validation/types.ts`: Replace duplicate move interfaces with re-exports
- `__tests__/room-realtime-updates.e2e.test.ts`: Fix socket-server import path
**Impact:**
- Zero new type errors introduced
- All existing functionality preserved
- Clear ownership: game types are source of truth
- Improved maintainability: changes in one place
**Verification:**
- TypeScript compilation: ✅ No new errors
- Server build: ✅ Successful
- All pre-existing errors unchanged (AbacusReact module resolution, etc.)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Updated all test files to use accessMode instead of isLocked field
- Fixed room-manager tests to reflect new access control schema
- Installed bcryptjs dependency for password hashing
- All access mode TypeScript compilation errors resolved
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**Problem:**
When users joined rooms, the arcade session was created with empty
activePlayers array, causing the game to start in single-player mode
even though multiple users had joined the room.
**Root Cause:**
Initial session creation in `join-arcade-session` handler set
`activePlayers: []` without fetching the actual room members' players.
**Solution:**
1. **Session Creation**: When creating initial session, fetch all room
members' active players using `getRoomPlayerIds()` and populate
`activePlayers` field.
2. **Dynamic Updates**: When members join room (`join-room` event) or
toggle players (`players-updated` event), update session's
`activePlayers` dynamically using new `updateSessionActivePlayers()`
function.
3. **Protection**: Only update `activePlayers` if game is in 'setup'
phase - prevents disrupting games in progress.
**Key Changes:**
- `socket-server.ts`: Import `getRoomPlayerIds`, use it to populate
activePlayers on session creation, update session when room membership
changes
- `session-manager.ts`: Add `updateSessionActivePlayers()` function to
safely update activePlayers during setup phase
- Test fixes: Updated test files to match new schema (roomId PRIMARY KEY)
**Testing:**
- Session correctly populated with all room members' players on creation
- New members' players added to session when they join (if in setup)
- Player toggle updates session activePlayers in real-time
- Games in progress protected from activePlayers modifications
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**Problem:**
- player-ownership.ts imported drizzle-orm and @/db at top level
- When RoomMemoryPairsProvider imported client-safe utilities, Webpack bundled ALL imports including database code
- This caused hydration error: "The 'original' argument must be of type Function"
- Node.js util.promisify was being called in browser context
**Solution:**
1. Created player-ownership.client.ts with ONLY client-safe utilities
- No database imports
- Safe to import from 'use client' components
- Contains: buildPlayerOwnershipFromRoomData(), buildPlayerMetadata(), helper functions
2. Updated player-ownership.ts to re-export client utilities and add server-only functions
- Re-exports everything from .client.ts
- Adds buildPlayerOwnershipMap() (async, database-backed)
- Safe to import from server components/API routes
3. Updated RoomMemoryPairsProvider to import from .client.ts
**Result:**
- No more hydration errors on /arcade/room
- Client bundle doesn't include database code
- Server code can still use both client and server utilities
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The room member real-time update bug was caused by module isolation when API
routes dynamically imported socket-server.ts. Each import created a separate
module instance where the `io` variable was null, preventing broadcasts.
Root cause:
- API routes called getSocketIO() via dynamic import
- Dynamic imports created separate module instances
- The module-level `io` variable was never initialized in these instances
- Broadcasts from API routes never reached connected clients
The fix:
- Store socket.io instance in globalThis.__socketIO instead of module variable
- Ensures same instance accessible across all module boundaries
- API routes can now successfully broadcast to connected clients
Changes:
- socket-server.ts: Use globalThis.__socketIO for cross-module access
- src/lib/socket-io.ts: Clean up debug logging
- src/app/api/arcade/rooms/[roomId]/join/route.ts: Clean up debug logging
- __tests__/room-realtime-updates.e2e.test.ts: Add comprehensive e2e tests
- socket-server.js: DELETED (outdated, missing room handlers)
Tests verify:
1. member-joined broadcasts when users join via API
2. member-left broadcasts when users leave
3. Both members and players lists update correctly
All 3 e2e tests passing. User confirmed fix works in real app.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement hybrid database + application-level enforcement to ensure users
can only be in one room at a time, with graceful auto-leave behavior and
clear error messaging.
## Changes
### Database Layer
- Add unique index on `room_members.user_id` to enforce one room per user
- Migration includes cleanup of any existing duplicate memberships
- Constraint provides safety net if application logic fails
### Application Layer
- Auto-leave logic: when joining a room, automatically remove user from
all other rooms first
- Return `AutoLeaveResult` with metadata about rooms that were left
- Idempotent rejoining: rejoining the same room just updates status
### API Layer
- Join route returns auto-leave information in response
- Catches and handles constraint violations with 409 Conflict
- User-friendly error messages when conflicts occur
### Frontend
- Room list and detail pages handle ROOM_MEMBERSHIP_CONFLICT errors
- Show alerts when user needs to leave current room
- Refresh room list after conflicts to show current state
### Testing
- 7 integration tests for modal room behavior
- Tests cover: first join, auto-leave, rejoining, multi-user scenarios,
constraint enforcement, and metadata accuracy
- Updated existing unit tests for new return signature
## Technical Details
- `addRoomMember()` now returns `{ member, autoLeaveResult? }`
- Auto-leave happens before new room join, preventing race conditions
- Database unique constraint as ultimate safety net
- Socket events remain status-only (joining goes through API)
## Testing
- ✅ All modal room tests pass (7/7)
- ✅ All room API e2e tests pass (12/12)
- ✅ Format and lint checks pass
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixes critical bug where users were redirected to non-existent games
after room TTL deletion. This occurred because:
1. User creates arcade session in a room
2. Room expires via TTL cleanup
3. Session persists as orphan (roomId = null or points to deleted room)
4. useArcadeRedirect finds orphaned session
5. User redirected to /arcade/matching with no valid game state
Changes:
**Session validation (session-manager.ts)**
- getArcadeSession() now validates room association
- Auto-deletes sessions with no roomId
- Auto-deletes sessions pointing to non-existent rooms
- Returns undefined for orphaned sessions
**Session creation (session-manager.ts, route.ts, socket-server.ts)**
- createArcadeSession() now requires roomId parameter
- Socket server checks for existing user rooms before creating new ones
- Socket server auto-creates rooms when needed for backward compatibility
- API route requires roomId in request body
**Tests**
- Added orphaned-session-cleanup.test.ts: Unit/integration tests
- Added orphaned-session.e2e.test.ts: E2E regression tests
- Updated existing tests to provide roomId
- Tests cover TTL deletion, null roomId, and race conditions
This ensures sessions are always tied to valid rooms and prevents
orphaned session redirect loops.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Replace direct NODE_ENV assignments with Object.defineProperty
to avoid "Cannot assign to read-only property" TypeScript errors.
This allows tests to safely override the readonly NODE_ENV
environment variable for testing different environments.
Fixes 4 TS2540 errors.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Run Biome formatter on all files to ensure consistent code style:
- Single quotes for JS/TS
- Double quotes for JSX
- 2-space indentation
- 100 character line width
- Semicolons as needed
- ES5 trailing commas
This is the result of running: npx @biomejs/biome format . --write
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Prevents users from changing isActive status of players while they have
an active arcade session in progress. Returns 403 error with game info
when blocked.
- Added arcade session check in PATCH /api/players/[id] endpoint
- Enhanced error handling to surface server validation errors to users
- Added comprehensive E2E tests for validation behavior
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Security improvements:
- Add comprehensive e2e tests for userId injection attacks
- Explicitly strip userId from abacus-settings PATCH request body
- Add security comments to player update routes
- Tests verify foreign key and unique constraints prevent attacks
- Document that API layer security is critical (DB constraints insufficient)
Test coverage:
- 12 tests for abacus-settings API (including 3 security tests)
- 11 tests for players API (including 3 security tests)
- All 23 tests passing
Key findings documented in tests:
- Database foreign keys prevent invalid userId references
- Primary key constraints prevent duplicate userIds (abacus_settings)
- For players, userId CAN be changed to another valid userId at DB level
- API layer MUST filter userId from request body and use session-derived userId
- WHERE clauses scope all queries to current user's data
Defense in depth:
1. Session-derived userId (JWT cookie)
2. Explicit userId filtering from request body
3. WHERE clauses limiting scope to user's own data
4. Foreign key constraints (fallback)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Phase 2.2: API Routes
- POST /api/players - Create player
- GET /api/players - List user's players
- PATCH /api/players/[id] - Update player
- DELETE /api/players/[id] - Delete player
- GET /api/user-stats - Get user statistics
- PATCH /api/user-stats - Update user statistics
Technical details:
- Middleware passes guest ID via x-guest-id header for same-request access
- API routes use getViewerId() to identify guest/user sessions
- Automatic user record creation on first API access
- Full test coverage (16 tests passing)
- Manual API testing verified with curl
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>