Commit Graph

2455 Commits

Author SHA1 Message Date
Thomas Hallock 5f609d82e6 Improve example generation reliability for complex paths
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>
2026-01-16 17:24:15 -06:00
Thomas Hallock 5109bf35a3 Add proper gridLabels for all fraction flowchart dimensions
- "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>
2026-01-16 17:02:26 -06:00
Thomas Hallock 70ee13404c Fix empty gridLabel fallback in dynamic grid dimensions
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>
2026-01-16 17:00:57 -06:00
Thomas Hallock c6a8d5d1d8 Add difficulty tier selection with dynamic grid dimensions
- 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>
2026-01-16 16:29:31 -06:00
Thomas Hallock 72362b6a3e Add simplification and improper fraction dimensions to flowchart paths
- 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>
2026-01-16 15:09:49 -06:00
Thomas Hallock 3e3b036fc7 Enable browser back button for flowchart navigation
- 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>
2026-01-16 15:03:58 -06:00
Thomas Hallock 5266249ac3 Add orderMatters option for two-numbers checkpoints
- 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>
2026-01-16 15:01:32 -06:00
Thomas Hallock 1f01ce598f Remove retry button - show passive feedback with fresh inputs
- 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>
2026-01-16 14:59:55 -06:00
Thomas Hallock 4530f50bf8 Auto-submit two-numbers checkpoint when both values entered
- 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>
2026-01-16 14:58:44 -06:00
Thomas Hallock 285e36a333 Speed up checkpoint auto-advance (1000ms -> 400ms)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 14:56:49 -06:00
Thomas Hallock 14d86e179b Use MathDisplay for problem header in flowchart walker
- 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>
2026-01-16 14:43:23 -06:00
Thomas Hallock ad70040b46 Add two-numbers checkpoint type for denominator entry
- 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>
2026-01-16 14:41:01 -06:00
Thomas Hallock 74da416ab4 Add interactive checkboxes that must all be checked to proceed
- 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>
2026-01-16 14:36:20 -06:00
Thomas Hallock efc98b8d4d Restore second checklist item in READY CHECK node
Added back "☐ I wrote the fractions down" checklist item.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 14:27:37 -06:00
Thomas Hallock 3c7d83767a Add click-to-navigate on ledger entries
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>
2026-01-16 14:27:09 -06:00
Thomas Hallock 1dcb1457ef Fix warning emoji placement in mermaid content
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>
2026-01-16 14:23:49 -06:00
Thomas Hallock 8aa5ea5528 Fix checklist item continuation in node content parser
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>
2026-01-16 14:16:16 -06:00
Thomas Hallock 6835db6f57 Fix text selection on mobile long press
- 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>
2026-01-16 14:14:38 -06:00
Thomas Hallock da527777b9 Enhance flowchart problem input with edit popover and dynamic difficulty
- 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>
2026-01-16 13:38:30 -06:00
Thomas Hallock 2a8bf23028 debug: Add MCP worksheet tool argument logging
Temporary logging to debug why worksheet parameters aren't being applied.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 13:04:37 -06:00
Thomas Hallock ebb05cfc29 Improve flowchart UX: instant example start and back navigation
- 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>
2026-01-16 10:32:04 -06:00
Thomas Hallock 0d0c9f4e73 Add constraint-guided example generation with variety improvements
- 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>
2026-01-16 09:22:39 -06:00
Thomas Hallock 49eb2c8c36 Integrate path preview into decision cards
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>
2026-01-15 20:51:52 -06:00
Thomas Hallock 441f9ab178 Refactor phase rail to show path taken with decision previews
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>
2026-01-15 20:45:21 -06:00
Thomas Hallock 891176188d Improve decision UX with Radix RadioGroup and instant retry
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>
2026-01-15 20:34:38 -06:00
Thomas Hallock 95fb268132 Add horizontal phase rail for flowchart navigation
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>
2026-01-15 20:28:45 -06:00
Thomas Hallock 86df7ec77a feat: add Redis status to health check endpoint
- 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>
2026-01-15 20:06:19 -06:00
Thomas Hallock adc7d8fa0e Enhance working problem display with full history ledger
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>
2026-01-15 20:00:04 -06:00
Thomas Hallock b8c24303df Add interactive flowchart walker for step-by-step math procedures
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>
2026-01-15 19:39:28 -06:00
Thomas Hallock 1483de3acd fix: Redis client initialization at runtime vs build time
- 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>
2026-01-15 19:17:46 -06:00
Thomas Hallock c9899eddb7 chore: formatting cleanup for MCP and API files
Auto-formatting changes from Biome linter.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 18:32:55 -06:00
Thomas Hallock 0bc55530ac fix(vision): detect orphaned recordings at read time
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>
2026-01-15 17:51:03 -06:00
Thomas Hallock 57781a9ecc feat: enhance deployment info with health checks and refactor keypad
- 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>
2026-01-15 17:22:52 -06:00
Thomas Hallock 31b9f9dac1 feat(docs): add auto-generated API documentation with Scalar UI
- 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>
2026-01-15 15:56:27 -06:00
Thomas Hallock 633c789338 feat(mcp): add worksheet tools and resources
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>
2026-01-15 15:38:09 -06:00
Thomas Hallock 9f483b142e feat(settings): add settings page with live abacus preview
- 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>
2026-01-15 15:23:37 -06:00
Thomas Hallock bf5de17e22 feat(mcp): add session management tools
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>
2026-01-15 14:05:57 -06:00
Thomas Hallock 4fbdb3fe50 feat(debug): add debugging tools for cross-instance issues
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>
2026-01-15 11:42:54 -06:00
Thomas Hallock 3286fc56b0 fix: Add missing journal entry and snapshot for mcp_api_keys migration
The migration SQL file was committed but the drizzle journal and snapshot
were not updated, causing the migration to be skipped on prod.
2026-01-15 11:39:10 -06:00
Thomas Hallock 16f67bf305 Add error details to MCP key creation response for debugging 2026-01-15 11:18:38 -06:00
Thomas Hallock 0346455b3e feat(remote-camera): add Redis for cross-instance session sharing
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>
2026-01-15 11:18:22 -06:00
Thomas Hallock 8e4338bbbe fix(deploy): add sticky sessions for Socket.IO and remote camera
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>
2026-01-15 11:08:32 -06:00
Thomas Hallock 26f90f5428 fix(vision): restart rectified view loop when switching to local camera
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>
2026-01-15 10:59:07 -06:00
Thomas Hallock 4399ce9cef fix(vision): separate local/remote calibration preview state
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>
2026-01-15 10:55:47 -06:00
Thomas Hallock e2816ae88b feat(vision): improve remote camera calibration UX
- 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>
2026-01-15 10:51:59 -06:00
Thomas Hallock 5977cca2b5 feat: Add MCP API keys settings UI
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>
2026-01-15 10:21:22 -06:00
Thomas Hallock 3d643d3cc5 feat: Add MCP (Model Context Protocol) integration
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>
2026-01-15 10:04:52 -06:00
Thomas Hallock bb385b1699 chore: upgrade @pandacss/dev from 0.20 to 1.8.1
- 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>
2026-01-15 09:01:49 -06:00
Thomas Hallock eaa1d11c65 chore: formatting and training data updates
- Apply code formatting across codebase
- Add new vision training boundary frames
- Update model configurations

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 06:38:57 -06:00
Thomas Hallock 5281e384ad fix(vision): hide crop settings when phone camera not connected
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>
2026-01-15 06:37:51 -06:00
Thomas Hallock ba3d204b11 feat(vision): move calibration controls out of video overlay
- 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>
2026-01-15 06:32:31 -06:00
Thomas Hallock c35b02bbaf fix(vision): fix QR code clipping in phone camera modal
- 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>
2026-01-15 05:51:20 -06:00
Thomas Hallock c7f14b11de fix(vision): improve modal UX on small screens and touch devices
- 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>
2026-01-14 19:17:28 -06:00
Thomas Hallock e703e90875 chore: cleanup unused imports and apply formatting
- 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>
2026-01-14 18:49:31 -06:00
Thomas Hallock cecd1e93e2 fix(practice): handle loading phase in activeProblem effect
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>
2026-01-14 18:32:39 -06:00
Thomas Hallock 4733149497 chore: update local Claude settings
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 17:50:00 -06:00
Thomas Hallock 95b209e471 style: apply formatting to vision and practice components
Minor whitespace/line-wrapping adjustments.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 17:49:55 -06:00
Thomas Hallock 2f06ed3bbf test(players): add unit tests for useUserPlayers optimistic updates
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>
2026-01-14 17:43:38 -06:00
Thomas Hallock 72c4825333 feat(players): add optimistic update for listWithSkillData
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>
2026-01-14 17:35:22 -06:00
Thomas Hallock 71e9345d2a fix(players): invalidate all player queries on create/delete
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>
2026-01-14 17:20:09 -06:00
Thomas Hallock eefa4e8d71 fix(vision): show disabled Enable Vision button with explanation
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>
2026-01-14 17:18:24 -06:00
Thomas Hallock 4cace475b9 fix(vision): infer activeCameraSource from existing config
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>
2026-01-14 17:17:20 -06:00
Thomas Hallock b47992f770 feat(deploy): add blue-green deployment with health endpoint
- 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>
2026-01-14 17:04:01 -06:00
Thomas Hallock b08a796058 fix(observe): show banner with link instead of auto-redirect
- 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>
2026-01-14 12:38:55 -06:00
Thomas Hallock 66acd0c7c9 feat(observe): redirect to session report when session is over
- 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>
2026-01-14 12:36:17 -06:00
Thomas Hallock a607fce977 fix(vision): add ffmpeg for video encoding and lazy encoding support
- 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>
2026-01-14 12:30:15 -06:00
Thomas Hallock b5c83b89eb refactor(practice): derive active problem from single source of truth
- 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>
2026-01-14 11:52:38 -06:00
Thomas Hallock ce8c15f6cf fix: add missing vision recording files
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>
2026-01-14 09:34:23 -06:00
Thomas Hallock e368a66b5f fix: add missing vision-recordings schema file
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>
2026-01-14 09:27:15 -06:00
Thomas Hallock a5f5179a57 feat(debug): add socket debug panels for practice and observer pages
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>
2026-01-14 09:10:33 -06:00
Thomas Hallock 5ee18d6f74 fix(socket): handle already-connected socket in session hooks
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>
2026-01-14 08:40:38 -06:00
Thomas Hallock 9b86739b46 feat(vision): add multi-attempt recording support for epoch retries and manual redos
- 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>
2026-01-14 07:55:24 -06:00
Thomas Hallock fbbbf9f50b fix(vision): fix problem video recording when camera starts mid-session
- 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>
2026-01-13 18:56:21 -06:00
Thomas Hallock 371b449d15 feat(vision): implement per-problem video recording for teacher playback
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>
2026-01-13 18:21:54 -06:00
Thomas Hallock 87112c0235 feat(vision): add per-problem DVR scrubbing for teacher observation
- 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>
2026-01-13 17:33:47 -06:00
Thomas Hallock 7d2756e3d7 feat(scoreboard): add skills metrics with classroom leaderboard
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>
2026-01-13 13:13:21 -06:00
Thomas Hallock f8861e6f8d fix(scripts): import classroom functions directly to fix build
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>
2026-01-13 11:38:21 -06:00
Thomas Hallock c587caaaae fix(scoreboard): use student's enrolled classroom for leaderboard
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>
2026-01-13 11:34:12 -06:00
Thomas Hallock e419725037 feat(seeding): add classroom enrollment for test students
- 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>
2026-01-13 11:27:30 -06:00
Thomas Hallock d258c267f5 fix(scoreboard): handle ISO string dates in formatRelativeTime
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>
2026-01-13 11:16:13 -06:00
Thomas Hallock 12c95c92fa data(vision): add boundary frame training data
Captured frames for boundary detector training

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 10:49:58 -06:00
Thomas Hallock 9fa95eca89 chore: update tsconfig, plans, lockfile, and templates
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 10:49:34 -06:00
Thomas Hallock ba7cdb7044 feat(core): update schema, hooks, and socket server
- 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>
2026-01-13 10:49:24 -06:00
Thomas Hallock 534192f60f chore(models): update boundary detector and column classifier models
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 10:49:19 -06:00
Thomas Hallock 4f812d4ad0 feat(arcade): update matching game and manifest schema
Updates to game break configuration and manifest validation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 10:49:13 -06:00
Thomas Hallock b197d8f035 feat(practice): update practice session components and APIs
Updates to session plans, attempt history, and game break configuration

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 10:49:08 -06:00
Thomas Hallock f45dc8b7f3 feat(vision): update vision training components and APIs
Updates to boundary detection, sync status, and data panel components

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 10:49:03 -06:00
Thomas Hallock 7e3932ae5f feat(queries): add game results query keys for scoreboard
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 10:48:57 -06:00
Thomas Hallock c182756b80 feat(seeding): add game results generation to test student profiles
- 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>
2026-01-13 10:48:51 -06:00
Thomas Hallock dda041e014 fix(scoreboard): use valid gray.800 token instead of gray.850
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>
2026-01-13 10:48:44 -06:00
Thomas Hallock 17b1749342 feat(dashboard): add scoreboard tab with game results tracking
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>
2026-01-13 10:26:49 -06:00
Thomas Hallock 706dbce6f5 feat(practice): add attempt history panel with result editing
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>
2026-01-13 07:19:04 -06:00
Thomas Hallock 94be27bc06 fix(practice): use findMatchedPrefixIndex in handleSubmit for docked abacus
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>
2026-01-12 20:44:12 -06:00
Thomas Hallock b4b1090281 feat(practice): add game break preconfiguration UI (Phases 1-3)
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>
2026-01-12 19:06:39 -06:00
Thomas Hallock 6eddd75536 fix(vision): add JPG support to boundary detector sync
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>
2026-01-12 17:50:51 -06:00
Thomas Hallock a8df139a00 fix(arcade): add missing practiceBreakReady field to game manifests
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>
2026-01-12 17:39:37 -06:00
Thomas Hallock ed93c36f52 feat(matching): add responsive compact layout for practice game breaks
- 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>
2026-01-12 17:21:43 -06:00
Thomas Hallock 32d23d026d feat(practice): improve game break duration selector as time continuum
- 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>
2026-01-12 16:27:05 -06:00
Thomas Hallock 54ca5e4f02 feat(practice): add test coverage and storybook for game break UI modes
- 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>
2026-01-12 16:04:14 -06:00
Thomas Hallock a12df6b332 feat(arcade): add shortName field to game manifest
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>
2026-01-12 15:49:04 -06:00
Thomas Hallock b58a0eeca9 feat(practice): add game break indicators and simplify single-game UI
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>
2026-01-12 15:46:00 -06:00
Thomas Hallock 93a25c1e7b feat(vision): enhance quad detection with Hough lines and multi-strategy preprocessing
- Add Hough line detection for improved edge finding with finger occlusion
- Implement multi-strategy preprocessing (standard, enhanced, adaptive, multi)
- Add configurable parameters for Canny thresholds, adaptive threshold, morph gradient
- Refactor useDocumentDetection hook with cleaner API
- Add OpenCV type definitions and async loading improvements
- Add loader test pages for debugging OpenCV initialization
- Add quad-test page for interactive detection testing
- Add document detection research notes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 11:16:06 -06:00
Thomas Hallock bc02ba281d feat(scanner): add camera controls, auto presets, and persisted settings
- 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>
2026-01-12 11:12:30 -06:00
Thomas Hallock fb0abcef27 feat(vision): add modular quad detection with opencv-react
- Add quad-test page for testing quadrilateral detection
- Add modular quadDetector.ts with createQuadDetector(cv) factory
- Add quadTracker.ts for temporal stability tracking
- Add OpenCV types and lazy loader modules
- Add opencv-react and @techstark/opencv-js dependencies
- Add useOpenCV and useQuadDetection hooks
- Add various loader test pages for debugging OpenCV loading

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 11:12:30 -06:00
Thomas Hallock b96f8e6d2f feat(vision): add nav sync indicator and useSyncStatus hook
- 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>
2026-01-10 18:21:23 -06:00
Thomas Hallock 9ec139bdfa feat(vision): add multi-selection and bulk actions to unified data panel
- 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>
2026-01-10 18:20:02 -06:00
Thomas Hallock 5239e3f3cf fix(vision): fix sync status API endpoint and enable for both models
- 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>
2026-01-10 18:19:07 -06:00
Thomas Hallock ff5e0afc64 feat(vision): unify data panels with inline model testers
- 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>
2026-01-10 14:00:09 -06:00
Thomas Hallock bc95a2f34b feat(vision): add sync history tracking with tombstone pruning
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>
2026-01-10 11:59:13 -06:00
Thomas Hallock 2635bc565b fix(vision): consolidate training data deletion with tombstone recording
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>
2026-01-10 11:31:54 -06:00
Thomas Hallock 391ae20157 fix(vision): support JPEG images and fix null safety in session detail
- 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>
2026-01-10 10:34:00 -06:00
Thomas Hallock cf45b86a28 feat(vision): add filtering and passive capture for boundary training data
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>
2026-01-10 10:25:56 -06:00
Thomas Hallock f19e8b1dfa feat(vision): add path-based URL routing for vision training
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>
2026-01-10 08:07:21 -06:00
Thomas Hallock 7ee42a1b56 chore: update pnpm-lock.yaml and add __pycache__ to gitignore
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 19:19:00 -06:00
Thomas Hallock f78eae1148 feat(vision): add boundary detector for marker-free calibration
- 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>
2026-01-08 19:15:00 -06:00
Thomas Hallock 07908218b5 fix(vision): remove data augmentation option from training UI
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>
2026-01-08 13:35:53 -06:00
Thomas Hallock aa10d549b5 fix(vision): resolve 6 issues from practice session testing
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>
2026-01-08 12:59:15 -06:00
Thomas Hallock 49d05d9f21 feat(vision): re-trained model without quantization
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>
2026-01-08 11:02:16 -06:00
Thomas Hallock a8c0c5d921 fix(vision): remove quantization from model export - corrupts weights!
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>
2026-01-08 10:58:28 -06:00
Thomas Hallock c7ae52212c feat(vision): update column classifier model with more training data
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>
2026-01-08 10:52:58 -06:00
Thomas Hallock 25bdc99b63 fix(vision): improve dock sizing and mirror hint UX
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>
2026-01-08 10:34:22 -06:00
Thomas Hallock 24d08bb0f5 fix(vision): constrain docked abacus size for responsive layout
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>
2026-01-08 10:32:08 -06:00
Thomas Hallock 3adc427b9d fix(vision): unify docked abacus layout with status bar
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>
2026-01-08 10:28:54 -06:00
Thomas Hallock e685f10aff fix(vision): move undock button to vision status bar
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>
2026-01-08 10:23:48 -06:00
Thomas Hallock fbf4aa449c feat(vision): improve docked vision UX with unified status bar
- 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>
2026-01-08 10:18:44 -06:00
Thomas Hallock 3c238dc550 feat(blog): add vision detection stories and screenshots
- 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>
2026-01-07 20:31:00 -06:00
Thomas Hallock 77b8e6cfb4 fix: correct blog post year to 2026 2026-01-07 19:39:02 -06:00
Thomas Hallock e64063aab9 blog: add detailed setup instructions for vision mode
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>
2026-01-07 19:02:37 -06:00
Thomas Hallock 8da1a760dd blog: add post about vision-powered abacus detection
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>
2026-01-07 18:55:14 -06:00
Thomas Hallock fb57d1f2ef feat(vision): integrate ML classifier for real-time abacus detection
- 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>
2026-01-07 18:36:52 -06:00
Thomas Hallock 71c5321431 docs: add TensorFlow.js model debugging notes
Document lessons learned from debugging Keras → browser inference:
- Normalization mismatch (preprocessing layers are preserved)
- Quantization corruption (--quantize_uint8 corrupts weights)
- Output tensor ordering (detect by shape, not index)
- Debugging checklist and key files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 18:07:23 -06:00
Thomas Hallock 9df367bfe2 fix(vision): correct model normalization and add test tooling
- 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>
2026-01-07 18:02:31 -06:00
Thomas Hallock e9bd3b3c61 feat(vision): add remediation advice when training produces poor results
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>
2026-01-06 17:06:56 -06:00
Thomas Hallock 2bcdceef59 feat(vision-training): animate background tiles between image and digit
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>
2026-01-06 16:03:44 -06:00
Thomas Hallock fc38b5e6d3 fix(vision-training): increase background tile opacity from 4% to 12% 2026-01-06 15:59:05 -06:00
Thomas Hallock a9db984a03 fix(vision-training): make background tiles cover full viewport
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>
2026-01-06 15:57:48 -06:00
Thomas Hallock 6efabc2968 fix(vision-training): force dynamic rendering for all API routes
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>
2026-01-06 15:51:06 -06:00
Thomas Hallock 6b1fa85bb8 fix(vision): fix video ref timing bug preventing marker detection
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>
2026-01-06 15:45:12 -06:00
Thomas Hallock 6fb8f71565 feat(vision): add model tester to training results
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>
2026-01-06 14:45:14 -06:00
Thomas Hallock 94f53d8097 feat(vision): improve training data management and share marker detection
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>
2026-01-06 14:27:55 -06:00
Thomas Hallock 47a98f1bb7 feat(vision): add dependency preflight checks and inline training capture
- 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>
2026-01-06 12:16:28 -06:00
Thomas Hallock 0e1e7cf25d fix(vision): use physicalAbacusColumns for training data capture
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>
2026-01-06 10:42:41 -06:00
Thomas Hallock a40087284c fix(vision): enable ML training on production server
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>
2026-01-06 09:58:36 -06:00
Thomas Hallock 2eee17e159 fix(vision): handle unsupported platforms gracefully for training
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>
2026-01-06 09:53:52 -06:00
Thomas Hallock e1e0e6cc0c fix(vision): ensure training data collection works with remote camera
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>
2026-01-06 09:45:51 -06:00
Thomas Hallock 44d3ca612d feat(vision): wizard-style training UI with production sync
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>
2026-01-06 09:33:14 -06:00
Thomas Hallock 8ec148985e fix(vision): simplify training page UX with single status source
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>
2026-01-06 07:30:05 -06:00
Thomas Hallock 19680e39fd fix(vision): consolidate setup status to hardware panel only
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>
2026-01-06 07:25:35 -06:00
Thomas Hallock aa9618d192 fix(vision): sync status panel with hardware setup state
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>
2026-01-06 07:23:59 -06:00
Thomas Hallock 17289c8a1c fix(vision): use --clear flag for venv creation and add preflight check
- 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>
2026-01-06 07:22:41 -06:00