Phase 3 of generateDiverseExamples was only making one attempt per
additional example, which meant complex paths (like LCD + borrow with
~13% hit rate) often didn't get enough examples generated.
Changed Phase 3 to make up to 50 attempts per additional example,
using a consecutive failures counter to stop early if we're not
hitting the path at all.
Also added generation.test.ts with tests for path enumeration,
constraint satisfaction, and grid dimension inference - these help
debug and verify the example generation system.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- "Different denominators" for STEP1 NO option
- "Subtract" for ADDSUB Subtracting option
- "No simplifying" for SIMPLIFY_Q NO option
- "Proper fraction" for IMPROPER_Q NO option
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When gridLabel is an empty string "", fall back to pathLabel instead
of showing an empty header. The check was only for undefined, not
for empty strings.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Easy/Medium/Hard tier filter tabs to flowchart example picker
- Create inferGridDimensionsFromExamples() to dynamically determine
grid axes based on which dimensions vary within filtered examples
- Grid adapts to show the most meaningful dimensions for each tier
- Difficulty scoring uses existing path complexity (decisions + checkpoints)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add computed variables for result: rawResultNum, resultGcd, simplifiedNum/Denom
- Add needsSimplify and isImproper variables based on result computation
- Add correctAnswer to SIMPLIFY_Q and IMPROPER_Q decision nodes
- Add pathLabel/gridLabel to classify paths by simplification and conversion needs
This makes the simplify and improper→mixed conversion steps part of the
graph-based path classification, consistent with existing architecture.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Push browser history state when advancing to each new node
- Listen for popstate events to handle browser back button
- Back button UI now uses history.back() to stay in sync
- Going back from first step triggers onChangeProblem
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- New `orderMatters` property (default: true) on checkpoint nodes
- When false, accepts values in either order
- STEP0 denominator entry now accepts either order
- Changed labels from "Left/Right" to "First/Second" to not imply order
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace "Try again" button with passive feedback message
- Checkpoint inputs are immediately ready for retry (no feedback prop)
- Shows hint after 2 wrong attempts
- Kid can just re-enter values without clicking anything
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Auto-validates when both fields have integer values
- No need to click Check button (removed for two-numbers)
- Small delay (150ms) so kid sees what they typed before validation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Render problem header with MathDisplay component for consistent math formatting
- Fix type error in validateCheckpoint for two-numbers input
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add 'two-numbers' inputType to checkpoint schema with array-based expected values
- Update FlowchartCheckpoint to render two labeled input fields side by side
- Update validateCheckpoint to validate array inputs against array expressions
- Convert STEP0 (Look at Bottoms) from tap instruction to denominator entry checkpoint
- Kids must enter both denominators to confirm they identified them correctly
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Checklist items in instruction nodes now render as clickable checkboxes
- All items must be checked before auto-advancing to next step
- "I did it" button is hidden when node has interactive checklist
- Update checklist text to "I wrote the new fractions down"
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Clicking on any non-latest entry in the working problem ledger
navigates back to that step, allowing users to redo from that point.
- Added navigateToStep function that restores state from history
- Ledger entries (except latest) are now clickable with hover effects
- Keyboard accessible (Enter/Space to activate)
- Truncates state history to the restored point
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move ⚠️ emoji inside <b> tags so it becomes part of the title
rather than being parsed as a separate orphaned warning element.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Indented lines following a checklist item (☐/☑) are now appended to
that item instead of being treated as separate body text. This fixes
display issues where multi-line checklist items were split incorrectly.
Example: "☐ Both bottoms are<br/> the SAME number" now renders as
a single checklist item instead of a body line followed by a checklist.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add e.preventDefault() to touchStart handler
- Add userSelect: 'none' and WebkitTouchCallout: 'none' to example buttons
- Prevents native text selection when triggering edit popover via long press
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add edit popover for examples: click pencil badge or long-press on mobile
- Popover border and submit button colors reflect problem difficulty (green/orange/red)
- Colors update dynamically as user edits values
- Move dice to cozy clipped corner pane in top-right
- Make all input forms more compact to prevent wrapping
- Session storage caching for stable examples across re-renders
- Show flowchart title in component instead of page header
- Add data attributes throughout for testing
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Quick Pick examples now start immediately on click (no select-then-start)
- Redesigned layout with 3-column grid and "Make Your Own" section
- Added back navigation with state history tracking
- Fixed path constraint generation to use option index instead of "yes" string match
- Added validation that generated values satisfy computed constraints (like needsBorrow)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add constraint parser for flowchart problem generation
- Add interestingness constraints to prevent trivially easy problems:
- fraction-add-sub: notIdentical, meaningfulSubtraction, nonTrivialNumerators
- subtraction-regrouping: meaningfulDifference, notTooEasy
- Fix deterministic example selection with randomized picking from
smaller half of candidates sorted by numeric sum
- Add deduplication to prevent duplicate examples from biasing selection
- Add TeacherConfigPanel for flowchart practice configuration
- Add InteractiveDice component for random problem generation UI
- Add MathML type declarations
- Various UI improvements to flowchart walker components
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Each decision button now shows where it leads directly on the card:
┌─────────────────┐ ┌─────────────────┐
│ YES ✓ │ │ NO │
├─────────────────┤ ├─────────────────┤
│ ↓ │ │ ↓ │
│ Skip to │ │ Find LCD │
│ add/subtract │ │ first │
└─────────────────┘ └─────────────────┘
This integrates the flowchart navigation with the interaction UI:
- Card header shows the choice label
- Card body shows arrow + where that choice leads
- Removes redundant "Choose" preview from phase rail
- Kids see consequences of their choice before clicking
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace the linear node display with a smarter representation:
**Path display:**
- Shows the actual path taken through the flowchart
- Includes which choice was made at each decision node (e.g., "Same? (NO)")
- Uses ellipsis (•••) when path has more than 2 previous nodes
- Only shows: ellipsis → previous → current (keeps it compact)
**Decision preview:**
- When current node is a decision, shows where each option leads
- Format: [YES ✓] → Skip ahead | [NO] → Find LCD
- Helps kids understand the consequences of their choices
This accurately represents the branching flowchart structure instead
of misleadingly showing all nodes as if they'd all be visited.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace the manual "try again" flow with a smoother experience:
- Use Radix RadioGroup for proper accessibility (keyboard nav, focus)
- Wrong answers trigger a shake animation and highlight correct/wrong options
- After 1.5s, the highlighting auto-clears and user can try again immediately
- No more clicking "Try again" button - just select again
- Correct/wrong indicators show as badges on the option buttons
- Better styling with rounded corners, hover states, and shadows
The component now handles its own feedback state internally, making the
FlowchartWalker simpler and the user experience more fluid.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements Approach C: a horizontal rail showing all phases with
the current phase expanded below.
Features:
- All phases visible as cards in a scrollable horizontal rail
- Current phase is wider and highlighted in blue
- Completed phases show green with checkmarks
- Future phases show gray with circles
- Dots within each phase card represent nodes
- Current node has a pulsing ring effect
- Expanded view below shows node progression with pills
- Nodes show completion status (✓), current (→), or pending
This replaces the simple linear progress bar with a visualization
that shows the actual flowchart structure, helping kids understand
where they are in the multi-step process.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Health check now includes Redis connectivity status
- New status levels: healthy, degraded, unhealthy
- degraded (200): Redis down but app can still serve traffic
- unhealthy (503): Database down, cannot serve traffic
- Lists affected features when Redis is unavailable:
- cross-instance sessions
- remote camera sync
- Socket.IO scaling
This helps catch issues like Redis not being connected early.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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>
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>
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>
- Add torch/flashlight toggle (when available)
- Add camera flip button (when multiple cameras)
- Add lighting preset selector with auto-detection based on frame analysis
- Add experimental finger occlusion mode for better edge detection
- Persist scanner settings per-user in database
- Add responsive ScannerControlsDrawer with compact mobile layout
- Hide sublabels on small screens, show on tablets+
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add NavSyncIndicator component for compact sync status in nav bar
- Shows sync availability, new items count, syncing progress
- Dropdown with sync actions and history
- States: loading, unavailable, in-sync, new-available, syncing, error
- Add useSyncStatus hook for managing sync state
- Fetches sync status from API
- Handles sync execution with SSE progress
- Tracks sync history
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add multi-selection support with click to toggle, shift+click for range
- Add bulk delete for selected items (both model types)
- Add bulk reclassify for column-classifier (move images to different digit)
- Show bulk selection panel when 2+ items selected with action buttons
- Single click now also opens detail panel for the clicked item
- Add selection indicators on grid items (checkmark badge)
- Add hover actions on grid items (view details, delete buttons)
- Update grid items to show focused vs selected states with different colors
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix sync status fetch URL from non-existent /api/vision-training/sync/status
to the correct /api/vision-training/sync endpoint
- Add modelType query parameter to both GET (status) and POST (sync) requests
- Enable sync feature for both boundary-detector and column-classifier
(was previously restricted to column-classifier only)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create unified data panel architecture replacing separate BoundaryDataPanel and ColumnClassifierDataPanel
- Add MiniBoundaryTester with SVG overlay showing ground truth vs predicted corners
- Add MiniColumnTester with prediction badge overlay showing digit and correct/wrong status
- Support preloading images in full test page via ?image= query param
- Fix overflow on detail panel to allow scrolling
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Track all training data sync operations with detailed metrics:
- Add vision_training_sync_history table with SQLite schema
- Record sync start/end times, file counts, tombstone stats
- Prune tombstone entries for files that no longer exist on remote
- Show sync history indicator in ColumnClassifierDataPanel
New files:
- vision-training-sync-history.ts - DB schema for sync history
- sync/history/route.ts - API to query sync history
- SyncHistoryIndicator.tsx - UI component showing recent syncs
The tombstone pruning ensures local tombstone files don't grow unbounded -
after each sync, entries for files deleted on remote are removed since
there's no longer a risk of re-syncing them.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
All training data deletions now go through shared utility functions that
ensure deletions are recorded to tombstone files (preventing re-sync from
production).
- Add trainingDataDeletion.ts with deleteColumnClassifierSample() and
deleteBoundaryDetectorSample() functions
- Update bulk delete endpoint to use shared utility (was missing tombstone)
- Update single-file delete endpoint to use shared utility
- Update boundary-samples delete to use shared utility (was missing tombstone)
- Update sync route to use shared readTombstone() function
- Add comprehensive unit tests (17 tests covering validation, deletion,
tombstone recording, and edge cases)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Image route now tries both .png and .jpg extensions (passive captures are JPEG)
- Session detail page guards against undefined result/config values
- Prevents "Cannot read properties of undefined" errors
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Major changes:
- Add passive boundary capture during practice sessions via DockedVisionFeed
- Create BoundaryFrameFilters component with capture type, session, player,
and time range filtering using TimelineRangeSelector
- Add sessionId/playerId metadata to boundary frame annotations
- Update boundary-samples API to store and return session/player context
- Improve boundary detector training pipeline with marker masking
- Add preprocessing pipeline preview in training data browser
- Update model sharding (2 shards instead of 1)
Files added:
- BoundaryFrameFilters.tsx - Filter UI component
- usePassiveBoundaryCapture.ts - Hook for passive training data collection
- saveBoundarySample.ts - Shared utility for saving boundary samples
- preview-augmentation/route.ts - API for preprocessing pipeline preview
- preview-masked/route.ts - API for marker masking preview
- marker_masking.py, pipeline_preview.py - Python training utilities
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implement shared nav bar and path-based model selection for vision training pages.
Changes:
- Add useModelType() hook to get model type from URL path
- Add VisionTrainingNav component with model selector dropdown and tab links
- Add [model]/layout.tsx with fixed nav and --nav-height CSS variable
- Move pages under [model]/ directory structure:
- /vision-training/[model] - Data hub
- /vision-training/[model]/train - Training wizard
- /vision-training/[model]/test - Model tester
- /vision-training/[model]/sessions - Sessions list
- /vision-training/[model]/sessions/[id] - Session detail
- Remove Model Card from training wizard (model selection now via URL/nav)
- Update TrainingWizard to use modelType from URL (always defined, not null)
- Remove localStorage restoration of model type
- Add registry.tsx for model metadata
URL is now single source of truth for model type. Browser history and
deep linking work correctly.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add boundary detector ML model infrastructure (MobileNetV2-based)
- Add training script for boundary detector (train_model.py)
- Add useBoundaryDetector hook for browser inference
- Add BoundaryCameraTester for real-time camera testing
- Add BoundaryImageTester for static image testing
- Add sync API support for boundary detector training data
- Add model type selector on test page (column classifier vs boundary detector)
- Add marker inpainting for training data preprocessing
- Update training wizard to support both model types
The boundary detector aims to detect abacus corners without ArUco markers,
using ML to predict corner positions from raw camera frames. Currently
requires more training data for accurate predictions.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Data augmentation doesn't work well for abacus column recognition,
so remove the option entirely. Training now always runs without
augmentation (--no-augmentation flag always passed to Python script).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1. VisionSetupModal too tall - constrain modal height and video aspect ratio
2. Modal not draggable after setup - add drag constraint ref to motion.div
3. Remote camera URL changes on reload - prevent race condition by checking
localStorage before rendering QR code component
4. "Waiting for remote connection" stuck - add 15s timeout with retry/settings buttons
5. Auto-undocking on problem progression - remove incorrect isDockedByUser=false
from unregisterDock() which fired during React re-renders
6. Re-docking goes to fullscreen - check dock existence instead of visibility
Also includes updated ML model weights (non-quantized export).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Model exported without --quantize_uint8 flag, which was corrupting
weights. Size increased from ~556KB to ~2.2MB but predictions should
now be correct in the browser.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The --quantize_uint8 flag in tensorflowjs_converter corrupts model
weights, causing completely wrong predictions in the browser even
though Python testing works fine.
This was documented in .claude/CLAUDE.md but the training script
still used quantization. Model size increases (556KB → 2.2MB) but
predictions are now correct.
Removed quantization from all 3 export paths:
- SavedModel → GraphModel conversion
- Keras → LayersModel fallback
- Direct Python API fallback
After this fix, re-run training via /vision-training/train to get
a working model.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Re-trained model with additional training samples for improved
digit detection accuracy.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Dock sizing:
- Larger on desktop: up to 240px wide, 300px tall (md breakpoint)
- Smaller on mobile: 140-180px wide, 180-240px tall (base)
Mirror hint:
- Once shown, stays visible until dismissed or mirror enabled
- No more flashing when stability temporarily drops
- Added dismiss (✕) button to hide hint for rest of session
- Clicking "Try Mirror" still enables mirror mode
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add explicit height constraint alongside width for the docked abacus.
Both vision-enabled and vision-disabled modes now share the same
container size constraints:
- Width: clamp(140px, 16vw, 200px) - about 2/3 of previous
- Height: clamp(180px, 24vh, 260px) - prevents going under nav
Also reduced container max-width from 900px to 780px to match.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use the same flex column layout for both vision-enabled and digital
abacus modes. The status bar is now at the bottom (not overlapping the
abacus columns) with VisionIndicator on left and undock button on right.
This matches the DockedVisionFeed layout for consistency.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move the undock button into the DockedVisionFeed status bar alongside
the disable-vision button. This prevents the buttons from overlapping.
When vision is enabled, the dock-controls overlay is now hidden since
all controls are in the unified status bar.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Consolidate scattered controls into unified status bar at bottom
- Replace auto-switching with subtle "Try Mirror" hint when detection stable
- Use flex layout instead of absolute positioning to prevent clipping
- Make AbacusDock a flex sibling of problem-area for proper layout
- Use responsive sizing with clamp() instead of matching problem height
- Add Video/Mirror toggle with clear text labels
- Container widens to 900px when dock is shown (vs 600px normally)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create VisionDetection.stories.tsx with interactive demos
- Add screenshot capture script for Storybook stories
- Update blog post with captured screenshots
- Include before/after comparison, progress gallery, and step demos
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace vague "Getting Started" section with step-by-step guide:
- Printing and attaching ArUco markers
- Docking the abacus in practice sessions
- Opening vision settings via camera icon
- Camera options (local vs phone)
- Automatic marker-based calibration
- Enabling vision and starting practice
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Introduces the new ML-powered vision system that watches physical
abacus use in real-time, providing instant feedback as students
work through problems.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Enable auto-detection in DockedVisionFeed using ML column classifier
- Replace CV-based detection with useColumnClassifier hook
- Add concurrent inference prevention with isInferringRef
- Show model loading status in detection overlay
- Add detectedPrefixIndex prop to VerticalProblem for visual feedback
- Display ✓ checkmarks on terms completed via vision detection
- Connect vision detection to term tracking and auto-submit flow
The vision system now:
1. Detects abacus values via ML classification at 10fps
2. Shows visual feedback (checkmarks) as terms are completed
3. Triggers help mode when prefix sums are detected
4. Auto-submits when the correct answer is shown
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix column classifier to use [0,1] normalization (model has internal
Rescaling layer that converts to [-1,1] for MobileNetV2)
- Remove quantization from model export (was corrupting weights)
- Add /vision-training/test page for direct model testing
- Add Python test script for local model verification
- Clean up verbose debug logging from classifier
- Add model tester to training results card
- Add training data hub modal and supporting components
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When training accuracy is low (<50%), the results card now shows
a yellow warning box explaining why accuracy might be poor and
what actions to take:
- Data imbalance: identifies underrepresented digits
- Insufficient data: recommends collecting more images
- Poor convergence: suggests checking data quality
- Unknown issues: fallback advice
Uses a context provider pattern to avoid prop drilling through
5 component levels (page → TrainingWizard → PhaseSection →
CardCarousel → ExpandedCard → ResultsCard).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Each tile now randomly transitions between showing the training image
and the numeral it represents. The animations are staggered with random
initial delays and intervals so tiles don't all animate together.
- Created AnimatedTile component with crossfade effect
- Random 3-8 second intervals between transitions
- 0.8s ease-in-out opacity transitions
- Random initial delays (0-10s) for natural feel
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The tiled background wasn't covering the entire viewport because
the inner grid had no explicit dimensions. Fixed by:
- Setting grid to 120vw x 120vh to overflow viewport
- Negative margins to center the oversized grid
- Increased tile count from 100 to 800 for full coverage
- Removed scale(1.2) since explicit sizing handles it
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Next.js App Router caches GET route handlers by default when they
don't use request-based data. This caused /api/vision-training/samples
to return cached "no data" responses even after data was collected.
Added `export const dynamic = 'force-dynamic'` to all vision-training
API routes that read from disk or run system commands:
- samples/route.ts
- hardware/route.ts
- preflight/route.ts
- sync/route.ts
- train/route.ts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The videoRef callback in VisionCameraFeed was only firing once on mount,
before the video element existed (due to early return when no videoStream).
Added videoStream to the effect dependency array so it re-runs when the
stream becomes available.
Also:
- Add willReadFrequently option to canvas contexts in frameProcessor
and arucoDetection to fix console warnings about getImageData
- Add VISION_COMPONENTS.md documentation with required wiring checklist
- Add reference in CLAUDE.md to prevent this recurring mistake
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
After training completes, users can now test the model directly:
- New "Test Model" tab in results phase
- Uses CameraCapture with marker detection
- Runs inference at ~10 FPS and shows detected value + confidence
- Color-coded confidence display (green > 80%, yellow > 50%, red otherwise)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Training Data Management:
- Add bulk delete modal with timeline range selection
- Add deletion tracking (.deleted tombstone file) to prevent re-syncing
- Add file-by-file comparison for sync (shows new/deleted counts)
- Improve image viewer with filtering, sorting, and batch operations
- Add delete toast with undo functionality
Shared Marker Detection:
- Create useMarkerDetection hook for ArUco marker detection
- Refactor DockedVisionFeed to use shared hook (removes ~100 lines)
- Add marker detection support to CameraCapture component
- Calibration now persists when markers are temporarily lost
Training Wizard UI:
- Add tabbed "Get More Data" section in DataCard
- Show excluded (deleted) file count in sync tab
- Improve phase navigation and state management
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add preflight API endpoint to check Python dependencies before training
- Create DependencyCard wizard step showing installed/missing packages
- Create reusable CameraCapture component supporting local + phone cameras
- Add inline training data capture directly in the training wizard
- Update venv setup to install all requirements from requirements.txt
- Block training start until all dependencies verified
The wizard now shows a dedicated Dependencies step between Hardware and
Config, displaying which packages are installed and providing pip install
commands for any missing packages.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Training images were incorrectly showing 2 columns instead of 1 because
the vision system confused "digits in answer" with "physical columns".
Root cause: For 2-digit problems, dock.columns was 2 (digits needed),
but the physical abacus has 4 columns. Training images were sliced into
2 parts, each containing 2 physical columns.
Fixes:
- ActiveSession: Use physicalAbacusColumns for training capture
- MyAbacus: Use physicalAbacusColumns for vision detection
- VisionSetupModal: Use physicalAbacusColumns for calibration
- frameProcessor: Add column margins (6% left/right) for isolation
- types/vision: Add ColumnMargins interface to CalibrationGrid
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Research confirmed TensorFlow has wheels for Linux x86_64 (including
Synology NAS). The previous failures were due to:
1. Training scripts not being copied to Docker image
2. Venv path not being writable in container
Changes:
- Update isPlatformSupported() to include Linux ARM64 support
- Move venv to data/vision-training/.venv (mounted volume, writable)
- Add python3-venv to Docker image dependencies
- Copy training scripts to Docker image
- Create vision-training directories in Docker build
Sources:
- https://www.tensorflow.org/install/pip
- https://pypi.org/project/tensorflow/ (shows manylinux_2_17_aarch64 wheels)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add platform detection before attempting TensorFlow installation.
TensorFlow doesn't have wheels for ARM Linux (like Synology NAS),
so the hardware detection now returns a clear "Platform Not Supported"
message instead of failing during pip install.
Changes:
- Add isPlatformSupported() check in config.ts
- Check platform before attempting venv setup in hardware/route.ts
- Check platform before starting training in train/route.ts
- Show user-friendly message in HardwareCard for unsupported platforms
- Add data/vision-training/collected/ to .gitignore
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The vision source registration effect was not re-running when the first
remote camera frame arrived, because remoteLatestFrame was not in the
dependency array. The <img> element that remoteImageRef points to only
renders when remoteLatestFrame is truthy, so the effect would run before
the element existed and fail to register the source.
Now both local and remote cameras properly register their vision source
for training data collection when a correct answer is entered.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Transform training pipeline into a phase-based wizard stepper:
- Preparation phase: data check, hardware detection, config
- Training phase: setup, loading, training progress, export
- Results phase: final accuracy and train again option
Add sync UI to pull training images from production NAS:
- SSE-based progress updates during rsync
- Shows remote vs local image counts
- Skip option for users who don't need sync
New wizard components:
- TrainingWizard: main orchestrator with phase management
- PhaseSection: collapsible phase containers
- CardCarousel: left/center/right card positioning
- CollapsedCard: compact done/upcoming cards with rich previews
- ExpandedCard: full card with content for current step
- Individual card components for each step
APIs added:
- /api/vision-training/samples: check training data status
- /api/vision-training/sync: rsync from production with SSE
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Consolidated confusing multiple status messages into one clear flow:
- Status panel is now the single source of truth for what's happening
- "Preparing..." with explanation that first run takes 2-5 minutes
- Hardware panel just shows hardware (dash while loading, name when ready)
- Button always says "Start Training" (disabled state is visual enough)
- Error details shown once in status panel, with hint
- Simple "Retry" button in hardware panel on failure
Before: "Detecting..." + "Setting up..." + "Setting up Python environment..."
After: "Preparing..." + "Installing TensorFlow... First run may take 2-5 min"
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Removed redundant status messages:
- Hardware panel is the single source for setup state
- Button just says "Start Training" (disabled when not ready)
- Status panel only shows training progress, not setup state
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Status indicator and text now properly reflect:
- Blue pulsing dot + "Setting up Python environment..." while loading
- Red dot + "Setup failed" when hardware detection fails
- Green dot + "Ready to train" only when hardware is ready
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use Python's --clear flag to handle existing venv directories atomically
- Disable "Start Training" button until hardware setup succeeds
- Show "Setup Failed" state on button when hardware detection fails
- Add "Retry Setup" button when setup fails
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>