- Add rotation_degrees parameter to ImageProcessor.process_photo()
- Pass rotation through _auto_save_photo_from_extraction() to processor
- Allow no-crop fallback when crop_bounds is None
- Add buildPhotoUrl() helper to resolve backend URLs correctly
- Update frontend components to use backend URL for image sources
- Replace Use/Skip Photo buttons with checkbox in AI extraction UI
- Add images/ to .gitignore to prevent accidental commits
Addresses: rotation never applied, image 404s (relative to Next.js not backend), preview blank in edit form
- Extend ItemCreate schema with optional extracted_image_bytes (base64) and image_processing (dict)
- Update create_item endpoint to call _auto_save_photo_from_extraction after item creation
- Decode base64 image bytes and pass crop_bounds, rotation_degrees to helper
- Don't block item creation if photo save fails (log warning instead)
- Item returned with photo_path, photo_thumbnail_path populated if save succeeded
- Full backward compatibility: old clients without image fields work unchanged
- Add 5 integration tests covering all scenarios:
- Create item WITH image_processing → photo auto-saved
- Create item WITHOUT image_processing → no photo (backward compatible)
- Create item WITH invalid image_processing → item created, photo skipped
- Create item WITH crop_bounds=None → item created, photo skipped
- Create item WITH bytes but NO processing metadata → item created, photo skipped
- All 158 backend tests passing, zero regressions
New Component: ItemComparisonModal.tsx
- Shows existing vs new item side-by-side
- Highlights fields that are different (in yellow)
- Options to Update item or Skip (local-only save)
- Shows existing item ID and comparison details
Backend Changes:
- Updated error message to say 'Part Number' not 'barcode'
- 409 response includes existing item data for comparison
- Clear, user-friendly conflict messaging
Frontend Changes:
- New state for comparison modal (newItem, existingItem, existingId)
- handleOnboardingComplete() shows modal on 409 conflict
- handleComparisonUpdate() calls updateItem() API
- handleComparisonSkip() saves locally without syncing
- Better error handling distinguishes 409 from other failures
Workflow:
1. User imports item with Part Number that already exists
2. System shows comparison modal
3. User can:
- Update (merges new data into existing)
- Skip (saves locally, doesn't sync to cloud)
Backend:
- Added UNIQUE constraint check on barcode before item creation
- Returns 409 Conflict with user-friendly message if duplicate exists
- Prevents sqlite3.IntegrityError crashes
Frontend:
- Improved error handling for cloud sync failures
- Detects 409 status code (duplicate barcode)
- Shows specific error message to user
- Gracefully falls back to local-only save
Example error message: 'Item already exists. Item with barcode P66093-002 already exists (ID: 42). Update it instead or use a different barcode.'