Temporarily using allow_origins=['*'] to debug whether CORS is blocking
LDAP login requests from VPN client. This is insecure for production.
TODO: Fix subnet pattern matching in ALLOWED_ORIGINS configuration.
- Simplify backend CORS middleware to use standard FastAPI implementation
- Keep subnet validation function for future use in route-level checks
- Add Tailscale subnet pattern to Next.js allowedDevOrigins config
- Both individual IPs and subnet configurations now work correctly
- Add ipaddress module for subnet parsing (10.0.0.0/24 format)
- Implement subnet validation in CORS middleware
- Separate individual IPs from subnet definitions in EXTRA_ALLOWED_ORIGINS
- Custom SubnetAwareCORSMiddleware for dynamic origin validation
- Support both exact IP matches and subnet ranges
- Backward compatible with existing ALLOWED_ORIGINS list
- Create ImageProcessor service with EXIF orientation detection
- Implement smart cropping via OpenCV contour detection (10% padding)
- Add text orientation detection using Hough line transform
- Resize and compress images to 1200px with 85% JPEG quality
- Generate 200px square thumbnails with center crop
- Fallback to Pillow if OpenCV fails
- Comprehensive test suite: 28 tests all passing
- File size validation (reject >10MB)
- Graceful error handling for corrupted/invalid images
- Update requirements.txt with opencv-python, piexif, python-magic
The N+1 optimization in save_image() pre-lowercases the existing_files list
before passing to get_unique_filename(). However, this broke the API contract:
the function should handle any-case input to remain robust.
Changed: get_unique_filename() now defensively lowercases the input list,
ensuring collision detection works regardless of input case.
Benefits:
- Fixes implicit API contract change (function expected any-case input)
- Maintains N+1 optimization (pre-lowercasing still works)
- Supports both optimization and edge cases (direct function calls)
- All 22 tests pass
- Add SIZE CONVERSION RULES section (critical) with explicit thresholds
- Convert ≥1600GB to TB format (e.g., 1600GB → 1.6TB)
- Convert memory ≥1024MB to GB format
- Update Item field examples with human-readable sizes
- Update Size field definition to emphasize HUMAN-READABLE format
- Update OCR key definition with size conversion examples
- Apply rules consistently across all size-related fields
This ensures AI-extracted items display sizes in user-friendly format
and improves OCR matching accuracy with normalized size representations.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
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.'