- Create CLAUDE.md for GuidePage directory with architecture docs
- Create CLAUDE.md index for pages/ directory
Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Add Guide link to public nav bar (desktop + mobile) in HomePage
- Add Guide link to authenticated sidebar in Layout.tsx
- Add Guide link to HamburgerDrawer with window.location guard
- Add GuidePage integration tests (6 test scenarios)
- Remove old PDF user guide at public/docs/v2026-01-03.pdf
Co-Authored-By: Claude Opus 4.6 <[email protected]>
All 10 guide sections converted from USER-GUIDE.md to styled React
components using GuideTable and GuideScreenshot shared components.
Sections 1-5: Getting Started, Dashboard, Vehicles, Fuel Logs, Maintenance.
Sections 6-10: Gas Stations, Documents, Settings, Subscription Tiers, Mobile Experience.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Create GuidePage with responsive layout (sticky TOC sidebar desktop, collapsible accordion mobile)
- Add GuideTableOfContents with scroll-based active section tracking
- Create GuideScreenshot and GuideTable shared components
- Add guideTypes.ts with section metadata for all 10 sections
- Add lazy-loaded /guide route in App.tsx with public access
- Placeholder section components for all 10 guide sections
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Rename "Schedule Maintenance" to "Add Maintenance", match contained
button style to "Add Fuel Log", and open inline MaintenanceRecordForm
dialog on click. Applied to both desktop and mobile views.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Root cause: React fires child effects before parent effects. App's URL
sync effect called history.replaceState() on /callback, stripping the
?code= and &state= query params before Auth0Provider's useEffect could
read them via hasAuthParams(). The SDK fell through to checkSession()
instead of handleRedirectCallback(), silently failing with no error.
Guard both URL sync effects to skip on /callback, /signup, /verify-email.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Add allKeys() to IndexedDBStorage to eliminate Auth0 CacheKeyManifest
fallback, revert set()/remove() to non-blocking persist, add auth error
display on callback route, remove leaky force-auth-check interceptor,
and migrate debug console calls to centralized logger.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
loadCacheFromDB used store.getAll() which returns raw values, not
key-value pairs. The item.key check always failed, so memoryCache
was empty after every page reload. Auth0 SDK state stored before
redirect was lost on mobile Safari (no bfcache).
Also fixed set()/remove() to await IDB persistence so Auth0 state
is fully written before loginWithRedirect() navigates away.
Added 10s timeout on callback loading state as safety net.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
URL.createObjectURL on a PDF creates a blob URL that cannot render in
an img tag, showing broken image alt text. Skip preview creation for
PDF files so the review modal displays without a thumbnail.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
The 5s cloud timeout was too tight for the initial WIF authentication
which requires 3 HTTP round-trips (STS, IAM credentials, resource
manager). First call took 5.5s and was discarded, falling back to slow
CPU-based PaddleOCR. Increased to 10s to accommodate cold-start auth.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
pdf2image requires poppler-utils which is not installed in the OCR
container. PyMuPDF is already in requirements.txt and can render PDF
pages to PNG at 300 DPI natively without extra system dependencies.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
The backend SUPPORTED_IMAGE_TYPES set excluded application/pdf, returning
415 before the request ever reached the OCR microservice. Added PDF to
the allowed types in both controller and service validation layers.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
The receipt extractor only accepted image MIME types, rejecting PDFs at
the OCR layer. Added application/pdf to supported types and PDF-to-image
conversion (first page at 300 DPI) before OCR preprocessing.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
URL-to-screen sync on mount and screen-to-URL sync via replaceState
enable direct URL navigation, page refresh, and bookmarks on mobile.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Create shared getVehicleLabel/getVehicleSubtitle in core/utils with
VehicleLike interface. Replace all direct year/make/model concatenation
across 17 consumer files to prevent null values in vehicle names.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Backend: Add authenticated endpoints for pending association CRUD
(GET/POST/DELETE /api/email-ingestion/pending). Service methods for
resolving (creates fuel/maintenance record) and dismissing associations.
Frontend: New email-ingestion feature with types, API client, hooks,
PendingAssociationBanner (dashboard), PendingAssociationList, and
ResolveAssociationDialog. Mobile-first responsive with 44px touch
targets and full-screen dialogs on small screens.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Extract all notification logic from EmailIngestionService into
dedicated EmailIngestionNotificationHandler class
- Add notification_logs entries for every email sent (success/failure)
- Add in-app user_notifications for all error scenarios (no vehicles,
no attachments, OCR failure, processing failure)
- Update email templates with enhanced variables: merchantName,
totalAmount, date, guidance
- Update pending vehicle notification title to 'Vehicle Selection Required'
- Add sample variables for receipt templates in test email flow
Co-Authored-By: Claude Opus 4.6 <[email protected]>
- New ReceiptClassifier module with keyword-based classification for
fuel vs maintenance receipts from email text and OCR raw text
- Classifier-first pipeline: classify from email subject/body keywords
before falling back to OCR-based classification
- Fuel keywords: gas, fuel, gallons, octane, pump, diesel, unleaded,
shell, chevron, exxon, bp
- Maintenance keywords: oil change, brake, alignment, tire, rotation,
inspection, labor, parts, service, repair, transmission, coolant
- Confident classification (>= 2 keyword matches) routes to specific
OCR endpoint; unclassified falls back to both endpoints + rawText
classification + field-count heuristic
Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Add tier-gated "Scan Receipt" button to MaintenanceRecordForm
- Wire useMaintenanceReceiptOcr hook with CameraCapture and ReviewModal
- Auto-populate form fields from accepted OCR results via setValue
- Upload receipt as document and pass receiptDocumentId on record create
- Show receipt thumbnail + "View Receipt" button in edit dialog
- Add receipt indicator chip on records list rows
- Add receiptDocumentId and receiptDocument to frontend types
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Add useMaintenanceReceiptOcr hook mirroring fuel receipt OCR pattern,
MaintenanceReceiptReviewModal with confidence indicators and inline editing,
and maintenance-receipt.types.ts for extraction field types. Includes
category/subtype suggestion via keyword matching from service descriptions.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Add receipt_document_id FK on maintenance_records, update types/repo/service
to support receipt linking on create and return document metadata on GET.
Add OCR proxy endpoint POST /api/ocr/extract/maintenance-receipt with
tier gating (maintenance.receiptScan) through full chain: routes -> controller
-> service -> client.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
- New MaintenanceReceiptExtractor: Gemini-primary extraction with regex
cross-validation for dates, amounts, and odometer readings
- New maintenance_receipt_validation.py: cross-validation patterns for
structured field confidence adjustment
- New POST /extract/maintenance-receipt endpoint reusing
ReceiptExtractionResponse model
- Per-field confidence scores (0.0-1.0) with Gemini base 0.85,
boosted/reduced by regex agreement
Co-Authored-By: Claude Opus 4.6 <[email protected]>
EmailNotificationToggle used a custom button-based toggle that rendered
as a circle. Replaced with MUI Switch component to match the pill-style
toggles used on the SettingsPage throughout the app.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Maintenance page called useMaintenanceRecords() without a vehicleId,
causing the schedules query (enabled: !!vehicleId) to never execute.
Added vehicle selector to both desktop and mobile pages, auto-selects
first vehicle, and passes selectedVehicleId to the hook. Also fixed
stale query invalidation keys in delete handlers.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Handle poll errors including 410 Gone in useManualExtraction hook
- Add specific progress stage messages (Preparing/Processing/Mapping/Complete)
- Enforce 44px minimum touch targets on all interactive elements
- Add tests for inline editing, mobile fullscreen, and desktop modal layouts
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Add filename .pdf extension fallback and %PDF magic bytes validation to
extractManual controller. Update getJobStatus to return 410 Gone for
expired jobs. Add 16 unit tests covering all acceptance criteria.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Add 5000ms timeout to Places Text Search API call in searchStationByName.
Timeout errors log a warning instead of error and return null gracefully.
Add timeout test case to station-matching unit tests.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Adds minHeight/minWidth: 44 to ReceiptCameraButton, ReceiptOcrReviewModal
action buttons, and UpgradeRequiredDialog buttons and close icon to meet
mobile accessibility requirements.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Create reusable preHandler middleware for subscription tier gating.
Composable with requireAuth in route preHandler arrays. Returns 403
TIER_REQUIRED with upgrade prompt for insufficient tier, 500 for
unknown feature keys. Includes 9 unit tests covering all acceptance
criteria.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Add/update documentation across backend, Python OCR service, and frontend
for receipt scanning, manual extraction, and Gemini integration. Create
new CLAUDE.md files for engines/, fuel-logs/, documents/, and maintenance/
features.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Add POST /api/ocr/extract/manual endpoint that proxies to the Python
OCR service's manual extraction pipeline. Includes Pro tier gating via
document.scanMaintenanceSchedule, PDF-only validation, 200MB file size
limit, and async 202 job response for polling via existing job status
endpoint.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Replace traditional OCR pipeline (table_detector, table_parser,
maintenance_patterns) with GeminiEngine for semantic PDF extraction.
Map Gemini serviceName values to 27 maintenance subtypes via
ServiceMapper fuzzy matching. Add 8 unit tests covering normal
extraction, unusual names, empty response, and error handling.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Add standalone GeminiEngine class for maintenance schedule extraction
from PDF owners manuals using Vertex AI Gemini 2.5 Flash with structured
JSON output enforcement, 20MB size limit, and lazy initialization.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Add Google Places Text Search to match receipt merchant names (e.g.
"Shell", "COSTCO #123") to real gas stations. Backend exposes
POST /api/stations/match endpoint. Frontend calls it after OCR
extraction and pre-fills locationData with matched station's placeId,
name, and address. Users can clear the match in the review modal.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Free tier users see locked button with upgrade prompt dialog.
Pro+ users can capture receipts normally. Works on mobile and desktop.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Add POST /api/ocr/extract/receipt endpoint that proxies to the Python
OCR service's /extract/receipt for receipt-specific field extraction.
- ReceiptExtractionResponse type with receiptType, extractedFields, rawText
- OcrClient.extractReceipt() with optional receipt_type form field
- OcrService.extractReceipt() with 10MB max, image-only validation
- OcrController.extractReceipt() with file upload and error mapping
- Route with auth middleware
- 9 unit tests covering normal, edge, and error scenarios
Co-Authored-By: Claude Opus 4.6 <[email protected]>
The set -e + curl --fail-with-body inside $() caused the script to exit
with code 22 and empty stderr, hiding the actual Auth0 error. Switch to
writing the body to a temp file and checking HTTP status manually so the
error response is visible in logs.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
The google-wif-config.json was never synced to the deploy path, so the
Docker bind mount created a directory artifact instead of a file. Vision
client initialization failed on every request, silently falling back to
PaddleOCR.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Switch OCR engine config to google_vision primary / paddleocr fallback
- Mount Auth0 OCR secrets and WIF config into all OCR containers
- Add WIF config to repo (not a secret, contains no credentials)
- Remove obsolete google-vision-key.json.example
Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Add AUTH0_OCR_CLIENT_ID and AUTH0_OCR_CLIENT_SECRET to inject-secrets.sh
- Add new secrets to staging and production workflow env blocks
- Create .example files for new secret documentation
Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Create fetch-auth0-token.sh for Auth0 M2M -> GCP WIF token exchange
- Add jq to Dockerfile system dependencies
- Ensure script is executable in container image
Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Add VISION_MONTHLY_LIMIT config setting (default 1000)
- Update CloudEngine to use WIF credential config via ADC
- Rewrite HybridEngine to support cloud-primary with Redis counter
- Pass monthly_limit through engine factory
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Redis only supports debug|verbose|notice|warning -- not info or error.
The command was using ${LOG_LEVEL:-info} which resolved to INFO in
production (from workflow env), causing Redis to crash loop. Hardcode
the correct Redis-native levels (debug for dev, warning for prod) and
add available log level comments above every container's log setting.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
VIN OCR confidence now reflects recognition accuracy only (not match quality).
Review modal replaces read-only fields with editable cascade dropdowns
pre-populated from NHTSA decode, with NHTSA reference hints for unmatched fields.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
The retake button failed because the stream tracks could become inactive
during the crop phase, but handleRetake never re-acquired the camera.
Co-Authored-By: Claude Opus 4.6 <[email protected]>