- Update Loki image from 2.9.0 to 3.6.1 in docker-compose.yml
- Migrate schema from v11 to v13, store from boltdb-shipper to tsdb
- Update storage_config to use tsdb_shipper with new index paths
- Remove deprecated shared_store config (removed in Loki 3.0)
- Disable structured metadata (not needed for current setup)
- Preserve 30-day retention policy (720h)
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Promtail 2.9.0 embeds Docker client API v1.42 which is incompatible with
Docker Engine v29 (minimum API v1.44). Grafana Alloy v1.12.2 resolves this
by using a compatible Docker client.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Update Loki, Promtail, Grafana to use REGISTRY_MIRRORS
- Add grafana/loki, grafana/promtail, grafana/grafana to mirror script
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Create centralized logger utility at frontend/src/utils/logger.ts
- Support debug/info/warn/error levels controlled by VITE_LOG_LEVEL
- Sanitize sensitive data (tokens, passwords, secrets) in log output
- Graceful fallback to 'info' level for invalid VITE_LOG_LEVEL values
- Add VITE_LOG_LEVEL to ImportMetaEnv type definitions
Co-Authored-By: Claude Opus 4.5 <[email protected]>
The 60s start_period was too short - migrations can take 70+ seconds.
Docker was marking the container unhealthy before migrations completed.
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Add "Wait for backend health" step using docker exec to verify backend
is responding before attempting external health check. Matches staging
workflow pattern.
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Add Docker healthcheck loop to production verify-prod job matching
staging's 24 attempts x 10 seconds = 4 minutes max wait for backend
migrations.
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Replace Winston with Pino using API-compatible wrapper
- Add LOG_LEVEL env var support with validation and fallback
- Add correlation ID middleware (X-Request-Id from Traefik or UUID)
- Configure PostgreSQL logging env vars (POSTGRES_LOG_STATEMENT, POSTGRES_LOG_MIN_DURATION)
- Configure Redis loglevel via command args
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Create generate-log-config.sh that maps a single LOG_LEVEL env var to
per-container settings for Backend, Frontend, PostgreSQL, Redis, and
Traefik. Script validates input and generates .env.logging file.
Integrate script into staging and production CI/CD pipelines.
Remove obsolete SPRINTS.md calendar file.
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Implement async PDF processing for owner's manuals with maintenance
schedule extraction:
- Add PDF preprocessor with PyMuPDF for text/scanned PDF handling
- Add maintenance pattern matching (mileage, time, fluid specs)
- Add service name mapping to maintenance subtypes
- Add table detection and parsing for schedule tables
- Add manual extractor orchestrating the complete pipeline
- Add POST /extract/manual endpoint for async job submission
- Add Redis job queue support for manual extraction jobs
- Add progress tracking during processing
Processing pipeline:
1. Analyze PDF structure (text layer vs scanned)
2. Find maintenance schedule sections
3. Extract text or OCR scanned pages at 300 DPI
4. Detect and parse maintenance tables
5. Normalize service names and extract intervals
6. Return structured maintenance schedules with confidence scores
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add useReceiptOcr hook for OCR extraction orchestration
- Add ReceiptCameraButton component for triggering capture
- Add ReceiptOcrReviewModal for reviewing/editing extracted fields
- Add ReceiptPreview component with zoom capability
- Integrate camera capture, OCR processing, and form population
- Include confidence indicators and low-confidence field highlighting
- Support inline editing of extracted fields before acceptance
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Implement receipt-specific OCR extraction for fuel receipts:
- Pattern matching modules for date, currency, and fuel data extraction
- Receipt-optimized image preprocessing for thermal receipts
- POST /extract/receipt endpoint with field extraction
- Confidence scoring per extracted field
- Cross-validation of fuel receipt data
- Unit tests for all pattern matchers
Extracted fields: merchantName, transactionDate, totalAmount,
fuelQuantity, pricePerUnit, fuelGrade
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add VinCameraButton component that opens CameraCapture with VIN guidance
- Add VinOcrReviewModal showing extracted VIN and decoded vehicle data
- Confidence indicators (high/medium/low) for each field
- Mobile-responsive bottom sheet on small screens
- Accept, Edit Manually, or Retake Photo options
- Add useVinOcr hook orchestrating OCR extraction and NHTSA decode
- Update VehicleForm with camera button next to VIN input
- Form auto-populates with OCR result and decoded data on accept
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Add detailed step-by-step instructions for setting up SSH key-based
authentication from staging to production, including proper directory
and file permissions (0700 for .ssh, 0600 for authorized_keys).
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Add detailed step-by-step instructions for setting up SSH key-based
authentication from staging to production, including proper directory
and file permissions (0700 for .ssh, 0600 for authorized_keys).
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Implements a reusable React camera capture component with:
- getUserMedia API for camera access on mobile and desktop
- Translucent aspect-ratio guidance overlays (VIN ~6:1, receipt ~2:3)
- Post-capture crop tool with draggable handles
- File input fallback for desktop and unsupported browsers
- Support for HEIC, JPEG, PNG (sent as-is to server)
- Full mobile responsiveness (320px - 1920px)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
The CI was failing because Docker marked the backend unhealthy before the CI
wait loop completed. The backend needs time to run migrations and seed vehicle
data on startup.
Changes:
- start_period: 40s -> 180s (3 minutes)
- retries: 3 -> 5 (more tolerance)
Total time before unhealthy: 180s + (5 × 30s) = 5.5 minutes
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Backend with fresh migrations can take ~3 minutes to start.
Increased from 10x5s (50s) to 24x10s (240s) to accommodate.
Co-Authored-By: Claude Opus 4.5 <[email protected]>
The documents migration 003_reset_scan_for_maintenance_free_users.sql
depends on user_profiles table which is created by user-profile feature.
Move user-profile earlier in MIGRATION_ORDER to fix staging deployment.
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add OCR image build/push to staging workflow
- Add OCR service with image override to staging compose
- Add OCR service with image override to blue-green compose
- Add OCR image pull/deploy to production workflow
- Include mvp-ocr-staging in health checks
The OCR container is a shared service (like postgres/redis),
not part of blue-green deployment.
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Add Python-based OCR service container (mvp-ocr) as the 6th service:
- Python 3.11-slim with FastAPI/uvicorn
- Tesseract OCR with English language pack
- pillow-heif for HEIC image support
- opencv-python-headless for image preprocessing
- Health endpoint at /health
- Unit tests for health, HEIC support, and Tesseract availability
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Changed INSERT to INSERT...ON CONFLICT DO UPDATE so the migration works for:
- Fresh deployments (inserts new template)
- Existing databases (updates template to fix variable substitution)
Removed unnecessary migration 008.
Co-Authored-By: Claude Opus 4.5 <[email protected]>
The original migration already inserted the template with Handlebars conditionals.
This migration updates the existing record to use simple variable substitution.
Co-Authored-By: Claude Opus 4.5 <[email protected]>
The TemplateService only supports {{variable}} substitution, not Handlebars-style
conditionals. Changed to use a single {{additionalInfo}} variable that is built
in the service code based on upgrade/downgrade status.
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Adds email and in-app notifications when user subscription tier changes:
- Extended TemplateKey type with 'subscription_tier_change'
- Added migration for tier change email template with HTML
- Added sendTierChangeNotification() to NotificationsService
- Integrated notifications into upgradeSubscription, downgradeSubscription, adminOverrideTier
- Integrated notifications into grace-period.job.ts for auto-downgrades
Notifications include previous tier, new tier, and reason for change.
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- GET /api/vehicles now uses getUserVehiclesWithTierStatus() and filters
out vehicles with tierStatus='locked' so only selected vehicles appear
in the vehicle list
- GET /api/vehicles/:id now checks tier status and returns 403 TIER_REQUIRED
if user tries to access a locked vehicle directly
This ensures that after a user selects 2 vehicles during downgrade to
free tier, only those 2 vehicles appear in the summary and details screens.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add useNeedsVehicleSelection and useVehicles hooks in App.tsx
- Show blocking VehicleSelectionDialog after auth gate ready
- Call downgrade API on confirm to save vehicle selections
- Invalidate queries after selection to proceed to app
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add blocking prop to prevent dismissal
- Disable backdrop click and escape key when blocking
- Hide Cancel button in blocking mode
- Update messaging for auto-downgrade scenario
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add NeedsVehicleSelectionResponse type
- Add needsVehicleSelection API method
- Add useNeedsVehicleSelection hook with staleTime: 0
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
The grace-period job was using 'user_id' to query user_profiles table,
but the correct column name is 'auth0_sub'. This would cause the tier
sync to fail during grace period auto-downgrade.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Add adminOverrideTier() method to SubscriptionsService that atomically
updates both subscriptions.tier and user_profiles.subscription_tier
using database transactions.
Changes:
- SubscriptionsRepository: Add updateTierByUserId() and
createForAdminOverride() methods with transaction support
- SubscriptionsService: Add adminOverrideTier() method with transaction
wrapping for atomic dual-table updates
- UsersController: Replace userProfileService.updateSubscriptionTier()
with subscriptionsService.adminOverrideTier()
This ensures admin tier changes properly sync to both database tables,
fixing the Settings page "Current Plan" display mismatch.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Replace CardElement with PaymentElement + AddressElement in subscription forms
- Add AddressElement to donation forms for billing address collection
- Now collects: Name, Address Line 1/2, City, State, Postal Code, Country
- Card details: Card Number, Expiration, CVC
- Both desktop and mobile forms updated
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
The subscriptions feature migration was not being run because it was
missing from the MIGRATION_ORDER array. Added it after ownership-costs
since it depends on user-profile (for subscription_tier enum) and
vehicles (for FK relationships).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Added a Subscription section to the mobile Settings screen that displays:
- Current subscription tier (Free/Pro/Enterprise)
- Status indicator for non-active subscriptions
- Manage button linking to the subscription screen
- Descriptive text based on current tier
This completes the subscription section on both desktop and mobile.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Fixed conditional logic for subscription description text to properly
handle the case when subscription data is not loaded or unavailable.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Added a Subscription section to the desktop Settings page that displays:
- Current subscription tier (Free/Pro/Enterprise)
- Status indicator for non-active subscriptions
- Manage button linking to the subscription management page
- Descriptive text based on current tier
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
The staging workflow was not copying docker-compose.yml to the server,
causing configuration changes (like Stripe secrets) to not take effect.
Added rsync step to sync config, scripts, and compose files before
deployment, matching the production workflow behavior.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add VITE_STRIPE_PUBLISHABLE_KEY to frontend Dockerfile build args
- Add VITE_STRIPE_PUBLISHABLE_KEY to docker-compose.yml build args
- Add :ro flag to backend Stripe secret volume mounts for consistency
- Update inject-secrets.sh with STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET
- Add Stripe secrets to staging.yaml workflow (build arg + inject step)
- Add Stripe secrets to production.yaml workflow (inject step)
Requires STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET secrets and
VITE_STRIPE_PUBLISHABLE_KEY variable to be configured in Gitea.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
API Endpoints (all authenticated):
- GET /api/subscriptions - current subscription status
- POST /api/subscriptions/checkout - create Stripe subscription
- POST /api/subscriptions/cancel - schedule cancellation at period end
- POST /api/subscriptions/reactivate - cancel pending cancellation
- PUT /api/subscriptions/payment-method - update payment method
- GET /api/subscriptions/invoices - billing history
Grace Period Job:
- Daily cron at 2:30 AM to check expired grace periods
- Downgrades to free tier when 30-day grace period expires
- Syncs tier to user_profiles.subscription_tier
Email Templates:
- payment_failed_immediate (first failure)
- payment_failed_7day (7 days before grace ends)
- payment_failed_1day (1 day before grace ends)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
When a user signs up but doesn't verify their email, clicking the Login
button on the landing page would either do nothing or get stuck in a
loading state. Now checks for pendingVerificationEmail in localStorage
(set during signup) and redirects to /verify-email instead of attempting
Auth0 login.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Changed icon button hover behavior to match VehicleCard pattern:
- Removed background color fills on hover (was primary.main/error.main)
- Icons now use default MUI IconButton gray ripple on hover
- Edit icons use text.secondary color (matches VehicleCard)
- Delete icons use error.main color (matches VehicleCard)
Affected files:
- DocumentsPage.tsx
- FuelLogsList.tsx
- MaintenanceRecordsList.tsx
- MaintenanceSchedulesList.tsx
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Added missing Edit icon button between Eye and Trash icons.
Clicking Edit opens EditDocumentDialog to modify the document.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Documents page: Convert from text buttons to icon buttons (Eye for
View Details, Trash for Delete), add card hover shadow effect,
convert to MUI components for consistency
- Fuel Logs: Add row hover background effect on list items
- Maintenance Records: Add card hover shadow effect
- Maintenance Schedules: Add card hover shadow effect
All changes follow the VehicleCard pattern with:
- Light gray shadow/elevation on hover with 0.2s transition
- Consistent icon button styling with mobile-responsive touch targets
- Proper MUI component usage throughout
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
PostgreSQL DECIMAL columns return as strings from pg driver.
- Add Number() conversion for fuelUnits and costPerUnit in toEnhancedResponse()
- Add query invalidation for 'all' key to fix dynamic updates
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Enhanced repository methods were incorrectly calling mapRow() which
converts snake_case to camelCase, but the service's toEnhancedResponse()
expects raw database rows with snake_case properties. This caused
"Invalid time value" errors when calling new Date(row.created_at).
Fixed methods:
- createEnhanced
- findByVehicleIdEnhanced
- findByUserIdEnhanced
- findByIdEnhanced
- getPreviousLogByOdometer
- getLatestLogForVehicle
- updateEnhanced
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Replace polling-based auth detection with event-based subscription
- Remove unnecessary 100ms delay on desktop (keep 50ms for mobile)
- Unify dashboard data fetching to prevent duplicate API calls
- Use Promise.all for parallel maintenance schedule fetching
Reduces dashboard load time from ~1.5s to <500ms.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Rename "Open" button to "View Details" on desktop and mobile document lists
- Add hasDisplayableMetadata helper to check if document has metadata to display
- Conditionally render Details section only when metadata exists
- Prevents showing empty "Details" header for documents without metadata
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Create ExpirationBadge component with 30-day warning and expired states
- Create DocumentCardMetadata component for type-specific field display
- Update DocumentsPage to show metadata and expiration badges on cards
- Update DocumentsMobileScreen with metadata and badges (mobile variant)
- Redesign DocumentDetailPage with side-by-side layout (desktop) and
stacked layout (mobile) showing full metadata panel
- Add 33 unit tests for new components
- Fix jest.config.ts testMatch pattern for test discovery
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add purchase price and purchase date display to vehicle detail page
- Fix form validation to handle NaN from empty number inputs using z.preprocess
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
The VIN Number field incorrectly showed license plate when VIN was empty.
Now displays "Not provided" for missing VIN values, matching mobile behavior.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Remove CostInterval type and TCOResponse interface from frontend types
- Remove insurance/registration cost fields from VehicleForm schema and UI
- Keep purchasePrice and purchaseDate fields on vehicle form
- Remove TCODisplay component from VehicleDetailPage
- Delete TCODisplay.tsx component file
- Remove getTCO method from vehicles API client
Legacy TCO fields moved to ownership-costs feature in #29.
Backend endpoint preserved for future reporting feature.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Replace raw HTML checkboxes with MUI Checkbox wrapped in FormControlLabel
for consistent styling and theme integration across:
- DocumentForm.tsx (shared vehicles + scan maintenance checkboxes)
- VehicleForm.tsx (TCO enabled checkbox)
- SignupForm.tsx (terms acceptance checkbox)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
The upload was hanging silently because breaking early from a
`for await` loop on a Node.js stream corrupts the stream's internal
state. The remaining stream could not be used afterward.
Changes:
- Collect ALL chunks from the file stream before processing
- Use subarray() for file type detection header (first 4100 bytes)
- Create single readable stream from complete buffer for storage
- Remove broken headerStream + remainingStream piping logic
This fixes the root cause where uploads would hang after logging
"Document upload requested" without ever completing or erroring.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Document uploads were failing with "timeout of 10000ms exceeded" error
because the global axios client timeout (10s) was too short for
medium-sized files (1-5MB).
Added calculateUploadTimeout() function that calculates timeout based on
file size: 30s base + 10s per MB. This allows uploads to complete on
slower connections while still having reasonable timeout limits.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Created DeleteDocumentConfirmDialog with context-aware messaging:
- Primary vehicle with no shares: Full delete
- Shared vehicle: Remove association only
- Primary vehicle with shares: Full delete (affects all)
- Integrated documents display in VehicleDetailPage records table
- Added delete button per document with 44px touch target
- Document deletion uses appropriate backend calls based on context
- Mobile-friendly dialog with responsive design
Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>