- Remove unused `and` import from VisionRecorder.ts
- Remove unused `IncomingMessage` and `ws` imports from socket-server.ts
- Add `muted` attribute to video element in ProblemVideoPlayer
- Apply code formatting across vision and practice components
- Update documentation formatting in DEPLOYMENT.md and README
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The useInteractionPhase effect now handles two cases:
1. activeProblem.key changes (redo mode, navigation, session advances)
2. Phase becomes 'loading' (after incorrect answer or part transition)
Previously, only key changes triggered problem loading, which caused
"Loading next problem..." to persist after incorrect answers since
clearToLoading() sets phase to 'loading' without changing the key.
Also adds debug logging for part transitions and game breaks.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tests cover:
- useCreatePlayer optimistic updates to list() and listWithSkillData()
- Graceful degradation when listWithSkillData isn't cached
- Rollback on server errors and network failures
- Cache invalidation on success/error
- API integration (correct endpoint, request body)
- useUpdatePlayer optimistic updates and rollback
- useDeletePlayer optimistic delete and rollback
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When creating a new player, optimistically update both the player list
and the listWithSkillData query (used by /practice page). This makes
new players appear immediately without requiring a page reload.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
useCreatePlayer and useDeletePlayer were only invalidating
playerKeys.lists() which doesn't include listWithSkillData().
The practice page uses usePlayersWithSkillData() so newly created
players weren't appearing until page reload.
Now invalidates playerKeys.all to catch all player-related queries.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Instead of hiding the "Enable Vision" button when setup isn't complete,
show it disabled with an explanation of what's needed:
- "Waiting for camera access..." for local camera
- "Connect your phone to enable vision" for phone camera
This provides better UX feedback about what the user needs to do.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When loading vision config from localStorage, infer activeCameraSource
if it's null but cameraDeviceId or remoteCameraSessionId is set.
This fixes a bug where the "Enable Vision" button wouldn't appear
until the user clicked "This Device" or "Phone Camera", even if
they had previously configured vision before activeCameraSource
was added to the config schema.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add /api/health endpoint that checks database connectivity
- Set up blue-green deployment with two containers (abaci-blue, abaci-green)
- Add docker-compose.yaml with YAML anchors for DRY config
- Add generate-compose.sh to create blue/green compose files from main
- Update deploy.sh with NAS-specific fixes (scp -O, PATH for docker)
- Fix deploy.sh to not overwrite production .env by default
The blue-green setup allows zero-downtime deployments via compose-updater,
which watches separate compose files and restarts containers independently.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change from automatic redirect to persistent banner with link
- Authenticated observation: show "session ended" banner with link to report
- Public observation: show banner with link if user has view access
- Remove auto-redirect behavior per user feedback
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Authenticated observation page: redirect to session report if completed
- Public observation page: redirect to session report if user has view access
- Show friendly "session ended" page for unauthenticated users on public links
- Prevents 404 for ended sessions when user has access to view the report
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ffmpeg to Docker runtime dependencies for encoding vision frames to MP4
- Fix VisionRecorder to mark videos as 'failed' (not 'ready') when ffmpeg unavailable
- Add lazy encoding: when video requested but MP4 missing, encode frames on-demand
- Returns 202 with retryAfterMs so client knows to poll for completion
- Add link to vision markers page from /create hub
- Various vision recording improvements from plan mode work
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add activeProblem computed value that derives from redoState + currentProblemInfo
- Modify useInteractionPhase to accept activeProblem prop and react to key changes
- Remove synchronization effects that caused redo mode bugs
- Simplify SessionProgressIndicator to compute attempt counts from results directly
- Remove plan prop dependency - both student and observer views now use same code path
This eliminates the dual source of truth (redoState vs phase.attempt.problem) that
was causing synchronization bugs when entering/switching/exiting redo mode.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
These files were created but never committed:
- VisionDvrControls.tsx - DVR-style playback controls
- VisionRecordingPlayer.tsx - video player component
- useSessionRecording.ts - recording data fetching hook
- cleanupVisionRecordings.ts - cleanup script
- ObserverVisionFeed.stories.tsx - storybook stories
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The TypeScript schema file was never committed, causing build failures.
The migration (0067) that creates the table was already committed.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add visual debug panels that show socket connection state when the
"visual debug" toggle is enabled in the app nav:
- BroadcastDebugPanel: Shows on student practice page with socket
connection status, broadcast state, recording status, and current
problem info
- ObserverDebugPanel: Shows on observer page with connection status,
observed state, vision frame info, DVR buffer info, and error messages
Both panels are fixed at bottom-left, collapsible, and include
JSON copy buttons for easy bug reporting.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When socket.io-client reuses an existing Manager that's already connected,
the 'connect' event doesn't fire for new Socket instances. This caused
session broadcasting and observation to silently fail until the old
connection timed out (~45 seconds).
Fix: After attaching the 'connect' handler, check if socket.connected is
already true and manually trigger the connect logic.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add epoch/attempt tracking to vision_problem_videos schema (epochNumber, attemptNumber, isRetry, isManualRedo)
- Update VisionRecorder filename pattern to include epoch/attempt: problem_NNN_eX_aY.mp4
- Fix getRetryContext to use correct slot indices during manual redos
- Add answer-submitted marker for redo problems via handleRecordRedo wrapper
- Create attempt-tracking.ts utility for video attempt grouping and labeling
- Add attempt selector dropdown to ProblemVideoPlayer when multiple attempts exist
- Update API routes to accept epoch/attempt query params for video and metadata
- Add comprehensive documentation for vision recording system (README.md)
Multiple attempts at the same problem (from epoch retry rounds or manual redo clicks)
now save separate recordings instead of overwriting each other.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add effect to emit problem-shown marker when recording starts, ensuring
the current problem gets a video entry even if camera was enabled late
- Improve DockedVisionFeed UX: show session ID, action buttons immediately
for remote camera connections (not just after 15s timeout)
- Add .prettierignore to prevent prettier from corrupting Panda CSS
generated styled-system directory
- Document styled-system fix procedure in CLAUDE.md
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add server-side per-problem video recording that allows teachers to click
on completed problems in the observer progress indicator to watch recorded
videos of each problem attempt.
Key changes:
- New vision_problem_videos schema and migration for per-problem videos
- VisionRecorder refactored for per-problem frame management
- Socket server triggers ffmpeg encoding on problem transitions
- API routes for streaming videos and listing available recordings
- SessionObserverModal enables browse mode on progress indicator
- ProblemVideoPlayer component for viewing past problem recordings
Each problem gets its own MP4 video encoded incrementally as it completes,
making playback immediately available for recently completed problems.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add 'problem-shown' marker emission when new problems appear in practice
- Track currentProblemStartMs in VisionRecorder for each session
- Update vision-buffer-info socket event to include problem context
- Constrain DVR scrubber to current problem range only
- Auto-reset to live view when student moves to next problem
- Move DVR controls below video for better usability
- Fix QR code being cut off by removing overflow:hidden from containers
- Add QueryClientProvider to Storybook for React Query components
- Add comprehensive stories for ObserverVisionFeed with DVR states
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add comprehensive skill metrics to the scoreboard that allow comparing
students across a classroom with "fun ways to compare across ability levels":
Skills Progress Section (individual view):
- Overall mastery % (weighted by BKT confidence)
- Category breakdown (basic, fiveComplements, tenComplements, etc.)
- Speed metric (normalized seconds per term) with trend arrows
- Accuracy with trend indicators
- Weekly/streak/total problem counts
Classroom Achievements Section (fair comparisons):
- Practice Warriors: most problems this week
- Streak Masters: longest practice streak
- Rising Stars: highest improvement rate (pKnown delta/week)
- Speed Champions: fastest per category (only mastered students compete)
Also includes:
- Remote camera session validation to prevent stale sessions
- Comprehensive Storybook stories for all skill metrics components
New files:
- src/lib/curriculum/skill-metrics.ts (core computation)
- src/app/api/curriculum/[playerId]/skills/metrics/route.ts
- src/app/api/classroom/[classroomId]/skills/leaderboard/route.ts
- src/hooks/useSkillMetrics.ts (React Query hooks)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The barrel export from @/lib/classroom pulls in all manager modules,
some of which have transitive dependencies on React components with
styled-system imports. Import directly from the specific manager files
(classroom-manager.ts and enrollment-manager.ts) to avoid the
dependency chain that breaks esbuild bundling for the seed script.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The scoreboard was incorrectly using the teacher's classroom instead of
the student's enrolled classroom. Now uses useEnrolledClassrooms hook
to get the student's classroom membership for the leaderboard display.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create classroom for current user if they don't have one
- Enroll all seeded test students in the teacher's classroom
- Enables testing of classroom leaderboard features
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Dates serialized through JSON API come back as ISO strings, not Date objects.
Updated formatRelativeTime to handle string | number | Date inputs.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update scanner settings schema
- Update remote camera hooks
- Update socket server and vision session API
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add gameHistory configs to all 21 test student profiles
- Implement generateGameResults function to create scoreboard data
- Each profile has appropriate game history matching their characteristics:
- Struggling students get low scores (35-45)
- Developing students get medium scores (55-75)
- Strong students get high scores (78-95)
- Add accuracyMultiplier to TuningAdjustment interface (fixes TS error)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
gray.850 is not a valid Panda CSS token, causing the value to be
output as a literal string instead of resolving to a hex color.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add complete game results tracking and scoreboard system:
- Database schema: Add game_results table for storing game outcomes
- API endpoints: Add routes for saving results, player history, and
classroom leaderboards
- React Query hooks: Add usePlayerGameHistory, usePlayerClassroomRank,
useSaveGameResult
- ScoreboardTab: New dashboard tab showing personal bests, recent games,
and classroom rankings
- GameBreakResultsScreen: Interstitial showing results after game breaks
- Integration: Save game results when matching game completes, display
results before returning to practice
Includes Storybook stories and unit tests for both components.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add AttemptHistoryPanel component for browse mode that displays all
attempts (original + retries) for each problem slot. Teachers/parents
can now:
- Mark incorrect attempts as correct (for typo fixes)
- Exclude attempts from progress tracking
- Re-include previously excluded attempts
Technical changes:
- New ResultWithGlobalIndex type for tracking result indices
- Extended SlotResultSource with 'teacher-corrected' and 'teacher-excluded'
- New PATCH API endpoint for editing individual results
- updateSessionPlanResults() function in session-planner
- Query invalidation on result edits
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When abacus is docked, handleSubmit was using indexOf() to check if the
user's answer matched a prefix sum. This failed when an intermediate
prefix sum equaled the final answer (e.g., 65-34-15+33-18=31 where
prefixSums=[65,31,16,49,31]). indexOf(31) returns 1, triggering help
mode instead of accepting the correct answer.
Now uses findMatchedPrefixIndex() which correctly checks if the answer
equals the final answer FIRST before looking for intermediate matches.
Adds regression tests for this edge case.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 1 - Foundation:
- Extended PracticeBreakConfig type with suggestedConfig, lockedFields, difficultyPresets
- Added PracticeBreakOptions and PracticeBreakGameConfig types
- Extended GameBreakSettings with gameConfig field
Phase 2 - Game Integration:
- Added practiceBreakConfig to matching, memory-quiz, card-sorting manifests
- Implemented getInitialStateForPracticeBreak in game validators
- Set practiceBreakReady: false until full integration complete
Phase 3 - Teacher Configuration UI:
- Added GameBreakDifficultyPresets component (Easy/Medium/Hard presets)
- Added GameBreakCustomConfig component (per-field customization)
- Extended StartPracticeModalContext with game config state
- Integrated presets and customize into GameBreakSettings
Test Coverage:
- 23 context tests for game config state
- 8 component tests for GameBreakDifficultyPresets
- 12 component tests for GameBreakCustomConfig
- 7 Storybook stories for manual testing
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The boundary detector saves images as either PNG or JPG depending on the
camera source format, but the sync system only looked for PNG files. This
caused 3000+ JPG boundary frames on production to be invisible to sync.
Changes:
- SSH find pattern now includes *.png and *.jpg for boundary-detector
- countLocalData counts both PNG and JPG for boundary-detector
- listLocalFiles lists both extensions for boundary-detector
- rsync progress regex matches both extensions
Column classifier remains PNG-only as intended.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add practiceBreakReady: false to all game manifests that were missing it.
This field is required by the GameManifest type after it was added for
practice session game break support.
Affected games:
- card-sorting
- complement-race
- know-your-world
- memory-quiz
- rithmomachia
- yjs-demo
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add GameLayoutContext to control StandardGameLayout behavior
- Container mode uses 100% height (no viewport sizing) for practice breaks
- SetupPhase detects compact mode and hides verbose descriptions/hints
- Add data attributes to all SetupPhase elements for debugging
- Add Storybook stories for matching game in practice context
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add fill indicator track that animates to selected position
- Use dramatic typography progression (font-size and weight increase)
- Position track at bottom as baseline, not through text
- Export GameInfo type from context for consistent typing
- Combine label and toggle into single clickable element
- Add gaming-inspired gradient backgrounds and glow effects
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add unit tests for single-game scenario (hasSingleGame, singleGame, auto-selection)
- Add GameBreakSettings tests for single-game UI mode
- Add initialExpanded and practiceApprovedGamesOverride props for Storybook
- Update stories to properly demonstrate both single-game and multi-game UI
- Increase expanded panel maxHeight from 520px to 620px for multi-game UI
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace hacky .replace(' Battle', '').replace(' Lightning', '') calls
with a proper shortName field that games can define for compact UI spaces.
- Add optional shortName to GameManifestSchema
- Update matching game with shortName: 'Matching Pairs'
- Use shortName || displayName in GameBreakSettings
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Game Break Progress Indicators:
- Add game break countdown badge to practice nav (shows "3 until 🎮")
- Display specific game icon when a game is selected (not random)
- Show game icon and name in game break HUD header
Practice-Approved Games:
- Add practiceBreakReady flag to game manifest schema
- Games must opt-in via manifest AND be whitelisted
- Currently only Matching Pairs is practice-break ready
StartPracticeModal Refactoring:
- Extract state management to StartPracticeModalContext
- Create sub-components: DurationSelector, PracticeModesSelector,
GameBreakSettings, MaxTermsSelector, TutorialCTA, RemediationCTA,
StartButton, ErrorDisplay, SessionConfigSummary
- Add comprehensive unit tests for context and sub-components
Single-Game Mode UI:
- Simplified GameBreakSettings when only one game available
- Shows game icon + name inline with compact duration selector
- Removes unnecessary selection mode toggle and dropdown
- Adds "More games coming soon!" teaser
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>