Major refactoring of worksheet parsing to use centralized state management:
New architecture:
- WorksheetParsingContext: React context provider for parsing state
- state-machine.ts: Typed reducer with actions for streaming lifecycle
- sse-parser.ts: Shared SSE parsing utility for OpenAI Responses API
- usePartialJsonParser.ts: Progressive JSON extraction during streaming
Streaming UI improvements:
- ParsingProgressOverlay: Dark overlay on photo tile during parsing
- ParsingProgressPanel: Collapsible reasoning text panel
- ProgressiveHighlightOverlay: Problem boxes light up as LLM parses
- New streaming API routes: /parse/stream and /parse-selected/stream
Bug fixes during testing:
- Fix TypeScript error: cast event.response for id access in sse-parser
- Fix reparse reasoning display: preserve "processing" status for reparse
- Fix concurrent parsing: revert previous attachment status when switching
- Fix problem count: track dispatched problems to prevent duplicates
Components updated to use context:
- SummaryClient: Wrapped with WorksheetParsingProvider
- OfflineWorkSection: Uses context instead of local streaming state
- PhotoViewerEditor: Uses context for coordinated parsing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Connect onApproveProblem, onFlagProblem, and onFocusReviewComplete
callbacks from PhotoViewerEditor to useUpdateReviewProgress hook.
This enables the one-at-a-time problem review flow where users can:
- Approve individual problems
- Flag problems for later review
- Complete the review workflow
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
DocumentAdjuster needs OpenCV for the perspective transform preview,
but loadOriginalForEditing was only loading OpenCV when corners
weren't already saved. This caused photos with saved corners to get
stuck on "Loading editor..." forever.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
SummaryClient Refactoring:
- Extract SessionAttachmentResponse type to src/types/attachments.ts
- Create usePhotoManagement hook consolidating 10+ callbacks and 7+ state vars
- Create usePhotoViewer hook for photo viewer modal state
- Extract CameraModal and DocumentAdjustmentModal components
- Add getPendingAttachmentId helper to useWorksheetParsing
Worksheet Review UI:
- Add ProblemReviewCard component for individual problem editing
- Add ProblemReviewFlow for swipeable problem navigation
- Add ReviewMiniMap for visual problem overview
- Add ReviewToolbar for review mode controls
- Add ReparseHintsModal for reparse with hints
- Add LLMDebugPanel for viewing raw LLM responses
- Add unapprove endpoint to revert approved worksheets
Storybook:
- Add worksheet parsing stories with sample images
- Update preview configuration
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Break the import chain that bundled arcade-specific code (useRoomData,
GameModeProviderWithHooks) with practice pages:
- Create PreviewModeContext.tsx to avoid pulling in full GamePreview module
- Refactor GameModeContext to receive player/room data as props
- Create GameModeProviderWithHooks wrapper that calls the heavy hooks
- Use dynamic imports in arcade layouts for GameModeProviderWithHooks
- Update GamePreview.tsx to dynamically import GameModeProviderWithHooks
Result: Practice summary page First Load JS reduced from 467KB to 413KB.
useRoomData and arcade room code now only loaded by arcade pages.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The `interaction` object from useInteractionStateMachine() was included
in several useEffect dependency arrays. However, this hook returns a
useMemo'd object whose dependencies include objects like cursorPosition
and magnifierPosition that get new references on every state change
(even if values are the same).
This caused an infinite loop:
1. State change → new interaction object reference
2. useEffect runs → dispatches event → state change
3. Repeat → "Maximum update depth exceeded" error
Fix: Extract specific stable values from interaction (dispatch, setMode,
state.mode) before using them in dependency arrays, instead of using
the whole interaction object.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The fancy interactive 404 page was importing AbacusReact and other heavy
dependencies, causing ~4.7MB of JavaScript to be loaded on EVERY page
(Next.js App Router preloads not-found chunks for soft navigation).
Changes:
- Extract heavy 404 content to InteractiveNotFound component
- Use next/dynamic with ssr: false to lazy-load only when 404 occurs
- Simplify practice/[studentId]/not-found.tsx by removing PageWithNav
- Remove unused manifest.json reference from layout.tsx (was 404ing)
- Simplify /api/classrooms/mine to use getDbUserId helper
Also includes worksheet parsing review progress feature:
- Add useReviewProgress hook for tracking review state
- Add WorksheetReviewSummary component for summary display
- Add review-progress API endpoint
- Integrate into OfflineWorkSection and SummaryClient
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Auto-generated fresh SVG examples and unified gallery from latest templates.
Includes comprehensive crop mark demonstrations with before/after comparisons.
Files updated:
- packages/templates/gallery-unified.html
🤖 Generated with GitHub Actions
Co-Authored-By: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
- Fix dual cancel button bug: clear preview state before starting
re-parse mutation, not after
- Add cancellation check to parse route: re-read status from DB after
LLM completes, discard results if status was reset to null
- Add same cancellation check to parse-selected route for consistency
- Set parsingStatus to 'processing' at start of parse-selected so
cancellation can be detected
Previously, clicking cancel would reset the DB status to null, but
when the LLM call completed, it would overwrite with new results.
Now both routes check if parsing was cancelled before writing.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add cancel button to gallery thumbnails when parsing in progress
- Add cancel button to fullscreen PhotoViewerEditor when parsing
- Add cancel button for re-parsing in progress (fullscreen view)
- Track reparsingPhotoId to show correct status per-photo in both views
- Gallery shows "Re-parsing..." badge on specific photo being re-parsed
- DELETE endpoint resets parsing status for immediate retry
Also includes codebase-wide formatting from biome.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Users can now immediately cancel a parsing operation instead of waiting
for it to complete or fail. This prevents attachments from getting stuck
in "processing" state with no way to recover.
Changes:
- Added DELETE endpoint to parse API route to cancel/reset parsing
- Added useCancelParsing mutation hook with optimistic updates
- Added cancel button (✕) to the "Analyzing..." badge in OfflineWorkSection
- Cancel button resets the attachment to unparsed state
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Two fixes:
1. Outer catch block now updates status to "failed" if an error occurs
before the parsing starts (e.g., file read error). Previously, the
attachment would stay in "processing" state forever.
2. Allow retry if attachment has been stuck in "processing" for > 5 mins.
The `parsedAt` timestamp is now set when entering "processing" state
to track how long it's been processing.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
OpenAI's strict JSON schema mode requires ALL properties to be in the
`required` array. The `notes` and `excluded` fields used `.optional()`
which caused them to be excluded from `required`, resulting in API error:
"Missing 'notes'" in required array.
Fix:
- `notes`: Changed from `.nullable().optional()` to just `.nullable()`
- `excluded`: Changed from `.optional()` to `.default(false)`
Both fields are now properly included in the required array while still
allowing null/false values as defaults.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The Docker build was failing because packages/llm-client/package.json
was not being copied during the dependency installation stages. This
caused zod and other llm-client dependencies to not be installed,
resulting in "Cannot find module 'zod'" errors during the build.
Added the COPY command for llm-client/package.json in both:
- base stage (line 30) - for build dependencies
- deps stage (line 81) - for production dependencies
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The zod package was listed as peerDependency which isn't automatically
installed in Docker production builds. Moving it to regular dependencies
ensures the package builds correctly in CI.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove per-problem Exclude/Restore buttons from EditableProblemRow
- Add bulk "Exclude Selected" and "Restore Selected" buttons to selection toolbar
- Add toast notifications for approve success/failure
- Close viewer and refresh page after successful approve to show updated session
- Fix mutation to properly await res.json() before returning
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix thumbnail thrashing when adjusting bounding boxes by splitting
generation into two effects: initial load vs incremental updates
- Use fixed 48x32px thumbnail container with objectFit: contain to
maintain aspect ratio while preventing layout shifts
- Move selection toolbar outside scrollable area to prevent row jumps
- Replace local debug toggle with global visual debug context
- LLM debug panel now only shows when visual debug is enabled
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Selective Re-parsing:
- Add parse-selected API endpoint for re-parsing specific problems
- Support user-adjusted bounding boxes that persist across re-parses
- Add crop-utils for extracting problem regions from worksheet images
LLM Metadata Tracking:
- Store JSON schema, prompt, and raw response in database
- Add debug panel in PhotoViewerEditor to inspect LLM details
- Add migrations for llm_metadata, llm_prompt, llm_json_schema columns
UI Improvements:
- Remove selection mode toggle - problems always selectable
- Show checkboxes on hover only (no layout jump)
- Move selection toolbar to fixed footer outside scrollable area
- Add BoundingBoxOverlay component for visual problem selection
- Add EditableProblemRow with hover-based checkbox visibility
- Unified hover highlighting across checkbox and problem cells
Also includes:
- Fix approve route to handle excluded problems correctly
- Add DebugContentModal for viewing prompts/responses
- Update LLM client to return metadata in responses
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When canUpload is false but there's no specific remediation available
(e.g., due to a bug in access control), show a generic "Unable to upload
photos" banner instead of silently hiding the upload buttons.
This ensures users see feedback when access is unexpectedly denied,
rather than being confused by missing UI elements.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Worksheet Parsing UI (Slices 1-2):
- Add parse button to OfflineWorkSection thumbnails and PhotoViewerEditor
- Create ParsedProblemsList component to display extracted problems
- Add useWorksheetParsing hook with mutations for parse/review/approve
- Add attachmentKeys to queryKeys for cache management
- Wire up parsing workflow in SummaryClient
Fix parent upload access:
- Change /api/players/[id]/access to use getDbUserId() instead of getViewerId()
- Guest users' guestId was not matching parent_child.parent_user_id
- Parents can now see upload/camera buttons in offline work section
Fix curriculum type errors:
- Add missing 'advanced' property to createFullSkillSet()
- Fix enabledRequiredSkills -> enabledAllowedSkills in problem-generator
- Remove incorrect Partial<> wrapper from type casts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Part A: @soroban/llm-client package
- Multi-provider support (OpenAI, Anthropic) via env vars
- Zod schema validation for structured LLM responses
- Retry loop with validation error feedback in prompt
- Progress indication hooks for UI feedback
- Vision support for image analysis
Part B: Worksheet parsing feature
- Zod schemas for parsed worksheet problems
- LLM prompt builder for abacus workbook images
- Parser using llm.vision() with retry logic
- Session converter to create SlotResults for BKT
- Database migration for parsing columns
- API routes: /parse, /review, /approve workflow
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Make session observer modal/page fully responsive for all screen sizes
- Replace absolute positioning with flex layout for problem + abacus
- Create MobileResultsSummary component for compact results on small screens
- Full-screen modal on mobile, centered dialog on desktop
- Stack problem and abacus vertically on small screens (<640px)
- Reduce vertical spacing to eliminate scrolling on mobile
- Hide desktop results panel on mobile, show compact summary chip
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add VISION_DOCK_INTEGRATION_PLAN.md for vision dock architecture
- Add VisionCameraControls.stories.tsx for storybook
- Update .gitignore to exclude venv, uploads, and training data
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add ENABLE_AUTO_DETECTION flag to ObserverVisionFeed.tsx to hide the
useless detection overlay that always showed "---" and "0%" since
auto-detection is globally disabled. This matches the pattern already
used in DockedVisionFeed.tsx.
Also includes minor formatting fixes from Biome.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix race condition in useRemoteCameraDesktop where session ID wasn't
saved before socket connection check, preventing auto-reconnect
- Same fix in useRemoteCameraPhone for phone-side connection
- Fix "new session" button in RemoteCameraQRCode - properly clears old
session and creates new one using prevRef to detect state changes
- Show full QR code UI with copyable URL (removed compact mode)
- Redesign AbacusVisionBridge UI: camera feed as hero, toolbar on feed,
collapsible crop settings, source selector as tabs
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add explicit activeCameraSource field to VisionConfig to track which
camera is in use (local vs phone), fixing button visibility bugs when
switching between camera sources
- Simplify calibration UI by removing the confusing "Auto/Manual" mode
toggle, replacing with a cleaner crop status indicator
- Remove calibration requirement from isVisionSetupComplete for local
camera since auto-crop runs continuously when markers are detected
- Update DockedVisionFeed to use activeCameraSource instead of inferring
from which configs are set
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Merge VisionSetupModal and AbacusVisionBridge into unified UI
- Remove two-step configuration process (no more "Configure Camera" button)
- Add vision control props to AbacusVisionBridge:
- showVisionControls, isVisionEnabled, isVisionSetupComplete
- onToggleVision, onClearSettings callbacks
- Add Enable/Disable Vision and Clear Settings buttons to bridge footer
- Simplify VisionSetupModal from ~257 to ~93 lines
- Modal is now draggable via framer-motion (built into AbacusVisionBridge)
User experience: Open modal → immediately see camera feed and all controls
in one place. Drag modal anywhere. Configure, enable/disable, close.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix camera source switching: clear remoteCameraSessionId in context when
switching to local camera so DockedVisionFeed uses the correct source
- Fix modal drag during calibration: disable framer-motion drag when
calibration overlay is active to allow handle dragging
- Fix initial camera source: pass initialCameraSource prop to
AbacusVisionBridge so it shows phone camera when reconfiguring remote
- Extend session TTL from 10 to 60 minutes for remote camera sessions
- Add localStorage persistence for remote camera session IDs
- Add auto-reconnect logic for both desktop and phone hooks
- Add comprehensive tests for session-manager, useRemoteCameraDesktop,
and useRemoteCameraPhone hooks
- Guard test setup.ts for node environment (HTMLImageElement check)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- VisionIndicator.test.tsx: tests for rendering, status indicator, click behavior, accessibility
- ObserverVisionFeed.test.tsx: tests for image display, detected value, live/stale indicator
- useSessionBroadcast.vision.test.ts: tests for sendVisionFrame socket emission
- useSessionObserver.vision.test.ts: tests for visionFrame receiving and cleanup
- MyAbacusContext.vision.test.tsx: tests for vision config state and callbacks
Also fixes:
- useSessionObserver: clear visionFrame and transitionState on stopObserving
- test/setup.ts: add canvas Image mock to prevent jsdom errors with data URIs
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ENABLE_AUTO_DETECTION flag (set to false) in DockedVisionFeed
- Conditionally import detection modules for tree-shaking when disabled
- Guard all detection processing, loops, and value handlers
- Hide detection overlay when auto-detection is disabled
- Remove vision toggle button from ActiveSession (no longer needed)
- Clean up unused imports and code
- Format fixes from biome
The camera feed still works for observation mode, but the ML/CV
bead detection is disabled until accuracy is improved.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The isVisionSetupComplete flag was only checking for local camera
setup (cameraDeviceId + calibration), which caused remote camera
mode to be treated as "not configured" even when connected.
Now considers vision setup complete if either:
- Local camera: has camera device AND calibration
- Remote camera: has remote session ID (phone handles calibration)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove unnecessary debug logging from vision components
that was used during development.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Wire up the vision broadcast pipeline:
1. DockedVisionFeed captures rectified frames from canvas and emits
them at 5fps via the context's emitVisionFrame callback
2. PracticeClient wires setVisionFrameCallback to call sendVisionFrame
from useSessionBroadcast, connecting the context to the socket
3. useSessionBroadcast sends VisionFrameEvent to the session channel
with imageData, detectedValue, and confidence
4. socket-server relays vision-frame events to observers
5. useSessionObserver receives and stores visionFrame for display
6. SessionObserverModal shows ObserverVisionFeed when visionFrame
is available, replacing the interactive AbacusDock with the
student's live camera feed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When switching between local and phone camera, clear the other
source's configuration to prevent stale data.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add vision state management to MyAbacusContext (camera, calibration,
remote session, enabled state)
- Add VisionIndicator component showing vision status on dock header
- Add VisionSetupModal for configuring camera and calibration
- Add DockedVisionFeed component that replaces SVG abacus when vision
is enabled, with:
- Continuous ArUco marker detection for auto-calibration
- OpenCV perspective correction via VisionCameraFeed
- Real-time bead detection and value display
- Support for both local camera and remote phone camera
- Wire AbacusVisionBridge to save config to context via
onConfigurationChange callback
- Update MyAbacus to conditionally render DockedVisionFeed vs
AbacusReact based on vision state
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add beadDetector.ts with intensity-profile-based bead detection (CV approach)
- Integrate CV pipeline for both local camera and remote phone camera feeds
- Add processImageFrame() to frameProcessor for remote camera image processing
- Fix React 18 Strict Mode duplicate session creation in RemoteCameraQRCode
- Add debug logging to remote camera hooks for connection troubleshooting
- Add VisionStatusIndicator for remote camera feed in AbacusVisionBridge
The duplicate session bug was caused by React 18 Strict Mode double-mounting
components and running effects twice with fresh state, which called
createSession() twice and created two different sessions - phone joined
one, desktop subscribed to the other.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add trained CNN model for abacus column digit classification
- model.json: TensorFlow.js layers model (fixed for Keras 3 compatibility)
- group1-shard1of1.bin: quantized model weights (~2.2MB)
- Improve detection performance and stability
- Throttle inference to 5fps (was running every animation frame)
- Lower stability threshold: 3 consecutive frames (was 10)
- Lower confidence threshold: 50% (was 70%)
- Clean up debug logging from development
Note: Model trained on synthetic data, accuracy on real images is limited.
Future work: retrain on real abacus photos for better accuracy.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Only show camera controls section when there's something to display:
- Flip button: only if multiple cameras
- Torch button: only if torch available
- Whole section: only if either button would be shown
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Physical Abacus Columns Setting:
- Add physicalAbacusColumns to AbacusDisplayConfig (default: 4)
- Add database column with migration 0054
- Add slider UI in AbacusDisplayDropdown (range 1-21)
- Update AbacusVisionBridge to use setting instead of calculating from problem
Remote Camera Flash Toggle Fix:
- Add socket events for torch sync (set-torch, torch-state)
- Phone reports torch state to desktop on change/connection
- Desktop can control phone's torch remotely
- Add torch button in AbacusVisionBridge for phone camera mode
- Both local and remote flash toggles now work correctly
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix remote camera autocrop rotation by swapping ArUco corners for phone camera
(detectMarkers assumes Desk View orientation which is 180° rotated)
- Add rotate left/right buttons to CalibrationOverlay for manual calibration
- Fix mode switching bug: switching to auto mode now clears desktop calibration
on phone via new 'remote-camera:clear-calibration' socket event
- Add copy button to QR code URL with visual feedback
- Fix text selection spanning into video feed with userSelect: none
- Add flip camera and torch controls to local camera UI
- Add session persistence for remote camera reconnection
- Fix 4:3 aspect ratio for cropped abacus output
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix hook dependency issues in AbacusVisionBridge by using destructured
stable function references instead of remoteCamera object
- Add proper container dimension tracking for remote camera calibration
overlay to fix coordinate mismatch
- Add rotate180 option to perspectiveTransform to support both Desk View
(camera pointing down, needs 180° rotation) and phone cameras (no rotation)
- Phone camera cropping now uses direct mapping without rotation
The main issues fixed:
1. Hook dependencies were causing effects to run repeatedly when navigating
to remote camera URL in a different window
2. CalibrationOverlay was using hardcoded fallback dimensions instead of
actual container dimensions for remote camera
3. Perspective transform was applying 180° rotation which is wrong for
phone cameras held at normal angles
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Re-run container dimension effect when isCalibrating changes
- Add height checks in addition to width checks for overlay visibility
- Add delayed dimension update to handle layout settling
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Enable using a phone as a remote camera source for abacus vision detection.
The phone handles calibration and perspective correction locally, then
streams cropped frames to the desktop via Socket.IO.
Features:
- QR code generation for easy phone connection
- Auto-calibration with ArUco markers on phone
- Manual calibration option
- Proper OpenCV perspective transform (not bounding box crop)
- Real-time frame streaming with frame rate display
- LAN address support via NEXT_PUBLIC_LAN_HOST env var
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>