Commit Graph
302 Commits
Author SHA1 Message Date
SnapOtter 48b795d073 fix: percent-encode X-File-Results header for non-ASCII filenames (#133)
Closes #133
2026-05-13 11:07:12 +08:00
SnapOtter 5a487746d0 fix: support SVGZ (gzip-compressed SVG) in SVG-to-raster tool 2026-05-13 10:58:23 +08:00
SnapOtter 8637105bce fix: add exotic format decoding to image-to-pdf tool 2026-05-13 10:39:38 +08:00
SnapOtter c15e94c969 fix: add exotic format decoding and SVG sanitization to all custom-route tools
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.
2026-05-13 10:39:17 +08:00
SnapOtter f93e864094 fix: add progress bar and batch download to vectorize tool
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.
2026-05-13 10:36:35 +08:00
SnapOtter 0874cfc809 fix: add exotic format decoding and upload progress to stitch tool
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.
2026-05-13 10:27:59 +08:00
SnapOtter ac8c585beb fix: percent-encode X-File-Results header to support non-ASCII filenames
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
2026-05-12 23:19:20 +08:00
SnapOtter 2d17fd4903 fix: show real image dimensions in right pane for DNG/RAW files
The preview endpoint now returns X-Original-Width/Height headers with
dimensions read from Sharp metadata (or ExifTool for RAW files). The
frontend stores these in FileEntry and the ImageViewer prefers them over
the browser's naturalWidth/naturalHeight, which reflects the resized
preview rather than the original sensor dimensions.
2026-05-12 22:13:58 +08:00
SnapOtter 8c1526559b fix: read DNG/RAW metadata without full decode in image info tool
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.
2026-05-12 21:55:17 +08:00
SnapOtter 7a3ec93847 fix: clear error when navigating to cached image info and pass RAW extension
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.
2026-05-12 21:02:35 +08:00
SnapOtter 449f5dc97e fix: add exotic format decoding to image composition tool
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.
2026-05-12 19:41:23 +08:00
SnapOtter a86129187c fix: add exotic format decoding to favicon generator
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.
2026-05-11 23:58:08 +08:00
SnapOtter d14691f75b fix: add graceful error handling and SVG sanitization to image-to-pdf
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.
2026-05-11 23:34:39 +08:00
SnapOtter 56e8cf7352 fix: add exotic format decoding to image-to-pdf tool
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.
2026-05-11 22:47:10 +08:00
SnapOtter 095e3d9488 feat: add URL-based image import (single + bulk)
Add a fourth image ingestion path: importing images by URL.

Backend:
- POST /api/v1/fetch-urls endpoint with SSRF protection, image validation,
  preview generation, and p-queue concurrency
- SSRF utility blocking private IPs, validating redirect hops, with
  comprehensive IPv4/IPv6 range coverage

Frontend:
- Always-visible URL input in the dropzone for quick single-image import
- Bulk URL import modal with smart URL parsing (lists, markdown, HTML),
  per-URL progress tracking, retry on failure, and batch add
- useUrlImport hook managing the full fetch lifecycle

Tests: 48 new tests (23 SSRF unit, 10 URL parser unit, 15 integration)
2026-05-11 22:41:47 +08:00
SnapOtter b06906025c refactor: improve tool processing, dropzone, seam carving, and format encoding
- 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
2026-05-11 21:57:40 +08:00
SnapOtter 4a9cc715f2 fix: deduplicate filenames in fetch-urls to prevent overwrites
All URLs in a batch share a single workspace directory. When multiple
URLs resolve to the same filename (e.g. two different domains both
serving photo.jpg), the second writeFile silently overwrites the first.

Track used filenames in a Set and append _1, _2, etc. on collision,
mirroring the existing getUniqueName pattern from batch.ts.
2026-05-11 21:32:27 +08:00
SnapOtter d38101b8ea feat: add POST /api/v1/fetch-urls endpoint for server-side URL import
Accepts { urls: string[] } (1-50), fetches each URL with SSRF protection
via safeFetch, validates as image, saves to workspace, generates WebP
preview for non-browser formats, and returns results with download URLs.
Uses p-queue with concurrency 4 to parallelize fetches.
2026-05-11 21:24:56 +08:00
SnapOtter 694cc86aff feat: add content-aware-crop API route with async SSE processing 2026-05-11 21:12:00 +08:00
SnapOtter 2acf0ea48a fix: fall back to Sharp when CLI decoders are unavailable
DNG and FITS previews showed "Preview not available" because their CLI
decoders (ExifTool/ImageMagick) were not installed. Sharp can read both
formats natively (DNG is TIFF-based, FITS via libvips fitsload).

Added Sharp fallback to the preview endpoint, batch processing, and
tool factory: when decodeToSharpCompat throws, try sharp(buffer).metadata()
before returning 422. If Sharp can read the buffer, processing continues
without the CLI decoder.
2026-05-11 18:00:59 +08:00
SnapOtter 172c0fa772 fix: update tool count to 51, refresh hero copy and login page
- Update stale tool counts (48/49/50) to 51 across all apps, docs, and tests
- Add Meme Generator to landing page bento grid (was missing)
- Add 12 SEO-friendly rotating phrases to hero typing cursor
- Change landing hero subtitle to "Every image tool you need."
- Replace outdated login page copy with branded hero + rotating phrases
2026-05-10 22:18:11 +08:00
SnapOtter a0556772e8 test: expand coverage across all layers -- 1,268 new tests, fix replace-color div-by-zero
14-agent parallel test expansion covering integration, unit, E2E, E2E-Docker,
cross-format matrix, adversarial, GUI navigation/tools/settings/visual/a11y/perf.

- Integration: expand 23 tool test files with HEIC, stress, batch, edge cases
- Unit: close coverage gaps in image-engine, stores, lib (metadata, auto-enhance,
  connection-store, lazy-with-retry, collage/file-store HEIC preview)
- AI bridge: 141 new tests for dispatcher buffering, crash recovery, OOM/segfault
- Cross-format: 794 parameterized tests (16 formats x 12 tools + no-crash matrix)
- Adversarial: memory stress (50x large file), zero-byte, corrupted headers, unicode
- E2E-Docker: expand 8 spec files with dimension verification, pipeline chains
- GUI E2E: tool UI settings/interactions for all 47 tools, remove all test.skip,
  RBAC per-role verification, visual screenshot naming, cross-browser smoke tests,
  a11y ARIA/focus/contrast, performance budgets, 15-tool stability test
- Fix: replace-color.ts tolerance=0 caused division-by-zero producing NaN pixels

Total: 8,958 tests passing across 202 files. Zero failures, zero skips.
2026-05-09 18:02:58 +08:00
SnapOtter 7b1f09f5d0 fix: CLAHE tile size and alpha channel corruption in image enhancement
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.
2026-05-09 15:20:55 +08:00
SnapOtter 7f131d99a6 fix: QA sweep -- SSE crash, memory leaks, HEIC Docker decode, TGA detection, lint cleanup
- 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
2026-05-09 13:55:00 +08:00
SnapOtter 40522ebe1a feat: add Deep Enhance AI tier to image enhancement
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.
2026-05-09 11:29:27 +08:00
SnapOtter b61be9e438 fix: replace enhancement pipeline with CLAHE + normalise + gamma
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
2026-05-09 11:24:00 +08:00
SnapOtter e3e5fd5890 Merge branch 'testing/image-editor' into main
Merges all image editor work: RAW decode improvements, expanded format
export (AVIF/TIFF/GIF/JXL/PSD), Photoshop-style menu bar, custom Konva
filters, smart guides, clone stamp, dodge/burn, eyedropper, pixel brush,
selection and transform tool overlays, autosave blob URL persistence,
and 49+ bug fixes across editor canvas and E2E tests.

# Conflicts:
#	apps/api/src/routes/tool-factory.ts
#	apps/api/src/routes/tools/convert.ts
#	apps/web/src/components/editor/common/export-dialog.tsx
#	apps/web/src/components/editor/editor-canvas.tsx
2026-05-08 21:37:52 +08:00
SnapOtter 3a02affae3 feat: recover AVIF/TIFF/GIF/JXL/PSD export from orphaned commit
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.
2026-05-08 21:06:29 +08:00
SnapOtter e7001df047 fix: add ImageData type assertion for barcode reader typecheck 2026-05-08 19:08:38 +08:00
SnapOtter aa773addb4 merge: resolve conflict with main branch in tool-registry.tsx 2026-05-08 18:55:28 +08:00
SnapOtter e118717337 merge: resolve conflict with main branch in README.md 2026-05-08 17:32:16 +08:00
SnapOtter 466ef1efb1 docs(beautify): add API docs, OpenAPI spec, LLM docs, update README
Add beautify tool endpoint to OpenAPI spec with full parameter
documentation. Add tool to REST API docs Layout & Composition table.
Update tool count from 49 to 50 across README, docs homepage,
architecture docs, VitePress config, OpenAPI spec, and LLM docs
generator.
2026-05-08 16:31:43 +08:00
SnapOtter 136a4a0752 Merge branch 'feat/format-support-expansion'
# Conflicts:
#	README.md
#	apps/web/src/components/layout/app-layout.tsx
2026-05-08 16:21:11 +08:00
SnapOtter a90ba372df feat(beautify): add HTTP route, process pipeline, and pipeline registration 2026-05-08 16:17:17 +08:00
SnapOtter 6ec3a51fa4 feat(meme-generator): add meme generation API route with template and custom image modes
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.
2026-05-08 16:13:34 +08:00
SnapOtter b0cb17ff55 feat(meme-generator): add template listing and static serving endpoints 2026-05-08 16:05:17 +08:00
SnapOtter eedc2443d6 feat: add color-blindness API route and integration tests 2026-05-08 15:18:38 +08:00
SnapOtter 80961c0187 feat: add JXL to all tool format selectors, expand convert with BMP/ICO/JP2/QOI, add server-side editor export 2026-05-08 00:19:19 +08:00
SnapOtter 5c9fa2de49 feat: add decode pipelines for JP2, EPS, QOI, DDS, CUR, DPX, FITS, PPM, SVGZ, APNG
Add input decode support for 10 new image format families:

- JPEG 2000 (JP2/J2K): opj_decompress with ImageMagick fallback
- EPS: ImageMagick + Ghostscript delegate with 50MB size guard
- DDS: ImageMagick decode, first frame extraction
- CUR: reuses ICO decoder (structurally identical)
- DPX/Cineon: ImageMagick with sRGB colorspace conversion
- FITS: ImageMagick with normalize + sRGB conversion
- QOI: stub decoder (real codec deferred to Task 4)
- PPM/PGM/PBM/PFM: Sharp-native via libvips (no CLI decoder needed)
- SVGZ: gzip decompression with bomb protection before SVG sanitization
- APNG: accepted via extension, decoded as PNG first frame by Sharp

Updates magic bytes, MIME mappings, and frontend accept lists across
all file picker entry points (dropzone, tool page, file upload, editor).
2026-05-08 00:05:50 +08:00
SnapOtter 5205f4a215 feat: expand camera RAW support with exiftool-first decode strategy
- Add CR3 (Canon) brand detection in ftyp magic bytes, returning "raw"
  to route through the RAW decode pipeline
- Add magic bytes for Fujifilm RAF, Sigma X3F, and Minolta MRW formats
- Expand RAW_EXTENSIONS set from 6 to 24 formats (CR3, NRW, RAF, PEF,
  3FR, IIQ, SRW, X3F, RWL, GPR, FFF, MRW, MEF, KDC, DCR, ERF, PTX)
- Rewrite decodeRaw() with two-tier strategy: ExifTool embedded JPEG
  extraction (fast path) with ImageMagick+LibRaw fallback (full decode)
- Add ext parameter to decodeToSharpCompat/decodeRaw so temp files use
  the original extension for correct format identification
- Pass fileExt from tool-factory when calling decodeToSharpCompat
- Add JXL, ICO, PSD, EXR, HDR, TGA MIME entries to CONTENT_TYPE_TO_EXT
2026-05-07 23:59:47 +08:00
SnapOtter 3bcf06f1ec feat: expand camera RAW support with exiftool-first decode strategy
- Add CR3 (Canon) brand detection in ftyp magic bytes, returning "raw"
  to route through the RAW decode pipeline
- Add magic bytes for Fujifilm RAF, Sigma X3F, and Minolta MRW formats
- Expand RAW_EXTENSIONS set from 6 to 24 formats (CR3, NRW, RAF, PEF,
  3FR, IIQ, SRW, X3F, RWL, GPR, FFF, MRW, MEF, KDC, DCR, ERF, PTX)
- Rewrite decodeRaw() with two-tier strategy: ExifTool embedded JPEG
  extraction (fast path) with ImageMagick+LibRaw fallback (full decode)
- Add ext parameter to decodeToSharpCompat/decodeRaw so temp files use
  the original extension for correct format identification
- Pass fileExt from tool-factory when calling decodeToSharpCompat
- Add JXL, ICO, PSD, EXR, HDR, TGA MIME entries to CONTENT_TYPE_TO_EXT
2026-05-07 23:58:48 +08:00
SnapOtter 16af9c573a feat: remove app name and logo customization feature
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.
2026-05-07 19:41:30 +08:00
SnapOtter 979a833978 fix: resolve analytics data gaps and resize validation failures
- 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
2026-05-06 23:21:45 +08:00
SnapOtter eb3d0828cb docs: add transparency-fixer endpoint documentation 2026-05-05 23:24:22 +08:00
SnapOtter b1959e977d fix: improve defringe quality and add defensive guards in transparency-fixer 2026-05-05 23:07:14 +08:00
SnapOtter 423711ac42 feat: add transparency-fixer API route with defringe post-processing 2026-05-05 23:03:07 +08:00
SnapOtter e358634f8b fix: production CSP blocking PostHog/Sentry/Scalar and silent failure hardening
The production CSP had connect-src/script-src/font-src set to 'self' only,
silently blocking all analytics and error reporting in production while
working fine in dev (where CSP is not applied).

CSP fixes:
- Add PostHog ingest + assets origins to connect-src and script-src
- Add Sentry ingest origin to connect-src
- Add Scalar fonts origin to font-src for API docs pages
- Extract CSP construction into testable buildCsp() function

Silent failure hardening:
- Settings/features stores now set loadError flag and allow retry on
  subsequent fetch() calls instead of permanently caching failed state
- Analytics init no longer sets initialized=true before the try block,
  allowing retry on failure
- Settings dialog Tools section disables save button when settings
  failed to load, preventing accidental config wipe
- Branding logo storage moved from process.cwd() to FILES_STORAGE_PATH
  so logos persist across Docker container recreation

Test coverage:
- 16 CSP directive tests covering all external service domains
- Store retry-on-error behavior tests for settings and features stores
- Analytics init retry-after-failure test
2026-05-05 17:16:19 +08:00
SnapOtter 66211ed6a7 fix: resolve 20 Sentry issues and fix navbar test flakiness
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
2026-05-01 19:01:05 +08:00
SnapOtter ff8dcf63c7 fix: resolve 14 security, correctness, and robustness issues found during QA sweep
Security fixes:
- Add auth + ownership check to thumbnail endpoint (was unauthenticated)
- Validate ExifTool fieldsToRemove against safe tag name pattern
- Add SVG sanitization to pipeline execute and batch endpoints
- Replace basename() with sanitizeFilename() in 16 tool routes
- Escape SQL LIKE wildcards in file search to prevent pattern injection
- Improve settings HTML tag validation pattern

Bug fixes:
- Skip autoOrient for SVG inputs in pipeline (prevents misinterpretation)
- Remove double-encode in compress targetSize (was degrading quality)
- Fix bg-effects alpha value from 255 to 1.0 (Sharp expects float)
- Guard download stream error handler against headers-already-sent race
- Use O_EXCL atomic file creation for install lock (fixes TOCTOU race)
- Truncate collage file array to template image count

UX fixes:
- Accept empty JSON bodies on POST endpoints (install/uninstall)
- Custom JSON content type parser that treats empty body as {}
2026-05-01 18:11:49 +08:00
SnapOtter fa479dcee4 fix: resolve 3 pre-existing issues found during test coverage expansion
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.
2026-05-01 13:44:04 +08:00