- Replace [object Object] errors with readable messages across all 20+ API
routes by normalizing Zod validation errors to strings (formatZodErrors)
- Add parseApiError() on frontend to defensively handle any details type
- Add global Fastify error handler with full stack traces in logs
- Fix image-to-pdf auth: Object.entries(headers) → headers.forEach()
- Fix passport-photo: safeParse + formatZodErrors, safe error extraction
- Fix OCR silent fallbacks: log exception type/message when falling back,
include actual engine used in API response and Docker logs
- Fix split tool: process all uploaded images, combine into ZIP with
subfolders per image
- Fix batch support for blur-faces, strip-metadata, edit-metadata,
vectorize: add processAllFiles branch for multi-file uploads
- Docker: LOG_LEVEL=debug, PYTHONWARNINGS=default for visibility
- Add Playwright e2e tests verifying all fixes against Docker container
canBrowserPreview() was checking for a file extension in blob: URLs, which
have none (blob:http://localhost:1349/<uuid>). This caused the optimize-for-web
live preview — which stores a blob: URL in processedUrl — to always return false
and show the 'Conversion complete' fallback card instead of the BeforeAfterSlider.
Fix: blob: URLs are always renderable in <img> tags; short-circuit the extension
check with `if (url.startsWith('blob:')) return true` in both tool-page.tsx and
multi-image-viewer.tsx.
Both routes showed "Tool not found" — the UI was never implemented.
Pipeline functionality already lives at /automate (the Automate page).
Also removed the empty Automation category and unused Workflow/FolderInput
icons from icon-map.ts.
- Fix "Cannot access 'a' before initialization" TDZ error after login
caused by manualChunks splitting react-vendor + lucide icons into
circular ES-module chunks. Removed manualChunks entirely.
- Replace `import * as icons from "lucide-react"` (pulls all ~1000 icons)
with a targeted icon-map of ~50 icons actually used by tool definitions.
Reduces shared icons chunk from 745KB to 62KB (132KB→16KB gzip).
- Exclude static files from @fastify/rate-limit via allowList so rapid
page navigations don't 429 on JS/CSS chunk requests.
- Move Docker auth defaults (AUTH_ENABLED, DEFAULT_USERNAME,
DEFAULT_PASSWORD) from Dockerfile ENV to entrypoint.sh runtime exports
to avoid SecretsUsedInArgOrEnv warnings.
- Fix Docker CMD to use pnpm --filter for workspace-scoped tsx binary.
- Set COREPACK_HOME system-wide so non-root user can access pnpm cache.
- Lazy-load all pages in App.tsx and all controls in
pipeline-step-settings.tsx to keep main bundle under 300KB.
The package scope rename changed the alphabetical position of imports
(@ashim sorts before @dnd-kit; @stirling-image sorted after), causing
biome's organizeImports rule to flag them as unsorted.
- apps/api/src/index.ts: sort registerRestorePhoto import correctly
- apps/web/src/components/tools/pipeline-builder.tsx: sort @ashim/shared import
- apps/web/src/pages/privacy-policy-page.tsx: auto-format
Apply stash from feat/border-redesign branch. Rewrites collage backend
with improved layout engine and adds CollagePreview results panel for
interactive preview. Updates tool registry to use no-dropzone display
mode with the new preview component.
The debounce effect depended on currentEntry (from the file store), but
fetchPreview wrote back to the same store entry on completion. This
created a loop: preview completes -> store updates -> new entry ref ->
effect re-fires -> new preview. Fix by reading the file from a ref
instead of reactive state, so the effect only triggers on actual
settings changes (format, quality, dimensions, metadata toggle).
- Zoom now controls the actual crop: zoom in = tighter crop, zoom out = more body
- Drag adjusts face position within the frame
- Backend receives zoom value and applies the same zoomed crop
- Download produces exactly what the user sees on the canvas
- Zoom range 0.5x-3x (can zoom out to show more body)
- "What you see is what you download" label
- Removed zoom controls entirely from preview pane
- Canvas always shows exact passport photo output at 1:1
- Drag to adjust position still works for fine-tuning
- Overlay lines use crop coordinates directly instead of zoom-adjusted ones
- Added "what you see is what you download" label with output dimensions
Old checks validated head height and eye position against the auto-computed
crop, so they always passed. New checks validate the source photo itself:
face centered, head level (eyes same height), looking straight (nose between
eyes), and face size (large enough for quality crop). Adds detail text on
failed checks and updates the canvas overlay to show eye-level and center
indicators instead of the old crown/chin/eye lines.
- Added "Custom Dimensions" option in country dropdown with width/height inputs
- Added DPI control (72-600, default 300) for all specs
- Backend now uses actual bg-removed image dimensions for crop computation,
scaling landmark coordinates when dimensions differ from the original
- Dropdown background uses explicit bg-white/dark:bg-zinc-900 classes
- Backend supports customWidthMm, customHeightMm, and dpi in generate request
- Backend now pads the image with background color when crop region extends
beyond image bounds, instead of clamping (which cut off heads)
- Dropdown uses explicit bg-white/dark:bg-zinc-900 instead of CSS variable
that was transparent on some themes
- Added "Scroll to zoom" hint on the right pane canvas
- Fixed file size compression loop to use padded source image
- Country dropdown now uses position:fixed with z-index:9999 to render above all siblings
- Added 5 common passport background color presets (white, off-white, light gray, light blue, red)
- Zoom now crops into the image center rather than scaling the canvas element
- Compliance overlay lines are zoom-aware
- Process files one at a time for real per-file progress bar
- Sync right panel with left panel file navigation (arrows work)
- Show image preview before conversion
- Add "Download All as JSON" and "Download All as Text" batch buttons
- Unify batch action button styles
Co-authored-by: stirling-image <stirling-image@users.noreply.github.com>
- Add z-30 to country dropdown container to prevent overlap with settings below
- Add mouse wheel zoom on preview canvas (0.5x to 3x)
- Add zoom in/out buttons and reset with percentage display
- Update hint text to "Drag / Scroll to zoom"
* feat(image-to-base64): register tool in shared constants and i18n
* feat(image-to-base64): add API route with Sharp pipeline and base64 encoding
* feat(image-to-base64): add Zustand store for base64 results
* feat(image-to-base64): add settings panel component
* feat(image-to-base64): add results panel with 6-tab output and batch accordion
* feat(image-to-base64): register tool in frontend tool registry
* fix(image-to-base64): pass through original buffer when no resize/conversion needed
---------
Co-authored-by: stirling-image <stirling-image@users.noreply.github.com>
* feat(passport-photo): add passport specs database and tool constants
* feat(passport-photo): add MediaPipe FaceMesh landmark detection script
* feat(passport-photo): add TypeScript bridge for face landmark detection
* feat(passport-photo): add API routes with analyze and generate endpoints
* fix(passport-photo): accept landmarks from request body and fix pixel coordinate conversion
- Generate endpoint now accepts landmarks + imageWidth/imageHeight in request body
instead of re-running AI face detection (makes generate phase instant)
- Fixed bug where normalized landmark coordinates (0-1) were used directly
as pixel values in crop computation - now properly multiplied by imgW/imgH
- Fixed same bug in pipeline process function
* feat(passport-photo): add UI component with live preview and compliance overlay
---------
Co-authored-by: stirling-image <stirling-image@users.noreply.github.com>