- Updated confirmSingleItem() to include extractedImageBlob and imageProcessing
- Updated confirmAllItems() to pass image data for bulk item creation
- Each extracted item now carries its own image_processing metadata
- All items in bulk creation share the same extracted image blob
- Added 12 comprehensive tests verifying data is passed correctly
- All 465 frontend tests passing, zero regressions
- 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
- Added 11 comprehensive tests for image_processing parsing
- Tests validate crop_bounds structure: {x, y, width, height} all ints >= 0
- Tests validate rotation_degrees: int/float, -360 to +360
- Tests validate confidence: float, 0.0 to 1.0
- Tests graceful handling when image_processing field is missing
- Tests multiple items with image_processing data
- Tests partial data handling (optional fields)
- Tests with both Gemini and Claude providers
- Updated extract_label_info() to preserve and validate image_processing field
- All tests passing, no regressions
The username input was conditionally hidden when it had a value, causing
the field to disappear and focus to jump to password when typing. Fixed by:
1. Always rendering the username input (removed conditional)
2. Using controlled input with value prop
3. Only auto-focus password when username is already entered
This fixes the focus-jumping bug that made it impossible to enter usernames.
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.
local-ssl-proxy was binding to 0.0.0.0 which doesn't work reliably for
cross-network access (VPN, remote clients). Now binds to SERVER_IP from
inventory.env, ensuring the proxy is reachable from all networks that can
reach the server's main IP address.
When accessing from VPN/Tailscale (e.g., 100.78.182.28), frontend was trying
to reach backend on that same IP, but backend listens on SERVER_IP instead.
Now uses SERVER_IP from network.json config, ensuring remote clients connect
to the correct server address regardless of their access network.
- Next.js allowedDevOrigins now loaded from ALLOWED_DEV_ORIGINS env var
- start_server.sh generates ALLOWED_DEV_ORIGINS from EXTRA_ALLOWED_ORIGINS
- Subnet notation (10.0.0.0/24) auto-converts to wildcard patterns (10.0.0.*)
- Individual IPs convert to subnet patterns (192.168.1.100 -> 192.168.1.*)
- Zero hardcoded IPs in source code - all from inventory.env
- 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
- Add ItemDetailModal component for viewing item details and replacing photos
- Add photo replacement/deletion endpoints to API layer (PUT/DELETE /items/{id}/photo)
- Update InventoryTable to open detail modal on item click
- Show current photo thumbnail with Replace/Delete buttons
- Support uploading new photo with ItemPhotoUpload component
- Delete old photo on backend when replacing (no orphaned files)
- Full test coverage: 18 tests for ItemDetailModal component
- All 393 tests passing, zero TypeScript errors
- Build verified successfully
- Create frontend/app/items/create.tsx with multi-step item creation workflow (Details → Photo Upload → Preview → Confirm)
- Create frontend/hooks/useItemCreate.ts custom hook managing form state, step navigation, and photo upload
- Add integration tests for item creation workflow with photo upload support
- Photo upload step supports manual crop UI with crop bounds submission
- ManualCropUI visible by default with toggle to use full photo
- Photo uploaded before item confirmation, ensuring photo is attached
- Works with mobile camera capture via ItemPhotoUpload component
- All 374 tests passing
Fixed 3 critical code quality issues:
1. Act() warnings in tests (9 tests):
- Wrapped all async state updates in act() blocks in usePhotoUpload.test.ts
- Tests using waitFor() now properly await state updates within act()
- All 21 tests pass with zero act() warnings
2. Missing toast cleanup on unmount:
- Added toastIdRef to track pending toast IDs
- Added cleanup useEffect that dismisses toasts on component unmount
- Prevents memory leaks and orphaned toast notifications
3. Dual error reporting channels (lines 23-28):
- Removed useEffect that synced hook error to local state AND called onError callback
- Now syncs hook error to local state only (for display)
- Parent components rely on hook error state, reducing dual-path confusion
- Toast error calls are explicit in catch block
Test Results:
- Frontend: 312/312 tests passing (includes 21 photo upload tests)
- Act() warnings: Eliminated
- No regressions introduced