Replace the simple breadcrumb display with a proper ledger showing:
- Step numbers (1, 2, 3...)
- The math expression at each step with proper formatting
- Labels describing what happened (e.g., "Remove the constant")
- Tooltip showing which flowchart node caused each change
The latest step is highlighted with a more prominent border and
background, while previous steps are shown with reduced opacity
to maintain focus on current work while preserving full context.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements a generic flowchart execution engine that walks kids through
multi-step math procedures interactively. Features:
- Expression evaluator with support for arithmetic, comparisons, and functions
(lcm, gcd, min, max, abs, floor, mod)
- Flowchart state management with problem input, computed variables, and
user-entered values
- Working problem display that evolves as the student progresses
(e.g., 2x - 9 = 3 → 2x = 12 → x = 6)
- MathDisplay component for rendering fractions, mixed numbers, and
algebraic expressions with proper formatting
- Decision nodes with validation and feedback
- Checkpoint nodes with number input and answer validation
- Three initial flowchart definitions:
- Fraction addition/subtraction with LCD finding and borrowing
- Linear equation solving (one-step and two-step)
- Subtraction with regrouping
The system uses companion .flow.json files alongside Mermaid .mmd diagrams,
keeping the visual flowcharts valid while adding interactivity metadata.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add `dynamic = 'force-dynamic'` to /api/build-info route to prevent
static caching of runtime values (hostname, REDIS_URL, etc.)
- Fix Redis client singleton to retry initialization if REDIS_URL
wasn't set at build time but is set at runtime
- Update Traefik health check from `/` to `/api/health` for better
health checking (verifies database connectivity)
The issue was that Next.js was evaluating redis.ts at build time
(when REDIS_URL isn't set in Docker buildkit), caching `null` as the
client, and never re-checking at runtime. Now it retries if the env
var becomes available.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When VisionRecorder's in-memory state is lost (server restart, socket
reconnection, browser refresh), recordings can get stuck in 'recording'
status forever. Instead of complex cleanup logic on writes, detect
orphaned recordings at read time using a simple rule:
Any recording with status='recording' that has an older startedAt than
another recording in the same session is orphaned and treated as
'no_video'. Only the most recently started recording can legitimately
be "in progress".
This handles all scenarios: moving to next problem, manual redo, retry
epochs - all result in a newer recording starting, marking older ones
as orphaned.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add useDeploymentInfo hook with live health/build info fetching
- Refactor DeploymentInfoContent with server health status, WebSocket
connectivity, and database status displays
- Add Storybook stories and tests for DeploymentInfoContent
- Extract NumericKeypad styles to CSS file and config to separate module
- Add debug page index
- Update NAS deployment configs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Install next-openapi-gen for automatic OpenAPI spec generation from routes
- Add /api-docs page with Scalar UI for interactive API documentation
- Add generate step to build script (runs on every deploy)
- Configure to scan Zod schemas and App Router API routes
- Fix migration 0071 to use IF NOT EXISTS for idempotency
- Add public/openapi.json to .gitignore (generated file)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add MCP tools for worksheet generation:
- generate_worksheet: Create worksheets with configurable difficulty
- get_worksheet_info: Get info about existing worksheets
- list_difficulty_profiles: List available difficulty presets
Add MCP Resources for worksheet documentation:
- docs://worksheet/regrouping - Carrying/borrowing pedagogy
- docs://worksheet/scaffolding - Visual aids configuration
- docs://worksheet/difficulty-profiles - The 6 preset profiles
- docs://worksheet/digit-range - Min/max digit settings
- docs://worksheet/operators - Addition/subtraction/mixed
Includes 47 unit tests for tools, resources, and download endpoint.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create /settings page with tabs for General, Abacus Style, and MCP Keys
- Extract AbacusStylePanel as shared component for nav dropdown and settings
- Add AbacusDock integration with auto-dock/undock on tab mount/unmount
- Add hideUndock prop to prevent manual undocking in settings preview
- Add updateDockConfig to update dock props without re-registration
- Fix requestDock stability using ref to prevent animation on config changes
- Add ThemeToggle back to AppNavBar while keeping it in settings
- Add navSlot prop to PageWithNav for vision training contextual nav
- Create VisionTrainingNavSlot for model selection in vision training pages
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add 5 new MCP tools for creating and managing practice sessions:
- start_practice_session: Create/start sessions with full config
- get_active_session: Get current session with URLs and progress
- control_session: Approve, start, end, or abandon sessions
- create_observation_link: Generate shareable observation URLs
- list_observation_links: List active share links for a session
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1. Enhanced /api/build-info endpoint:
- Shows instance hostname and container ID
- Shows Redis connection status
- Shows Socket.IO adapter type (redis/memory)
2. Instance-specific subdomain routes:
- blue.abaci.one routes to blue container only
- green.abaci.one routes to green container only
- Useful for testing cross-instance communication
3. Socket.IO debug page (/debug/socket):
- Shows connection status and socket ID
- Join/leave rooms (remote-camera, arcade, game)
- Send custom events with JSON data
- Real-time event log with direction arrows
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
compose-updater can't resolve depends_on references to services
defined only in the main docker-compose.yaml. Remove depends_on
and rely on REDIS_URL environment variable instead.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Production blue/green deployment caused remote camera to fail because
desktop and phone could hit different instances with separate in-memory
session storage and Socket.IO rooms.
Changes:
- Add Redis service to docker-compose (production only)
- Create Redis client utility with optional connection
- Update session manager to use Redis when REDIS_URL is set
- Add Socket.IO Redis adapter for cross-instance room broadcasts
- Convert session manager functions to async
- Update tests for async functions
In development (no REDIS_URL), falls back to in-memory storage.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remote camera sessions are stored in-memory per instance. Without sticky
sessions, Traefik could route desktop to Blue and phone to Green, causing
"session expired" errors and failed connections.
Sticky sessions ensure the same client always hits the same backend instance,
which is required for:
- Socket.IO connections (rooms are per-instance)
- Remote camera session state (in-memory Map)
- Any stateful WebSocket communication
Note: Sessions will still be lost on container restart/deployment. For full
robustness, sessions should be persisted to database and Socket.IO should
use Redis adapter. This is a workaround for the immediate issue.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The rectified view effect checked `rectifiedCanvasRef.current` but refs
don't trigger re-renders. When switching back to local camera, the effect
ran before the canvas was mounted and returned early, showing a black image.
Added `canvasMounted` state that gets updated via the canvas ref callback,
ensuring the effect re-runs when the canvas becomes available.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The local camera preview was using a shared `calibrationCorners` state
that got polluted by remote camera calibration. Now each camera source
uses its own editing corners state:
- Local camera preview uses `localEditingCorners`
- Remote camera preview uses `remoteEditingCorners`
Removed the unused shared `calibrationCorners` state entirely.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 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>
Adds a user-facing settings page at /settings/mcp-keys for managing
MCP API keys. Features:
- Generate new keys with custom names
- Copy key to clipboard (only shown once on creation)
- List active keys with last-used timestamps
- Revoke keys with confirmation
- Shows collapsed list of revoked keys
- Usage instructions with copy-paste config
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds MCP support for external tools like Claude Code to access student
skill data via JSON-RPC 2.0 over HTTP.
Features:
- API key authentication with user-scoped access
- 6 MCP tools: list_students, get_student_overview, get_skills,
get_recent_errors, get_practice_sessions, get_recommended_focus
- API key management endpoints (create, list, revoke)
- Full documentation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Upgrade Panda CSS for better watch mode stability
- Add --clean flag at dev startup to prevent stale files
- Add --poll flag to watch mode for more reliable file watching
- Move @pandacss/dev to devDependencies (was incorrectly in dependencies)
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>
Don't show the crop settings section when phone camera source is
selected but no phone is connected yet - the user needs to connect
first before crop options are relevant.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Move Cancel, Done, and Rotate buttons from CalibrationOverlay to
the calibration-preview section in AbacusVisionBridge
- Add animated rotation with smooth easeOutCubic interpolation (300ms)
- CalibrationOverlay now exposes imperative handle (rotate, complete)
- Controls no longer block drag handles on the calibration quad
- Preview canvas animates in sync with corner rotation
- Support both local and remote camera calibration UIs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove redundant overflow:hidden from phone-camera-feed element
- Use compact QR code mode to reduce content height
- Use proper centering that fits within container constraints
- Increase QR size to 150px to show branded abacus logo
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix camera feed container to maintain fixed 4:3 aspect ratio regardless
of cropped image dimensions (prevents button being pushed off screen)
- Add touch-action: none to calibration handles for proper touch dragging
- Use drag controls to allow modal repositioning from header during calibration
- Reduce modal heights and padding for better small screen support
- Remove overflow:hidden from crop settings to prevent content clipping
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 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>