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.
- Route resize steps with contentAware=true to the content-aware-resize
tool in both single-file and batch pipeline paths
- Update HEIC filename extension to .png after decoding so downstream
tools don't attempt double-decoding
- Wrap per-step errors with step number and tool name for clarity
(e.g. "Step 1 (resize): Resize requires width, height, or percentage")
- Show first file's step-level error in batch failure messages
Reject non-SVG files with a clear error message instead of letting Sharp
fail with cryptic HEIF/corrupt header errors. Applies to both single-file
and batch endpoints.
Expand the svg-to-raster dedicated endpoint test suite from 3 to 10 tests:
- All 7 output formats (png, jpg, webp, avif, tiff, gif, heif)
- Quality setting verification (low vs high quality jpg file sizes)
- DPI setting verification (72 vs 300 dpi png file sizes)
- Preview URL generation for non-browser formats (tiff)
HEIF test skips gracefully if heif-enc is not installed (422 response).
Switch from manual fetch to useToolProcessor hook for consistency and
batch support. Add scale/custom sizing modes, DPI presets, 7-format
button grid, quality slider, transparent/color background toggle with
preset swatches, and ProgressCard during processing.
- Add clientJobId field support for SSE progress correlation
- Add updateJobProgress calls matching generic batch route pattern
- Use sanitizeFilename() instead of basename() for security
- Map Zod errors to {path, message} format for consistency
- Include errors array in all-failed response body
The generic batch route validates image buffers by magic bytes, which
fails for SVG input. This adds a dedicated /batch endpoint that sanitizes
each SVG individually before conversion, using p-queue for concurrency
and streaming the results as a ZIP with X-File-Results header.
Also extracts shared conversion logic into a reusable convertSvg helper
to avoid duplication between single-file and batch endpoints.
Sharp cannot encode HEVC directly. Use encodeHeic() (same as convert tool)
to go through heif-enc. Also decode HEIF before Sharp for preview generation.
Hoist NON_PREVIEWABLE set to module scope.
Add user-configurable DPI (36-1200) and quality (1-100) instead of
hardcoded values. Support avif, tiff, gif, heif output in addition to
png, jpg, webp. Width is now optional, defaulting to SVG intrinsic size
at the given DPI. Generate browser-previewable webp thumbnails for
non-browser formats (tiff, heif). Remove unused _contentType variable.
- Multi-file sequential processing with per-file progress
- Structured results table with type badges and copy per-result
- Copy All and Export CSV functionality
- Thorough scan toggle (maps to tryHarder in zxing-wasm)
- Before/after view shows annotated image with bounding boxes
- Updated tool description in constants and i18n
The edit-metadata integration tests require exiftool (libimage-exiftool-perl)
which was missing from the CI test runner, causing 4 tests to fail with 422.
Update erase-object pipeline, eraser canvas, and inpainting Python script.
Add LaMa model download script and update Dockerfile for model support.
Update multi-file tool routes for consistency.
Replace the confusing 2-mode smart crop with a clear 3-mode system:
- Subject Focus: Sharp attention/entropy saliency crop with social media presets
- Face Focus: MediaPipe face detection with headshot framing presets
- Auto Trim: Border removal with optional pad-to-square
Adds detectFaces() to AI package, face preset constants, backward
compatibility for old mode names, and comprehensive integration tests.
- Pin PaddlePaddle to 3.0.0 on ARM64 to fix segfault in PIR inference
engine (3.1+ crashes on aarch64 Debian Bookworm)
- Fix text extraction for PaddleOCR 3.4.x result format (rec_texts)
- Add Node.js-level fallback chain (best -> balanced -> fast) when
Python subprocess crashes
- Add multi-image OCR: processes all uploaded files sequentially with
per-file progress and filename headers in combined output
- Convert input images to PNG via Sharp before OCR so HEIC, AVIF, WebP,
TIFF all work transparently
- Implement real auto-detect language using Tesseract multi-lang script
detection (analyzes Unicode ranges for Hangul, CJK, Kana, Latin)
- Default enhance to off (hurts clean digital images)
- Replace Auto/AI/Fast buttons with Fast/Balanced/Best (consistent with other tools)
- Rename "Denoise" to "Noise Reduction" with explanatory subtitle
- Change output format from 3 buttons to dropdown with all formats (PNG, JPG, WebP, AVIF, TIFF, GIF, HEIC, HEIF)
- Add HEIC/HEIF input decoding (was missing unlike other tools)
- Add HEIC/HEIF/AVIF output conversion via Sharp and heif-enc
- Generate browser-compatible WebP preview for non-previewable output formats
- Fix torchvision compatibility shim so Real-ESRGAN actually loads (was silently falling back to Lanczos)
- Fix denoise crash: Image.fromarray() instead of type(img).fromarray()
- Redirect stdout for entire AI pipeline to prevent library messages corrupting JSON output
- Add GFPGAN model download for face enhancement
- Use batch endpoint for multi-file uploads (enables Download All ZIP)
- Fix multi-image: process selected file, not always first
- Fix progress bar: asymptotic fill prevents visual stalling
- Fix slider: write results to captured index, not current selection
- Add model selection (Auto/AI/Fast), face enhancement, denoise
- Add output format (PNG/JPEG/WebP) with quality control
- Add Upscale All for sequential batch processing with queue
- More granular Python progress stages for smoother UX
- Fix temp file leak: clean up preprocessed image in finally block
- Log warning instead of silently swallowing preprocessing failures
- Simplify auto_detect_language to honest default (was a stub that
wasted time loading a model but always returned "en")
Ultra quality (People only):
- BiRefNet-matting ONNX (928MB) for true alpha matting with per-pixel
transparency on hair wisps and fine edges
- Custom rembg session class, zero new Python dependencies
- Model pre-downloaded in Docker build for immediate availability
Quality tier labels: Fast / HD / Max / Ultra (shorter, fits 4-col grid)
Adds a new "Ultra" quality tier for People subject type that uses
BiRefNet-matting (ONNX, 928MB) for true alpha matting instead of
binary segmentation. Produces per-pixel transparency for hair wisps
and fine edges that standard models miss.
- Custom rembg session class loads BiRefNet-matting ONNX from GitHub releases
- Zero new Python dependencies (reuses existing onnxruntime)
- Model pre-downloaded in Docker build alongside existing models
- Ultra option only visible when subject is People
- Falls back to Best when switching to Products/General
Remove Background:
- Two-phase flow: AI removes bg once, then effects adjust instantly
- Blur background effect with real-time CSS preview (portrait mode)
- Drop shadow effect with opacity control
- Gradient backgrounds with presets, custom colors, and angle
- Custom background image upload (including HEIC/HEIF)
- Solid color backgrounds moved from Python to Node.js/Sharp
- Effects-only API endpoint for instant re-renders without AI re-run
- HEIC/HEIF input support (decoded before passing to Python/rembg)
- Passport/ID photo checkbox defaults ON for People subject
- Before/after slider preserved when no effects active
- 15 comprehensive Playwright e2e tests
Color Tools:
- Consolidated 4 tools (brightness-contrast, saturation, color-channels,
color-effects) into single "Adjust Colors" tool
- Added exposure, temperature, tint, hue, sharpness controls
- SVG filter-based live preview for all adjustments
- Backward-compatible URL redirects from old tool paths
Other fixes:
- Favicon tool: download button instead of auto-download
- Batch processing: HEIC filename extension fix
- File store: processedFilename field for proper batch downloads
- Add bidirectional HEIF support: decode (input) and encode (output) via system heif-convert/heif-enc
- Add server-side WebP preview generation for non-browser-previewable formats (HEIC, TIFF)
- Fix content-aware resize failing on HEIF input (decode before passing to caire)
- Fix content-aware resize timeout on large images by downscaling to max 1200px and using JPEG intermediate
- Add HEIF as target format in convert tool
- Add loading spinner for HEIF preview decode in file store
- Fix file picker not accepting HEIF files (explicit .heic,.heif,.hif extensions)
- Extend frontend timeout for medium tools to 180s with 45s progress animation
- Redesign rotate controls with preset buttons and compact flip section
- Remove misleading savings percentage from convert tool
Replace the Python seam-carving library with caire (esimov/caire v1.5.0),
a Go-based content-aware resize engine that is faster and supports both
shrinking and enlarging via seam insertion.
- Add Go builder stage in Dockerfile to compile caire from source
- Rewrite seam-carving.ts to call caire via execFile (no Python sidecar)
- Remove content-aware-resize from PYTHON_SIDECAR_TOOLS (60s timeout)
- Add new options: blur radius, edge sensitivity, square mode, face detection
- Move content-aware toggle below standard resize in UI (subtler placement)
- Rename "Don't enlarge" to "Limit to original size" with hover tooltip
- Add smooth progress bar for medium-duration tools
- Delete seam_carve.py and remove seam-carving pip dependency
- Update integration tests and visual regression screenshots
- Replace Rule of Thirds button with checkbox for clearer toggle
- Show side-by-side comparison after crop instead of overlay slider
- Add custom aspect ratio option with W:H number inputs
- Fix batch crop failing on files with different dimensions by
sending percentage-based coordinates instead of absolute pixels
- Add failed-file error state display in tool page
Several RBAC features from feat/rbac-permissions were silently lost
during the merge into main. This restores and completes them:
- Add permissions and teamName to login/session API responses
- Export Permission and Role types from shared package
- Filter settings tabs by user permissions in frontend
- Extend useAuth hook with role, permissions, and hasPermission
- Restrict teams listing to admin only
- Add admin override for API keys, files, and pipelines listing
- Add ownership scoping to file access, download, and delete routes
- Register userFileRoutes in integration test server
- Mock auth import in unit permissions test to avoid SQLite lock
Replace requireAuth with requirePermission("pipelines:own") on pipeline
save/list/delete routes. Admin users with pipelines:all can see and
delete all pipelines. Unauthorized delete returns 404 to avoid leaking
resource existence.
Migrate settings, teams, branding, and user management routes to use
the new permission-based authorization system. Remove requireAdmin
function entirely.
Create the RBAC permission module that maps roles to permissions and
provides a requirePermission middleware to replace requireAdmin. Update
the test server to use requirePermission for the admin health check.