feat: Improve VIN photo capture camera crop experience #123
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Improve the camera crop feature of the VIN photo capture flow. Currently there are two usability issues with the crop step after taking a VIN photo.
Current Behavior
Overlay/crop mismatch: The dashed rectangle guide overlay shown during camera capture ("Position VIN plate within the guide") does not match what the resulting cropped image looks like. The captured photo appears different from what was framed in the overlay.
Crop box is not adjustable: After capturing, the user can "Tap and drag to select crop area", but once the crop box is drawn it cannot be resized or repositioned. The user must start over if the crop selection is incorrect.
Expected Behavior
Consistent overlay-to-crop: The overlay guide shown during capture should accurately represent the area that will be cropped. What the user sees in the viewfinder should match the resulting image.
Adjustable crop box: After drawing an initial crop selection, the user should be able to:
Acceptance Criteria
Screenshots
See attached screenshots showing:
Plan: Improve VIN Photo Capture Camera Crop
Phase: Planning | Agent: Planner | Status: AWAITING_REVIEW
Overview
The VIN camera crop has two root cause issues: (1) the GuidanceOverlay positions a centered 85%-width guide via CSS flexbox, but the CropTool defaults to
{x:10, y:10, width:80, height:80}-- these systems share no coordinate data, so the crop area appears at the wrong position; (2) with VIN aspect ratio 6:1, the crop becomes a thin 13.33%-height strip at the top of the image with 24px touch targets, making handles nearly invisible on mobile.The fix uses Approach A: calculate guidance-aware initial crop coordinates from the existing
GUIDANCE_CONFIGSand pass them through touseImageCropvia the existinginitialCropprop. Touch targets are increased to 44px per Apple HIG and project conventions.Planning Context
Decision Log
useImageCropalready acceptsinitialCropprop -> minimal code changes with high confidence and no new abstractionsgetInitialCropForGuidance()in types.tsexecuteCrop()inuseImageCrop.tsproduces the same JPEG 0.92 output regardless of initial crop position -> OCR preprocessing pipeline receives identical image format -> crop position change is frontend-onlyRejected Alternatives
Constraints & Assumptions
useImageCropalready accepts optionalinitialCropparameter (useImageCrop.ts:11)GUIDANCE_CONFIGSdefines aspect ratios and overlay width percentages (types.ts:104-120)Known Risks
executeCropimage quality unchangedexecuteCropusescropAreapercentages to calculate pixel coords on the source image. Changing initial position does not affect the drawImage or toBlob logic.Invisible Knowledge
Architecture
Data Flow
Invariants
getInitialCropForGuidance()output must produce a CropArea that, when rendered on the captured image, visually matches the GuidanceOverlay position from the viewfinderTradeoffs
Milestones
Milestone 1: Bridge overlay position to crop initial coordinates
Files:
frontend/src/shared/components/CameraCapture/types.ts,frontend/src/shared/components/CameraCapture/CameraCapture.tsx,frontend/src/shared/components/CameraCapture/CropTool.tsxRequirements:
getInitialCropForGuidance(type: GuidanceType): CropArea | undefinedto types.ts{x: 7.5, y: 42.92, width: 85, height: 14.17}{x: 26.67, y: 15, width: 46.67, height: 70}{x: 22.95, y: 15, width: 54.09, height: 70}initialCropoptional prop to CropToolPropsAcceptance Criteria:
Tests:
frontend/src/shared/components/CameraCapture/__tests__/getInitialCropForGuidance.test.tsCode Changes:
Milestone 2: Improve crop handle touch targets for mobile
Files:
frontend/src/shared/components/CameraCapture/CropTool.tsxFlags: needs conformance check (44px touch target requirement)
Requirements:
Acceptance Criteria:
Tests:
Code Changes:
Milestone 3: Documentation
Files:
frontend/src/shared/components/CameraCapture/README.md(if needed)Requirements:
Acceptance Criteria:
Source Material: Invisible Knowledge section of this plan
Milestone Dependencies
M2 depends on M1 because correct crop positioning must exist before handle improvements matter. M3 depends on both being complete.
Verdict: AWAITING_REVIEW | Next: Plan review cycle (QR plan-completeness -> TW plan-scrub -> QR plan-code -> QR plan-docs)
QR Review: Plan Completeness + Plan Code
Phase: Plan-Review | Agent: Quality Reviewer | Status: NEEDS_CHANGES
Findings
RULE 0 (CRITICAL):
getInitialCropForGuidancelacks bounds validation -- if GUIDANCE_CONFIGS change, coordinates could exceed 0-100 rangeaspectRatio === 1falls through to height-constrained path -- should use>= 1RULE 1 (HIGH):
3. At 320px viewport height, VIN crop strip is ~45px tall. With 44px corner handles, they nearly fill the entire strip height. Handles need to be clamped or use a responsive approach.
4. Diff line numbers are approximate (acceptable per diff-format spec -- context lines are the authoritative anchors)
RULE 2 (SHOULD_FIX):
5. Centering calculation
(100 - dimension) / 2duplicated in both paths -- minor, acceptable for a 2-path functionPlan Revisions Required
Math.min/Math.maxbounds clamping to returned CropAreaconfig.aspectRatio > 1toconfig.aspectRatio >= 1Verdict: NEEDS_CHANGES | Next: Revise plan, re-review
Plan Revision: Addressing QR Findings
Phase: Planning | Agent: Planner | Status: AWAITING_REVIEW
Changes from QR Review
RULE 0 Fix 1 -- Bounds validation:
getInitialCropForGuidancenow clamps all values to 0-100 range usingMath.max(0, Math.min(100, value)). This guards against future GUIDANCE_CONFIGS changes producing out-of-bounds coordinates.RULE 0 Fix 2 -- Aspect ratio boundary: Changed condition from
config.aspectRatio > 1toconfig.aspectRatio >= 1so square (1:1) aspect ratios use the width-constrained path.RULE 1 Fix 3 -- 320px viewport handle sizes: Use
useTheme+useMediaQueryto detect small screens (< 400px height) and reduce handle size to 32px. This keeps handles within the VIN crop strip on very small viewports while maintaining 44px on normal mobile and desktop.Revised Code: getInitialCropForGuidance (types.ts)
Revised Code: Responsive Handle Sizes (CropTool.tsx)
Handle component updated to use passed config instead of hardcoded values:
CropHandle component accepts config prop:
Updated Decision Log Entry
useMediaQuery('(max-height: 400px)')to detect compact viewports -> 32px handles on compact, 44px on standard -> maintains usability without overflowing> 1sends to height-constrained path which uses 70% height ->>= 1sends to width-constrained path using 85% width -> width-constrained is more appropriate for square guides on wide screensUpdated Test Scenarios (M1)
Add to existing scenarios:
Updated Known Risks
Verdict: AWAITING_REVIEW | Next: Re-review QR plan-completeness
Plan Review Results
Phase: Plan-Review | Agent: Multiple | Status: PASS
QR Plan-Completeness Re-Review: PASS
All three RULE 0/1 findings addressed. Plan structure, decision log, tests, and risks verified complete.
TW Plan-Scrub: PASS (with 2 minor corrections)
Both corrections will be applied during execution.
QR Plan-Code: Addressed in revision
All RULE 0/1/2 findings resolved. No remaining blockers.
Verdict: PLAN APPROVED | Next: Create branch, begin execution
egullickson referenced this issue2026-02-11 03:04:24 +00:00