validateImageBuffer catches corrupt image data before processing
reaches the tool handler, so the correct status code is 400 (bad
request) rather than 422 (processing failure). Also fix SVGZ
watermark validation by returning early for compressed SVG (Sharp
cannot read gzip-compressed SVGZ directly) and passing the actual
watermark filename to validateImageBuffer for correct format
detection.
The transparency-fixer now directly detects the baked-in checkerboard
pattern using per-pixel chroma analysis instead of BiRefNet AI matting.
Achromatic pixels in the gray range are classified as background
(transparent), chromatic pixels as foreground (opaque), with smooth
transitions at anti-aliased edges.
- No longer requires Python sidecar or background-removal bundle
- Watermark removal uses Sharp median(5) filter pre-processing
- Moved tool from "ai" to "utilities" category
- Removed from PYTHON_SIDECAR_TOOLS and background-removal enablesTools
- Near-instant processing (pure Sharp, no model inference)
The luminance anomaly detection + LaMa inpainting approach failed because
watermark signal on the matted foreground was too weak (10-15 units vs
threshold of 25). Median filter with kernel=5 effectively removes
semi-transparent watermark text while preserving the stamp structure.
Pipeline is now: median filter (if toggle on) -> BiRefNet matting -> defringe.
No longer requires object-eraser-colorize bundle for watermark removal.
Add tier enum (fast/balanced/high) with balanced default to the Zod
settings schema. Pass tier through to outpaint options in both the HTTP
route and the pipeline/batch registry. Fix log message to say
"Starting AI canvas expand" and include tier in structured log fields.
- Fix dispatcher pipe deadlock: drain stdout pipe in a background thread
to prevent blocking when ONNX runtime output exceeds 64KB pipe buffer
- Add 5-minute SSE stall timeout so the UI shows an error instead of
hanging forever when async AI processing stalls
- Guard CPU colorization: skip for images >2MP on CPU and when DDColor
model is not installed, with clear user-facing messages
- Add AVIF decode fallback via ImageMagick for bitstream variants that
Sharp's bundled libheif cannot decode (affects all tools)
HDR and EXR files decoded by ImageMagick can produce 16-bit PNG buffers.
Sharp's CLAHE operation (hist_local) requires VIPS_FORMAT_UCHAR (8-bit).
Check the buffer depth and convert to 8-bit sRGB before processing.
Sharp's TIFF encoder silently strips the alpha channel, flattening
transparency against black. This caused border (corner radius, shadow),
beautify, and replace-color tools to produce wrong output for TIFF
inputs when the operation needs transparency. Remove TIFF from
ALPHA_FORMATS so these tools fall back to PNG output.
The find-duplicates tool failed entirely when any uploaded file couldn't
be processed, returning "Duplicate detection failed" or a format-specific
error that aborted the whole batch. With mixed-format uploads (77 files),
this made the tool unusable.
- Skip unprocessable files instead of aborting; return skippedFiles in response
- Switch from fetch() to XHR with upload progress tracking (Uploading X%)
- Add Vite proxy timeout config (5min) to prevent connection drops on large uploads
- Add "Download Grouped" button: ZIP with each duplicate group in its own folder
- Add collapsible skipped-files section in the results UI
- Add 3 integration tests for skip behavior (43 total)
Custom-route tools (split, compare, collage, find-duplicates, etc.) only
handled HEIC via ensureSharpCompat, failing on BMP, PSD, RAW, TGA, EXR,
HDR, JXL, and other formats Sharp cannot decode natively. Added the full
decode pipeline from createToolRoute to all 16 affected routes: format
validation via validateImageBuffer, HEIC decoding with actionable error
messages, CLI-based exotic format decoding with nested fallback, and SVG
sanitization to prevent XXE/SSRF/script injection.
The vectorize tool had a custom processing flow that bypassed the
standard useToolProcessor hook -- no progress indication, no server-side
batch, and the Download All ZIP relied on a client-side sequential loop.
Backend: extract core logic into vectorizeBuffer(), register via
registerToolProcessFn() so the /batch endpoint works with p-queue
concurrency and SSE progress events.
Frontend: replace custom fetch loop with useToolProcessor hook and
ProgressCard, giving upload progress, per-file batch status, and
automatic Download All ZIP via the existing tool-page infrastructure.
Also set image/svg+xml MIME type on SVG blobs during batch ZIP
extraction to ensure reliable rendering in <img> tags across browsers.
The stitch route only decoded HEIC via ensureSharpCompat, so exotic
formats (DNG, PSD, TGA, BMP, JXL, HDR, QOI, DDS, ICO, JP2, DPX, etc.)
crashed Sharp at metadata read time, causing "Stitch creation failed".
Add the full CLI decode pipeline (decodeToSharpCompat) matching the
tool-factory pattern, plus SVG sanitization with proper error handling
for each format category.
Replace fetch() with XMLHttpRequest in the frontend to surface upload
progress via onprogress events, and add a progress bar that shows upload
percentage then pulses during server-side stitching.
The X-File-Results header contained raw JSON with non-ASCII characters
from filenames (Chinese, Japanese, etc.), violating RFC 7230. Node.js
threw ERR_INVALID_CHAR on writeHead(). Fixed by wrapping the JSON in
encodeURIComponent() on the backend and decodeURIComponent() on the
frontend, ensuring only ASCII goes into the header while preserving
the original filenames after decoding.
Closes#133
Sharp can read TIFF-based RAW files (DNG, CR2, NEF) directly for
metadata without requiring ImageMagick/darktable to fully decode them.
Try Sharp on the raw buffer first; only fall back to the decode pipeline
for formats Sharp cannot open (PSD, ICO, TGA). For RAW files, enrich
with ExifTool to get real sensor dimensions instead of thumbnail size.
Error from a failed image (e.g. DNG) persisted when navigating to a
previously cached image because the cache path skipped setError(null).
Also pass the original file extension to decodeToSharpCompat so RAW
variants get the correct temp file suffix for ExifTool/ImageMagick.
The compose route only decoded HEIC/HEIF via ensureSharpCompat, causing
EPS, PSD, BMP, RAW, and other exotic formats to fail with
"Processing failed". Now uses the same full format pipeline as the
tool-factory for both base and overlay buffers.
The favicon route bypassed createToolRoute and only handled HEIC/HEIF
via ensureSharpCompat. Exotic formats (PSD, BMP, JXL, JP2, EXR, HDR,
DNG, ICO, TGA, etc.) passed validation but crashed Sharp after
reply.hijack(), causing the response stream to hang silently until the
5-minute XHR timeout.
Now decodes all formats before hijacking the response, matching the
pipeline used by createToolRoute. Files that fail to decode are skipped
with a skipped-files.txt manifest in the ZIP rather than aborting the
entire batch.
Per-image try-catch with Sharp fallback for CLI-decoded formats (matching
createToolRoute), SVG decompression/sanitization, and filename-specific
error messages so users know which image failed and why.
The image-to-pdf route used ensureSharpCompat (HEIC-only) instead of the
full format decode pipeline from tool-factory. Formats like FITS, PSD,
RAW, EXR, HDR, TGA, etc. passed through undecoded and crashed Sharp.
Replace with validateImageBuffer + decodeToSharpCompat to match the
standard tool pipeline.
- Refactor use-tool-processor and use-pipeline-processor hooks
- Enhance dropzone component with improved UX
- Improve seam carving with better error handling and tests
- Add JXL format encoding support to format-encoders
- Update tool routes for consistent format handling
- Add dropzone unit tests
CLAHE width/height is tile size in pixels, not tile count. A 3px tile on
a 992x1088 image created ~330x360 independent histogram regions, producing
crosshatch/etching artifacts. Now uses image_dimension/8 (clamped 8-256)
for ~8 tiles per axis. Also strips alpha before enhancement and re-joins
after to prevent CLAHE/normalise/linear from corrupting transparency.
- Fix SSE write-after-end crash in progress.ts (remove callback before ending stream)
- Fix blob URL memory leaks: revoke processedPreviewUrl and old HEIC preview URLs
- Add AbortController to batch fetch in use-tool-processor and use-pipeline-processor
- Fix TGA format misidentified as CUR (extension overrides magic bytes)
- Add libheif-plugin-libde265 to Docker for HEIC/HEIF decode support
- Remove unused imports and state (AppLayout, setSampledColor, useEffect)
- Fix non-null assertions in meme-text-renderer and meme-generator
- Fix confusing void type in meme-templates
- Remove unnecessary useEffect deps in adjustments-panel
- Fix Playwright strict mode violations in 5 E2E tests
When deepEnhance is true and the upscale-enhance bundle is installed,
runs SCUNet (quality tier) after the Sharp pipeline for noise/artifact
cleanup. Falls back gracefully to Sharp-only if sidecar is unavailable.
CLAHE provides adaptive local contrast, normalise stretches the
histogram, and gamma adjusts exposure perceptually. Replaces the old
modulate/linear pipeline that compounded errors and darkened images.
Preset multipliers now include clahe and normalise entries.
Key fixes beyond the spec:
- maxSlope rounded to integer (Sharp requirement)
- White balance uses linear() instead of recomb() to avoid float-cast
that breaks CLAHE in the libvips pipeline
- CLAHE tile size adapts to image dimensions (1x1 for tiny images)
- Gamma clamped to Sharp's valid range (1.0-3.0)
- Normalise lower/upper correctly mapped to percentile cutoffs
Recovers expanded export format support from orphaned commit 80961c01
and PSD export from b07ecd5. The editor export dialog now supports 7
formats (PNG, JPEG, WebP client-side; AVIF, TIFF, GIF, JXL via server
conversion). PSD export uses ImageMagick on the backend.
Custom route handler supporting template mode (JSON body with templateId)
and custom image mode (multipart upload). Registers process function for
pipeline compatibility. Includes 18 integration tests.
- Fix resize 20% failure rate: add Zod refine requiring at least one
dimension, enforce integer/max constraints, clamp percentage scaling
to minimum 1px, and guard against missing metadata in withoutEnlargement
- Fix PostHog init race condition: move consent check before async import
so frontend events (search, pageview) are no longer silently dropped
- Fix identify() passing nested $set/$set_once wrappers instead of flat
properties, so version person property now appears on PostHog profiles
- Add error_code and error_message to failed tool_used analytics events
for debugging tool failures from PostHog
Sentry fixes:
- Only send 5xx errors to Sentry (was sending 4xx rate-limit, media type errors)
- Encode non-ASCII chars in X-Output-Filename header (encodeURIComponent)
- Handle FK constraint failures gracefully in file upload, pipeline save, API keys
- Harden getDirSize against ENOENT race on readdirSync
Test fixes:
- Wrap navbar test renders in act() to flush async useEffect state updates
- Add useEffect cleanup to navbar to prevent state updates on unmounted component
- Fixes timeout when running in full test suite
1. passport-photo 404 vs 501: add base route at /api/v1/tools/passport-photo
that returns 501 FEATURE_NOT_INSTALLED when the AI bundle is missing,
matching other AI tools. The /generate sub-route is Sharp-only (no
sidecar) so it correctly skips the isToolInstalled guard.
2. AuthGuard analytics consent race: don't evaluate shouldShowConsent()
until analyticsConfig has been fetched (guard on analyticsConfig !== null).
Prevents redirect to /analytics-consent before config is loaded.
3. Fragile sidebar Settings selector: add openSettings(page) helper to
E2E helpers that checks sidebar visibility with fallback to button role.
Replace all 134 occurrences of page.locator("aside").getByText("Settings")
across 18 test files.
Convert all 9 AI tool routes (colorize, restore-photo, remove-background,
enhance-faces, blur-faces, red-eye-removal, erase-object, noise-removal,
upscale) to async 202 processing so none are vulnerable to proxy
connection timeouts.
Also fixes:
- Replace basename() with sanitizeFilename() in all AI tool routes
(prevents double-extension attacks and adds length truncation)
- Add UUID format validation for clientJobId field
- Fix missing filename sanitization in noise-removal (was using raw
user-supplied filename with zero sanitization)
- Remove em dash from error message in use-tool-processor