chore: change maintenance receipt flow to upload-first with camera option (#162) #182
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?
Relates to #162
Severity: Medium
Problem
The maintenance record receipt scanning feature was mirrored from the fuel log receipt flow but should be closer to the document/manual scan pattern. Currently:
Changes Required
Acceptance Criteria
Plan: Change Maintenance Receipt Flow to Upload-First
Phase: Planning | Agent: Planner | Status: AWAITING_REVIEW (Rev 3 -- QR plan-docs fixes)
Context
The maintenance receipt scanning feature currently mirrors the fuel log receipt flow (camera-first). Users click "Scan Receipt" which immediately opens the camera. File upload is buried as a fallback inside the camera screen when permission is denied. This should be changed to an upload-first pattern where drag-and-drop is the primary option and camera is a secondary choice.
Codebase Analysis Summary
Current flow:
ReceiptCameraButton("Scan Receipt") ->startCapture()-> fullscreenCameraCaptureDialog ->processImage(file, croppedFile)->MaintenanceReceiptReviewModal-> form population viasetValue()Key finding: The hook's
processImage(file, croppedFile?)already handles both paths --croppedFileis optional (imageToProcess = croppedFile || fileinsideprocessImage). No hook or backend changes are needed.Files analyzed (7 files, all read in full):
MaintenanceRecordForm.tsx-- Form with ReceiptCameraButton and Camera DialoguseMaintenanceReceiptOcr.ts-- OCR orchestration hookFileInputFallback.tsx-- Standalone drag-drop componentCameraCapture.tsx-- Camera with internal FileInputFallback fallbackReceiptCameraButton.tsx-- Shared button (fuel-logs + maintenance)maintenance-receipt.types.ts-- OCR typesCameraCapture/types.ts-- Camera component typesArchitecture Decision
Build a new
AddReceiptDialogcomponent with inline drag-and-drop logic rather than embeddingFileInputFallbackinside it. Rationale:FileInputFallbackhas its own header ("Upload Image" + Cancel button) designed for full-screen standalone use. The new dialog needs both upload and camera options visible simultaneously with a single shared header.ReceiptCameraButtonis shared with fuel-logs and will NOT be modified. Instead, it will be replaced with a standard MUI Button in the maintenance form only.Sub-Issues
AddReceiptDialog.tsxMaintenanceRecordForm.tsx, MODIFY:maintenance/CLAUDE.mdMilestone 1: Create AddReceiptDialog Component (refs #183)
New file:
frontend/src/features/maintenance/components/AddReceiptDialog.tsxInline documentation (required JSDoc header):
Component API:
Constants (inline in component file):
Layout (fullScreen MUI Dialog):
Implementation details:
Paperbackground (not black like camera)<input type="file" accept={ACCEPT_STRING}>for click-to-browseFileInputFallbackpattern:ACCEPTED_FORMATS(with HEIC/HEIF extension fallback for inconsistent MIME types: checkfile.name.toLowerCase().endsWith('.heic')or.heif)MAX_FILE_SIZE<Alert severity="error">for invalid filesxsbreakpoint (display: { xs: 'block', sm: 'none' })minHeight: 44for touch targets<Divider>with<Typography>child)Milestone 2: Integrate into MaintenanceRecordForm (refs #184)
Modified file:
frontend/src/features/maintenance/components/MaintenanceRecordForm.tsxChanges:
Imports:
import { ReceiptCameraButton } from '../../fuel-logs/components/ReceiptCameraButton';import { AddReceiptDialog } from './AddReceiptDialog';import ReceiptIcon from '@mui/icons-material/Receipt';State (add after the
capturedReceiptFileuseState declaration):const [showAddReceiptDialog, setShowAddReceiptDialog] = useState(false);Replace the
ReceiptCameraButtonJSX block (the<Box>wrapper containing<ReceiptCameraButton>inside the{/* Receipt Scan Button */}comment):<Box sx={{ display: 'flex', justifyContent: 'center', mb: 3, pb: 2, ... }}><ReceiptCameraButton ... /></Box>blockAdd AddReceiptDialog (insert after the
{/* Camera Capture Modal */}Dialog block, before the{/* OCR Processing Overlay */}Backdrop):Keep unchanged (all existing modals/overlays remain as-is):
<Dialog open={isCapturing}>) -- still needed for "Take Photo" path<Backdrop open={isProcessing}>){ocrResult && <MaintenanceReceiptReviewModal ... />})<UpgradeRequiredDialog featureKey="maintenance.receiptScan" ... />){ocrError && <Dialog ... />})Update CLAUDE.md -- Add new entry to
frontend/src/features/maintenance/CLAUDE.mdKey Files table:Data flow after changes:
Milestone 3: Viewport Testing (refs #185)
Test at three breakpoints using browser automation:
Verify:
Files NOT Changed
useMaintenanceReceiptOcr.tsprocessImage(file, croppedFile?)already handles both pathsFileInputFallback.tsxCameraCapture.tsxReceiptCameraButton.tsxmaintenance-receipt.types.tsRisk Assessment
processImage()-> review -> form populationBranch and PR
issue-182-maintenance-receipt-upload-first(from main)chore: change maintenance receipt flow to upload-first (#182)Fixes #182,Fixes #183,Fixes #184,Fixes #185Verdict: AWAITING_REVIEW | Next: Plan review cycle (QR plan-completeness -> TW plan-scrub -> QR plan-code -> QR plan-docs)
QR Review: Plan Completeness
Phase: Plan-Review | Agent: Quality Reviewer | Status: PASS
Checklist
Findings
No issues found. The plan demonstrates:
Acceptance criteria coverage: All 7 ACs explicitly mapped to milestones (M1: dialog structure with two options, M2: button replacement + integration, M3: viewport testing)
Sub-issue decomposition: Clean 1:1 mapping (M1→#183, M2→#184, M3→#185) following workflow-contract.json pattern
File scope completeness: CREATE: AddReceiptDialog.tsx | MODIFY: MaintenanceRecordForm.tsx | DELETE: none | NOT_CHANGED: 6 files with rationale (processImage already handles both paths, ReceiptCameraButton shared with fuel-logs)
Data flow integrity: Upload path and camera path both converge at processImage() → review modal → form population. No orphaned work.
Testing rigor: M3 explicitly covers three viewports (320px, 768px, 1920px) with functional validation for both upload and camera paths plus error states
Risk mitigation: Low-risk pure frontend change, fuel-logs flow unaffected (ReceiptCameraButton untouched), same OCR pipeline preserved
Dependency ordering: M1 (create component) → M2 (integrate) → M3 (test) is logically sound
Verdict
The plan is structurally complete and ready for technical writing review. All acceptance criteria are covered, sub-issues are properly scoped, file changes are explicit, data flow is coherent, and testing is comprehensive.
Verdict: PASS | Next: TW plan-scrub
TW Review: Plan Scrub
Phase: Plan-Review | Agent: Technical Writer | Status: FAIL
Checklist
Findings
1. Temporal Contamination (FAIL -- blocking)
The plan contains 11 explicit line-number references across Milestone 2 and the Codebase Analysis Summary. These are location directives that violate the Timeless Present Rule and will become stale as soon as Milestone 1 modifies any imports or adds code above the referenced sections.
Specific instances in Milestone 2:
ReceiptCameraButtonimport from the fuel-logs imports block"capturedReceiptFilestate declaration"<ReceiptCameraButton>JSX block and its wrapping<Box>(insideCardContent, before the<form>tag)"<Dialog>block"Codebase Analysis Summary:
imageToProcess = croppedFile || file"processImageusescroppedFile || filefallback internally"The line numbers are currently accurate against the codebase, but they are fragile. Structural anchors (component names, variable names, JSX patterns) are resilient to code shifts and should replace all line-number references.
2. Completeness Gaps (non-blocking, advisory)
a) File validation constants not specified for AddReceiptDialog:
The plan says "accepted formats (JPEG, PNG, HEIC/HEIF with extension fallback), max 10MB" but does not provide the MIME type strings or max-size constant. The existing
FileInputFallbackreceives these as props. Since AddReceiptDialog implements inline drag-drop, the developer must know the exact values.Suggestion: Add to Milestone 1 implementation details:
b) Hidden input
acceptattribute not specified:The hidden
<input type="file">needs anacceptattribute string. Suggest adding:accept="image/jpeg,image/png,image/heic,image/heif,.heic,.heif"Verdict
FAIL -- The 11 line-number references constitute temporal contamination that will produce incorrect instructions after even minor edits. All line-number references must be replaced with structural anchors (component names, JSX patterns, variable declarations). The completeness gaps are advisory and can be addressed at the same time.
Verdict: FAIL | Next: Revise plan to replace all line-number references with structural anchors, then re-submit for TW plan-scrub
QR Review: Plan Docs
Phase: Plan-Review | Agent: Quality Reviewer | Status: FAIL
Checklist
Findings
1. Missing CLAUDE.md Update (FAIL - blocking)
Issue: The plan creates a new component
AddReceiptDialog.tsxbut does not specify updatingfrontend/src/features/maintenance/CLAUDE.mdto document it.Evidence: The current CLAUDE.md has a "Key Files" section listing important components like:
MaintenanceRecordForm.tsx- Form for manual record creationMaintenanceScheduleReviewScreen.tsx- Dialog for reviewing OCR-extracted maintenance itemsSubtypeCheckboxGroup.tsx- Multi-select checkbox groupWhy this matters: AddReceiptDialog is a significant user-facing component that changes the primary receipt entry flow. Future AI agents working on maintenance receipt features need to know this component exists and what it does.
Suggested fix: Add to Milestone 2 (after integration is complete):
Update
frontend/src/features/maintenance/CLAUDE.mdKey Files section:2. Missing Inline Documentation Specification (FAIL - blocking)
Issue: The plan does not specify adding JSDoc
@ai-summaryand@ai-contextcomments to the new AddReceiptDialog.tsx file.Evidence: Project pattern verified in multiple files (FileInputFallback.tsx, MaintenanceRecordForm.tsx, etc.):
Why this matters: Inline documentation is a project standard for helping AI agents quickly understand component purpose without reading full implementation. Missing this violates the "AI Context Efficiency" principle in CLAUDE.md.
Suggested fix: Add to Milestone 1 implementation details:
Add JSDoc header to AddReceiptDialog.tsx:
Verdict
The plan has comprehensive data flow documentation and follows the no-emoji rule, but it lacks two critical documentation specifications: CLAUDE.md update for the new component and inline JSDoc comments. Both are project standards for AI context efficiency.
Verdict: FAIL | Next: Revise plan to add CLAUDE.md update step and inline documentation specification
QR Review: Plan Code (RULE 0/1/2)
Phase: Plan-Review | Agent: Quality Reviewer | Status: PASS
RULE 0 (CRITICAL) - Production Reliability
No RULE 0 violations found.
Verified:
URL.createObjectURL(imageToProcess), stores it inreceiptImageUrlstate. Cleanup happens in acceptResult() at line 301 and reset() at line 312 viaURL.revokeObjectURL(receiptImageUrl). The plan preserves this existing pattern - no new blob URLs created by AddReceiptDialogRULE 1 (HIGH) - Project Conformance
No RULE 1 violations found.
Verified:
display: { xs: 'block', sm: 'none' }visible below drop zone on xs breakpoint. Touch targets specified asminHeight: 44for all interactive elements matching project standard (44px minimum per CLAUDE.md)AddReceiptDialogfollows existing dialog pattern (AddDocumentDialog, AddFuelLogDialog). Props interfaceAddReceiptDialogPropsfollows TypeScript camelCase convention. File placementfrontend/src/features/maintenance/components/AddReceiptDialog.tsxmatches feature capsule organizationAdvisory note (not blocking): The plan specifies fullScreen Dialog with AppBar/Toolbar header in the layout ASCII diagram, but the proposed JSX in M2 shows a standard Dialog structure without fullScreen or AppBar mentioned. Cross-referencing existing patterns: AddFuelLogDialog uses
fullScreen={isSmallScreen}conditional (line 24), while the ASCII layout shows AppBar which implies fullScreen mode. This should be clarified during implementation, but the pattern is well-established in the codebase so not flagging as HIGH severity.RULE 2 (SHOULD_FIX) - Structural Quality
No RULE 2 violations found.
Verified:
imageToProcess = croppedFile || file). No orphaned work, no god objectsConsidered But Not Flagged
Constants placement: ACCEPTED_FORMATS, MAX_FILE_SIZE, ACCEPT_STRING declared inline in component file rather than in a shared constants module. This is minor duplication (FileInputFallback has these as props from caller), but the plan states FileInputFallback won't be modified to avoid fuel-logs regression risk. The duplication is isolated to 3 const declarations and acceptable for this low-risk pure frontend change.
Dialog fullScreen behavior: The plan's ASCII layout shows AppBar/Toolbar (implies fullScreen), but implementation details don't explicitly specify fullScreen prop or conditional logic. However, existing patterns (AddFuelLogDialog, AddDocumentDialog) demonstrate this is well-understood in the codebase. Not flagging as the developer agent will reference existing dialog patterns during implementation.
Hidden input accept attribute: Plan specifies
ACCEPT_STRING = ACCEPTED_FORMATS.join(',')constant but doesn't show it being used in the hidden input's accept attribute. TW review flagged this as completeness gap (advisory, non-blocking). The pattern is clear from FileInputFallback line 115 and will be obvious during implementation.Verdict
PASS - The plan demonstrates production-grade reliability, full conformance to project standards (mobile+desktop, naming, MUI patterns, touch targets), and sound structural design (no duplication concerns, clear data flow, single responsibility). All three quality rules satisfied.
File validation handles edge cases with MIME type checking and extension fallback. Resource cleanup is handled by existing hook patterns. Error handling is comprehensive. Mobile and desktop viewports are explicitly tested. Component naming and API design follow established patterns. Data flow is well-documented and converges correctly at processImage(). No god objects or unnecessary duplication.
Verdict: PASS | Next: QR plan-docs
QR Review: Plan Docs (Re-Review)
Phase: Plan-Review | Agent: Quality Reviewer | Status: PASS
Checklist
frontend/src/features/maintenance/CLAUDE.mdwith AddReceiptDialog entryFindings
Previous failures successfully resolved:
CLAUDE.md Update (FIXED): M2 step 6 now explicitly specifies adding an entry to
frontend/src/features/maintenance/CLAUDE.mdKey Files table with component description and when-to-read context. The entry format matches existing patterns in the file.Inline Documentation (FIXED): M1 now includes a dedicated "Inline documentation (required JSDoc header)" section with proper @ai-summary and @ai-context JSDoc comments. The summary clearly states the component purpose ("Upload-first dialog for maintenance receipt input with drag-drop and camera options") and the context explains the architectural change ("Replaces camera-first ReceiptCameraButton pattern") plus data flow integration ("Both paths call parent callbacks that feed into useMaintenanceReceiptOcr.processImage()").
Additional verification:
The plan now meets all documentation requirements for AI context efficiency as specified in the project CLAUDE.md guidelines.
Verdict
All previous plan-docs failures have been addressed. The revised plan includes both CLAUDE.md update specification and inline JSDoc documentation requirements. Documentation is comprehensive, well-structured, and follows project standards.
Verdict: PASS | Next: Plan APPROVED - ready for implementation
Plan Review Cycle: COMPLETE
Phase: Plan-Review | Agent: Planner | Status: APPROVED
Review Results
Plan Approved (Rev 3)
The plan is ready for execution. Summary of milestones:
AddReceiptDialog.tsx-- new fullscreen dialog with drag-drop upload (primary) and camera option (secondary)MaintenanceRecordForm.tsx-- replace ReceiptCameraButton with "Add Receipt" button, wire both paths to existing OCR pipeline, update CLAUDE.mdVerdict: APPROVED | Next: Create branch and begin execution