When SKIP_MODEL_DOWNLOADS=true the download script never runs, so
/opt/models was never created and the subsequent chown -R ... /opt/models
failed with exit code 1. mkdir -p it alongside the other required dirs.
esbuild (used by Vite) crashes under QEMU amd64 emulation on Apple Silicon,
the same way the Go runtime did. Building the frontend on the native platform
is safe because the output (HTML/CSS/JS) contains no architecture-specific code.
- Fix docker run commands (was pointing to old stirlingimage/stirling-image)
- Add Docker Images section with all available tags for both registries
- Add GHCR badge alongside Docker Hub badge
- Remove rename banner (rename is complete)
Replace CGO_ENABLED=0 (which fails because gioui.org requires CGO on Linux)
with a proper C cross-compiler approach using Debian multi-arch packages.
Running caire-builder with --platform=\$BUILDPLATFORM avoids QEMU crashes on
Apple Silicon; the C cross-compiler bridges the CGO gap for the target arch.
Also adds SKIP_MODEL_DOWNLOADS=true to the CI docker build job to prevent
HuggingFace CDN 504s in CI (image structure is what matters there).
The caire-builder stage now runs the Go toolchain on the native build
platform instead of under QEMU emulation. For cross-arch builds
(arm64 host → amd64 image) CGO_ENABLED=0 avoids needing a full C
cross-toolchain; the display window is unused in server mode anyway.
The binary is staged to /tmp/caire so the COPY path is stable across
both native and cross-compiled builds.
Run both short-range and full-range MediaPipe models and merge results,
then apply non-maximum suppression to remove duplicate bounding boxes.
Fixes missed faces in group photos where the single-model loop exited
early after the first positive detection.
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.
urlretrieve against the HuggingFace CDN was consistently returning HTTP
504 in GitHub Actions runners for LaMa, NAFNet, and the OpenCV caffemodel.
The huggingface_hub library has built-in retry logic, resumable downloads,
and better CDN routing than bare urlretrieve.
- download_lama_model: urlretrieve → hf_hub_download (Carve/LaMa-ONNX)
- download_nafnet_model: urlretrieve → hf_hub_download (mikestealth/nafnet-models)
- download_opencv_colorize_models: caffemodel → hf_hub_download (space repo_type)
- _urlretrieve: retry count 3→5, flat 10s delay → exponential backoff (10/20/40/80s)
- Also reverts the SKIP_MODEL_DOWNLOADS=true from CI workflow (wrong approach)
The Docker Build Test was consistently failing because HuggingFace CDN
returns 504 Gateway Timeout when downloading the LaMa ONNX model (~200MB)
from GitHub Actions runners. Model availability is an external dependency,
not something CI can control.
Added SKIP_MODEL_DOWNLOADS build arg (default: false). When set to true,
the download_models.py step is skipped entirely. CI only needs to verify
the image structure builds — Python deps install, Node build runs, app
code is copied — not that every ML model CDN is reachable.
Production builds (docker build without the arg) still download all models
as before.
download_models.py used bare urllib.request.urlretrieve() with no retry
logic. CI hit a HTTP 504 Gateway Timeout mid-build, failing the Docker
Build Test. Added _urlretrieve() wrapper that retries up to 3 times with
a 10s delay on any 5xx or network error. Also adds imports for time and
urllib.error.
vitest was spawning multiple worker processes (one per test file) that
each independently opened the same SQLite database and raced to set
journal_mode = WAL. Adding pool: "forks" with singleFork: true runs all
test files in the same child process, eliminating the concurrent-open
race. The test suite (~830 tests) remains fast enough for CI.
The test-with-exif.jpg fixture still had "Stirling-Image Test" as its
Software EXIF field. Updated to "ashim Test" to match the assertions in
operations.test.ts that were already updated during the rebrand.
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 ONNX variant shadowed the .pth variant due to identical function
names, so codeformer.pth was never downloaded. Renamed the ONNX
function to download_codeformer_onnx_model so both run.
The original Berkeley server (eecs.berkeley.edu) is dead, returning
404 after redirect. Switched to a reliable HuggingFace-hosted mirror
of the same 129MB model file.
Three fixes to ensure zero network access after docker pull:
1. rembg model allowlist: validate model parameter against the 7
pre-downloaded models, preventing rembg from attempting to download
unknown models via a raw API call.
2. GFPGAN/CodeFormer auxiliary models: pre-download facexlib's
detection_Resnet50_Final.pth and parsing_parsenet.pth at build time.
These were previously downloaded on first use via basicsr. Symlinks
in /app/gfpgan/weights/ ensure codeformer-pip also finds them.
3. OpenCV colorize models: pre-download the prototxt, caffemodel, and
pts_in_hull.npy so the lightweight OpenCV colorizer fallback works
in addition to the primary DDColor method.
Co-authored-by: stirling-image <stirling-image@users.noreply.github.com>
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).
MediaPipe >= 0.10.30 removed the mp.solutions namespace. This broke
face blur, face enhance, red-eye removal, and photo restoration for
users running newer mediapipe versions (closes#43).
All 5 Python scripts that use mediapipe now try the legacy mp.solutions
API first and fall back to the new mp.tasks API on AttributeError.
Model files (blaze_face_short_range.task, face_landmarker.task) are
pre-downloaded during Docker build into /opt/models/mediapipe/ so the
image works fully airgapped. Local dev auto-downloads to .models/.
Co-authored-by: stirling-image <stirling-image@users.noreply.github.com>
- 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
npx attempts to reach the npm registry even when tsx is installed
locally, causing the container to crash in airgapped/offline
environments with ECONNRESET. pnpm exec resolves tsx from local
node_modules only, with no network calls.
Closes#29
Co-authored-by: stirling-image <stirling-image@users.noreply.github.com>
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
- Old API (mp.solutions.face_mesh) for Docker with mediapipe < 0.10.30
- New API (mp.tasks.vision.FaceLandmarker) for newer mediapipe >= 0.10.30
- Auto-downloads face_landmarker.task model on first use with new API
- Extracted shared landmark index constants and key point extraction
- 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"