Add TIER_PARAMS dict with fast/balanced/high presets controlling band
size, mask dilation, seam strip width, and Telea pre-inpainting. Parse
tier from sys.argv[7] with balanced fallback. Conditional Telea and
seam refinement steps skip cleanly for fast tier. Progressive outpaint
now accepts band_size and progress bounds for tier-appropriate scaling.
- 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)
AVIF (and other Sharp-native formats) were written as raw bytes to a
.png temp file, causing PIL to fail with "cannot identify image file".
Every other AI module wrapper already converts via sharp().png().toBuffer()
before writing; face-landmarks was the only one that skipped this step.
- 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
The binary search exited early at 5% tolerance, producing output like
48.3KB for a 50KB target. Reducing to 1% with 12 iterations makes the
output much closer to the requested target.
The binary search found the right quality but sharp(buffer).toBuffer()
re-encoded at default quality 80, inflating the output (e.g. 50KB target
producing 90KB). Replaced buffer-wrapping with proper Sharp pipelines
that include .toFormat() with the proven quality. Also added progressive
dimension reduction when quality alone cannot reach the target, and
tightened tolerance to only accept at-or-below-target results.
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.
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
Contrast score now uses linear stdevLum/1.2 centered at 50 (was
miscalibrated 25-75 range centered at 75). Corrections use dead zones
(score 40-60 = zero) so well-exposed images get near-zero adjustments
instead of being darkened.
Add colorBlindness() operation with 8 simulation matrices (Vienot/Machado)
for protanopia, deuteranopia, tritanopia, protanomaly, deuteranomaly,
tritanomaly, achromatopsia, and blue cone monochromacy.
Users can no longer customize the app name or logo. The branding API
endpoints, permission, frontend UI, env vars (APP_NAME, MAX_LOGO_SIZE_KB),
and all related tests are removed. Includes a migration to clean up
branding data from existing databases.
- 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
The BiRefNetHRMattingSession.predict normalization crashes when all
pixels share the same value (ma == mi). Use a guarded denominator so
uniform-alpha inputs produce a zero mask instead of a NaN explosion.
Also adds _register_birefnet_hr_matting() to install_feature.py so the
HR-matting model can be downloaded during feature installation, matching
the existing registration in remove_bg.py.
The upscale function called runPythonWithProgress without a timeout parameter,
defaulting to the bridge's 10-minute hard limit. On CPU-only systems like
Synology NAS devices, Real-ESRGAN 4x upscaling easily exceeds this for modest
images. Additionally, when the timeout fired on the dispatcher path, the Python
process was left running and blocked all subsequent AI operations.
This fix adds an adaptive timeout based on input megapixels, scale factor, and
GPU availability (180s/effective-MP on CPU, 30s/effective-MP on GPU, floor of
10 minutes). It also kills the dispatcher on timeout so subsequent requests can
proceed via a fresh restart.
Closes#119
The dispatcher was lazy-initialized on first AI request, but a race
condition meant the first call always missed it (dispatcherReady still
false) and fell through to cold per-request Python. initDispatcher()
starts the dispatcher eagerly and returns a Promise that resolves with
GPU status once ready (or after a timeout).
The close handler called recordCrash() unconditionally, even for exit
code 0 (normal MAX_REQUESTS restart). After 5 normal cycles within 60s
the dispatcher was permanently disabled. Now only non-zero exits count.
gpu.onnx_providers() trusted gpu_available() which returns True via
torch.cuda without checking whether onnxruntime actually has
CUDAExecutionProvider compiled in. When onnxruntime (CPU-only) is
installed, this caused silent fallback to CPU in every ONNX-based tool.
Now verifies onnxruntime.get_available_providers() directly and emits a
diagnostic warning when torch sees CUDA but onnxruntime does not.
Closes#104
Security:
- Apply sanitizeSvg() to all file upload routes (files.ts, user-files.ts)
preventing SSRF and script injection via SVG uploads to file library
Functional:
- Handle PaddleOCR-VL 1.5 markdown_texts output format in ocr.py
- Add empty-text fallback in OCR tier chain (ocr.ts) so higher tiers
that return empty text fall back to the next tier automatically
- Fix SVG->PNG filename extension mismatch in tool-factory.ts so
download endpoint serves correct Content-Type
- Report original upload size (not decoded size) in API response
Test infrastructure:
- Move Playwright auth state from test-results/ to .playwright/ to
prevent mid-run cleanup deleting auth files
- Fix auth.setup.ts navigation race with waitForURL
- Fix gui-batch.spec.ts regex matching "Presets" instead of "reset"
- Fix pipeline-advanced.spec.ts crop bounds and resize assertions
- Broaden pipeline cleanup to include all E2E-prefixed pipelines
- Add 55 unit tests for feature-status.ts (installed.json CRUD, cache
behavior, install lock, model verification, crash recovery, composite
state) using real temp directories
- Add 36 integration tests for full install/uninstall lifecycle against
Docker containers (face-detection bundle, SSE progress, tool gates,
shared model protection, concurrent install prevention, auth guards,
container restart recovery)
- Fix noise-removal CPU timeout by adding megapixel-based timeout
calculation (120s/MP, min 5 minutes)
- Fix Playwright auth storage state race condition (mkdirSync before
saving analytics-user.json)
- Fix 2 skipped tests in fixes-verification.spec.ts by replacing
external ~/Downloads/sample dependency with existing test fixtures
- Enable skipped analytics-consent settings toggle test
- Restructure features.spec.ts to manage bundle state (uninstall/
reinstall OCR) so 501 guard tests run instead of skipping
- Update noise-removal test mock to include sharp metadata() method
Skip alpha matting on CPU (pymatting's sparse matrices are the main
memory hog), auto-downscale images above 2048px before sending to
rembg, and retry with the lighter u2net model when OOM is detected.