Commit Graph
1675 Commits
Author SHA1 Message Date
SnapOtterandGitHub 622c9f98a5 fix(ci): resolve pre-existing failures making main red (#250)
After #249 merged, main's CI is red on failures that predate this work (they
were cache-masked on the old branch). This applies only the missing fixes onto
current main (reverts nothing from #246/#248; saml/user-files are already fixed
on main):

- remove 8 dead landing unit tests importing @landing/app and @landing/components
  React paths deleted in the Astro migration
- format tool-factory.ts and json-xml.ts to clear the api lint errors
- accept a 202 async fallback in the image-enhancement large-image test (it
  exceeds the sync window on slower CI runners)
- relax the pipeline no-file assertion to match 'No image file provided' or
  'No file provided'
2026-06-16 16:09:45 +08:00
SnapOtterandGitHub 7865339fc8 fix(lint): biome-format tool-factory and json-xml to green apps/api lint (#252)
Collapses the error_message line introduced in #251 plus the pre-existing
preValidate blocks and the json-xml wrapped assignment that were already
failing apps/api lint on main. Pure formatting, no logic change.
2026-06-16 16:07:15 +08:00
SnapOtterandGitHub 08961fcc89 fix: PDF tool QA sweep - library auto-save versioning, AI fileId threading, modality polish (#251)
* fix(pdf): never enlarge on compress, honor redact case, hide same-format convert

- compress-pdf: guard both modes so output is never larger than the input; low-DPI scans could be upsampled and grow. Falls back to the original bytes.

- doc_redact.py: caseSensitive=true now filters PyMuPDF's case-insensitive search to exact-case hits, so the toggle works instead of always over-redacting.

- convert-{document,presentation,spreadsheet}: omit the input's own format from the output dropdown; the backend already rejects same-format conversions.

Verified end-to-end against an isolated Docker stack during a full visual QA sweep of all 37 PDF tools.

* fix(ui): show real multi-file preview thumbnails per modality

The bottom multi-file preview strip rendered a raw <img src=blobUrl> for every file, so audio/video/PDF inputs showed a broken-image icon plus the filename. ThumbnailStrip now branches on FileEntry.previewKind: images use <img> (icon fallback on error), video shows a captured first frame, PDF shows a pdf.js page-1 render, and audio/other show a type icon + extension. Fixes the multi-file preview across all modalities.

Verified in the browser for image/PDF/audio/video.

* fix(modality): make pipeline, batch validation, save/upload, previews & UI modality-aware

The app grew up image-only; several paths still assumed image. They now dispatch on the tool/file modality (image/video/audio/document/file):

- pipeline /execute + /batch: validate+decode input via inputHandlerFor(modality) instead of validateImageBuffer, so PDF/audio/video/data pipelines work (were rejected 'Invalid image').

- batch: non-image inputs now get per-modality validation (ffprobe/qpdf) before the worker instead of passing through unchecked.

- files /upload, user-files /save-result + /thumbnail: accept non-image files (MIME from extension; video-poster / pdf-first-page thumbnails).

- postprocess CONTENT_TYPE_TO_EXT: cover video/audio/pdf/text/zip so output extensions are corrected for all modalities.

- worker pipeline-finalize: attach result payload to the complete SSE event so the sync-window-timeout fallback still delivers a download.

- frontend: batch-ZIP blob MIME by extension (not svg-only); modality-neutral fallback labels/filenames; 'smaller file' not 'smaller image'.

Found via a codebase-wide image-only-assumption audit. Verified: PDF/audio/video pipelines + batch now work; image paths unchanged. canBrowserPreview kept image-only by design (non-image is rendered by dedicated displayMode viewers).

* fix(pipeline): generate a modality-aware preview for pipeline results

processPipelineFinalize now derives the output content type from its extension and runs generatePreview (video poster / pdf first page / image thumb), sets previewRef on the result, and surfaces previewUrl in the /execute sync response and the SSE complete event (via buildLegacyResultPayload). Pipeline outputs get a preview like single-tool results instead of always returning previewUrl: undefined.

Verified: PDF pipeline -> previewUrl returns a valid PNG first-page render; png pipeline correctly has no previewUrl; audio/video/multi-step pipelines all 200.

* fix(worker): auto-save a new library version when processing a library file

The worker hardcoded savedFileId = undefined ('No auto-save') even though the whole versioning feature was wired around it: the frontend sends fileId for library files and reads result.savedFileId, tool-factory threads fileId into ToolJobData, and autoSaveToLibrary implements the new-version save -- but the worker never called it (dead code from the tool-first-workflow merge). processToolJob now calls autoSaveToLibrary with data.fileId; without a fileId it is a no-op, so tool-first uploads are unchanged.

Verified: processing a library PDF with fileId creates version 2 (parent linked, toolChain appended, savedFileId returned); processing without fileId saves nothing.

* fix(library): ownership check + modality-aware dimensions in autoSaveToLibrary

- Only create a new version when the requester owns the parent (parent.userId === opts.userId); prevents versioning another user's file via a known fileId.

- Dimensions are modality-aware: sharp for images, ffprobe (probeMedia) for video, null for audio/document. Previously sharp-only, so non-image versions always got null dims.

* fix(ai): thread fileId + real userId through the 16 AI tool routes

AI custom routes parsed neither the fileId multipart field nor the authenticated user (they hardcoded userId: null), so processing a library file via an AI tool never created a new version, and AI jobs were unattributed. Each route now parses fileId like clientJobId and passes getAuthUser(request)?.id as userId to enqueueToolJob.

Verified: ocr-pdf on a library PDF creates a new version (v2); the ownership check still denies cross-user versioning.
2026-06-16 15:48:07 +08:00
SnapOtterandGitHub d50e8e42a7 QA + image-tool depth pass: codec/eraser/PDF fixes, modality renames, 13 image tools deepened (#249)
* fix(media): mux container-correct codecs in video tools

Video tools hardcoded H.264 (and AAC) while keeping the input's container extension, so a .webm input produced an invalid file (ffmpeg exit 234: H.264 cannot be muxed into WebM). Add shared videoEncodeArgsForContainer/audioEncodeArgsForContainer helpers (vp9+opus for webm, theora+vorbis for ogv, h264+aac otherwise) and apply them across 14 tools; re-encode audio to AAC in burn-subtitles (forced mp4). Adds a webm regression test for change-fps.

* fix(eraser): recover Object Eraser when its progress SSE drops

The eraser used a bespoke EventSource with no recovery, so a dropped SSE left the UI stuck at ~25% forever even though the backend job had finished and saved its result. Add a resilient subscription (reconnect on tab refocus, which replays the cached terminal frame; 5-minute stall timeout) mirroring the standard processor's PR #203/#204 recovery.

* feat(ui): rename the Documents modality to PDF and Data to Files

Updates modality display names, the home-page tabs, the tool-page breadcrumb, and the homePage.documents/data + modalities labels across all 21 locales. URL slugs are unchanged for link stability.

* feat(compress-pdf): add quality and target-size compression modes

Mirror the image Compress tool: a quality slider (1-100) and a target file size, replacing the screen/ebook/printer preset. Adds gsCompressPdfQuality to doc-engine (quality maps to image downsample DPI, the dominant size lever for PDFs); target-size binary-searches the DPI for the highest quality under the target. The frontend reuses the shared CompressControls component, so no new translation strings are needed.

* feat(ocr-pdf): show the PDF preview and extracted text side by side

ocr-pdf fell back to the image viewer, which cannot render a PDF, so the right pane showed 'Preview not available' and the extracted text was only a download. It now uses a custom results view (custom-results display mode) rendering the input PDF via pdf.js (DocumentView gains an inputOnly prop, since the tool's output is a .txt) next to the extracted OCR text, with a copy button.

* feat(ui): link the modality breadcrumb to its tools tab

The modality segment of the tool breadcrumb (PDF, Image, Video, Audio, Files) is now a link to /?modality=<tab>. The home page reads the param, activates the matching tab, and cleans the URL, so it returns to the existing Tools page filtered to that modality without a new page. Handles the file modality whose tab key is 'data'.

* feat(circle-crop): add zoom/offset framing, border, background, and output size

Upgrade the circle-crop tool from a bare centered crop into a framing and
styling tool. New settings (all backward-compatible with the old empty
payload):

- zoom (1-5x) plus offsetX/offsetY (0-1) to control how tight the circle is
  and where it sits in the source image
- borderWidth (0-200px) plus borderColor for an optional ring
- background: transparent (clear corners) or a hex fill
- outputSize for a square output; omitted keeps native size

The settings panel gains an inline draggable circular preview that mirrors
the framing live, a zoom slider, a border slider with color, a
transparent/color background toggle, and an output-size field. Adds an
integration test covering output size, border, and a solid background.

* feat(image-tools): flesh out five thin tools (gif-webp, histogram, favicon, color-palette, lqip)

Tier A of the image-tool depth pass. Each of these was as bare as the old
circle-crop (empty settings, opaque or invisible output). Now:

- gif-webp: quality, lossless, and resize-percent controls; shows before/after size
- histogram: returns full per-channel bins + stats; the settings panel renders an
  inline interactive histogram with R/G/B/Luma toggles, linear/log scale, and a
  mean/median/stdev readout (server PNG still downloadable)
- favicon: background fill, padding, corner-radius, theme color, and a per-size
  checklist, with a live preview grid; the route applies the styling and honors
  the size filter
- color-palette: count (2-16) and hex/rgb/hsl format controls, median-cut
  extraction, a palette strip, and CSS/JSON export
- lqip-placeholder: blur/pixelate/solid strategies, format and quality; the
  output panel now surfaces the data URI with copy plus HTML/CSS snippets and a
  preview (previously the deliverable was never shown)

Also expose resultPayload from useToolProcessor so a tool can render the route's
extra result fields (histogram bins, lqip data URI) in its own panel. Updates the
five integration tests to cover the new settings.

* feat(image-tools): deepen five thin tools (duotone, vignette, pixelate, background-replace, blur-background)

Tier B of the image-tool depth pass.

- duotone: preset palettes, an intensity slider that blends the duotone with
  the original, and a true live duotone preview (a self-contained grayscale +
  lighten/darken overlay so the pane filter cannot wash it out)
- vignette: radius, softness, roundness, and center-x/y controls driving a
  rebuilt radial gradient, with a matching live overlay
- pixelate: a selection mode that exposes the route's region support via a
  draggable box over the image plus width/height sliders, so a face or plate
  can be pixelated in isolation
- background-replace: gradient backgrounds, edge feather, and webp output on top
  of the existing solid color; now shown before/after
- blur-background: edge feather and webp output; now shown before/after

The live previews for duotone and vignette needed onImageStyle to mount the
overlay branch in image-viewer. The duotone intensity blend and both AI tools'
edge feather were rewritten to splice the alpha channel through raw buffers;
joinChannel did not reliably re-tag the merged channel as alpha and a
raw-without-encoder buffer broke the next decode. Updates the five integration
tests.

* fix(data): rename Files modality to Data + 20 Data-tool bug fixes (#247)

* fix(ui): restore the Data modality name (revert Files rename)

The 'file' modality reverts to the 'Data' label in modality.ts, the home-page tab, and the tools.data + documentsAndFiles i18n keys across all 21 locales. The separate Documents to PDF rename is kept. The URL slug was already /data, so name and slug realign; the tool breadcrumb follows modality.ts automatically.

* fix(create-zip): require at least two files before enabling submit

create-zip enabled its submit button with a single file, but the backend rejects fewer than two files ('Zipping needs at least two files'), producing a 422 error. Gate the button on files.length >= 2 to match the sibling merge-csvs tool. Found during the Data-modality QA sweep.

* fix(data): resolve 17 bugs found in a deeper Data-tool review

Crashes (threw an internal error on otherwise-valid input):
- csv-json: a primitive JSON array like [1,2,3] threw "Unable to serialize"; now a clear error.
- json-xml: a null or primitive JSON root crashed the XML builder; now a clear 4xx.
- yaml-json: an empty or comment-only YAML returned undefined and threw on Buffer.from; now emits null.

Data loss / wrong output:
- csv-json: nested objects rendered as "[object Object]" (now serialized to JSON); heterogeneous objects dropped columns (now the union of all keys).
- xml-to-csv: leaked fast-xml-parser markers ("@_" on attributes, "#text") into CSV headers (now cleaned); a single-record XML failed to tabulate (now a 1-row table); heterogeneous records dropped columns (now the union of all keys).
- csv-excel: xlsx date cells were rendered in the server timezone via Date.toString (now ISO 8601, round-trippable).
- create-zip and extract-zip: filename/basename collisions overwrote zip entries and silently lost a file; dedup now checks generated names and guarantees uniqueness.
- chart-maker: negative values produced invalid/degenerate SVG that Sharp silently dropped; now rejected with a clear message.

Empty output / validation:
- split-csv: a header-only CSV produced an empty zip; now errors with "No data rows to split".
- extract-zip: a directory-only zip produced an empty zip; now errors with "No extractable files found".
- create-zip and merge-csvs: a single-file request fell through to the worker and returned 422; the factory now supports minInputs and returns 400 pre-enqueue.

UI:
- review-panel: the result card showed "Saved +X%" when the output grew; the savings row now appears only when the file is actually smaller (Original/Processed sizes always shown).

Found via two adversarial code-review passes over the 10 Data routes. All 24 fix + regression checks pass against a fresh Docker stack on :1359.

* fix(data): clean 400 for unsafe-zip entries; drop header on split keepHeader=false

- tool-factory: add an opt-in preValidate hook that runs after input prep and
  before enqueue. Throwing InputValidationError there returns its statusCode
  (400) instead of the worker's generic 422. BullMQ loses the error class across
  the job boundary, so InputValidationErrors thrown in the worker cannot be
  mapped to their status; pre-enqueue validation can.
- extract-zip: validate entry paths via preValidate, rejecting path-traversal
  and absolute-path archives (and unreadable/corrupt zips) with a clear 400. The
  processV2 guards remain as defense-in-depth for the pipeline/batch path.
- split-csv: keepHeader=false now drops the header (parts contain only data
  rows) instead of keeping it as the first data row of part-1.

Verified against a fresh Docker stack: unsafe / absolute / corrupt zips -> 400,
normal zip still 200; split keepHeader=false drops the header while true repeats
it in each part. No regressions across 51 fix + scenario checks.

* feat(image-tools): deepen image-pad and sprite-sheet, fix sprite-sheet multi-file submit

Tier C of the image-tool depth pass.

- image-pad: a custom W:H ratio alongside the presets, a background mode
  (solid color, transparent, or an Instagram-style blurred cover fill), and an
  extra padding margin. The settings panel gains a real live preview of the
  padded canvas (it previously declared live-preview but rendered nothing) via
  onImageStyle + onImageOverlay.
- sprite-sheet: PNG/WebP/JPEG output with a quality control, and the coordinate
  map it already computes is now returned and surfaced as Copy CSS (per-frame
  background-position rules) and Copy JSON exports.

Also fix a pre-existing sprite-sheet bug: with more than one image the panel
called processAllFiles, fanning out to the per-file batch route (422). It now
calls processFiles, which packs all images into a single sheet request (it is a
MULTI_FILE tool). Updates both integration tests.

* fix(media): preserve source sample rate after loudnorm (#243)

ffmpeg's loudnorm filter runs internally at 192 kHz and emits 192 kHz
unless the chain resamples back. normalize-audio and video-loudnorm
therefore produced 192 kHz output (4.3x larger files) regardless of the
input rate. Append aresample to restore the input's sample rate.
runMediaTool now exposes the input audio sample rate to its args callback.

* fix(color-palette): collapse solid-color images to one swatch

The median-cut bucket selector started bestRange at -1, so a uniform bucket
(range 0) still satisfied the > comparison and kept splitting, yielding N
identical swatches for a solid-color image. Start at 0 so only buckets with
real color spread are split.

* fix(lint): annotate implicit-any lets in saml and user-files

biome noImplicitAnyLet flagged the bare let in saml.ts (profile) and user-files.ts (stream); add derived type annotations (type-only, no behavior change). Latent on main via the turbo lint cache; surfaced when the Data changes busted the apps/api lint cache.
2026-06-16 15:16:13 +08:00
SnapOtterandGitHub 1548d475ca fix(normalize-audio): preserve source sample rate after loudnorm (#248)
ffmpeg's loudnorm filter runs internally at 192 kHz and emits 192 kHz
unless the chain resamples back, so normalize-audio produced 192 kHz
output (~4.3x larger files) regardless of the input rate. Append
aresample to restore the input's sample rate; runMediaTool now exposes
the input audio sample rate to its args callback.
2026-06-16 14:46:23 +08:00
SnapOtterandGitHub 8f6312c521 fix: repair URL import (DNS-pinned fetch on Node 22 + non-image modalities) (#246)
* fix(ssrf): honor lookup all-option so DNS-pinned fetch works on Node 22

createPinnedAgent's custom lookup always called back in single-address
form. Node 22 invokes the agent lookup with { all: true }, so the address
arrived as undefined and every URL fetch failed with "Invalid IP address:
undefined" -- breaking the URL-import feature entirely. Return the pinned
IP as an array when all is requested; the connection is still pinned to the
SSRF-validated public IP (no DNS-rebinding regression).

* fix(url-import): accept non-image modalities

fetch-urls validated every fetched file as an image and ran a Sharp
preview, so audio/video/document URL imports were rejected. Try image
validation, accept non-image media (typed from the HTTP content-type),
and skip the image preview for non-images. SSRF + size limits unchanged.
2026-06-16 14:33:30 +08:00
SnapOtterandGitHub aa3ae6ec91 fix(web): show modality thumbnails for non-image files in the strip (#245)
The multi-file thumbnail strip rendered an <img> with the file blob URL
for every entry, so audio, video, and document files showed a broken
image icon with the filename as alt text instead of a preview.

Render an <img> only when there is a real image to show (a processed
preview, a processed image output, or an image-modality original).
Otherwise show a modality icon (waveform / film / document) plus the
file's format label, using the entry's existing previewKind.
2026-06-16 14:33:27 +08:00
SnapOtterandGitHub 03e71236f9 fix: validate non-image inputs by modality (batch + pipeline) (#244)
* fix(batch): validate non-image inputs by modality

Batch processing ran validateImageBuffer and the image-only decode chain
on every uploaded file, so audio, video, and document tools rejected all
inputs with "Invalid image: Unrecognized image format" and returned
422 "All files failed processing".

Resolve the tool's modality and route non-image files through their own
input handler (inputHandlerFor(modality).prepare), mirroring the
single-file path. The image batch path is unchanged.

* fix(pipeline): validate non-image inputs by modality

The pipeline /execute and /batch routes validated every upload with
validateImageBuffer and ran the image-only decode chain, so audio,
video, and document pipelines were rejected with "Invalid image:
Unrecognized image format".

Resolve the input modality from the first step's tool and route
non-image inputs through their modality handler, mirroring the batch
and single-file paths. Image pipelines are unchanged.
2026-06-16 14:33:23 +08:00
SnapOtterandGitHub d8cf979d4b fix: resolve 18 QA-discovered bugs across tools, previews, and the AI pipeline (#242)
Exhaustive QA sweep of all 157 tools. Fixes: CSP blob media, csv-excel ExcelJS interop, ocr-pdf segfault, chart-maker upload, non-PDF doc preview, RAW decode, merge-tool multi-file path, html-to-image chromium, ogv/wma/amr/ac3 preview fallbacks, meme/gif/stabilize codecs, nav+home a11y. Plus orphan-format and test-debt cleanup, the AI bundle build script, and a reusable Playwright QA harness under tests/qa/.
2026-06-15 22:26:24 +08:00
SnapOtterandGitHub 25babfae15 test(e2e-landing): match enterprise redesign and fix Astro dev-server config (#241) 2026-06-15 22:14:50 +08:00
SnapOtterandGitHub 44bfca105f fix(ci): deploy landing from dist, not the stale Next.js out path (#240) 2026-06-15 22:00:00 +08:00
SnapOtterandGitHub f622a4f691 feat(landing): enterprise-focused hero redesign and section polish (#239)
Reframe the landing page around the self-hosted enterprise buyer.

Hero
- Rotating-modality headline with a fixed "Nothing leaves your network"
  promise (pure CSS, no client JS) and an enterprise subtitle
- Instant, modality-aware tool search (multi-word) linking to per-tool pages
- Modality cards with live counts; stat strip (150+ tools, GitHub stars,
  100K+ image pulls, 20+ languages); 7 enterprise trust badges
- Removed the Docker terminal and the hero CTAs

Sections
- "Built for enterprise deployment" redesigned as a security control plane
  (category-tagged 4-up grid, audit-ready frameworks row)
- "Built for regulated environments" polished with numbered kickers and an
  accurate Docker Compose deployment description
- ToolGrid is browse-only now (dropped its redundant search) with a clearer heading

Fixes and infra
- Per-tool pages render the real tool icon instead of a placeholder square
- Navbar GitHub stars fetch live client-side with a 1-hour localStorage cache
- Stats auto-update from the GitHub and Docker Hub APIs at build time
- Enterprise tab title and meta description
2026-06-15 21:55:00 +08:00
SnapOtter b76dc68682 fix: resolve Sharp 0.35.1 and BullMQ type incompatibilities after dep bumps
Sharp 0.35.1 moved FormatEnum to a namespace export and removed "avif"
from FormatEnum (now a separate literal in toFormat). BullMQ 5.78.1
bundles ioredis 5.10.1 while we have 5.11.1, causing structural type
mismatch. Also fixes new Biome 1.9 lint rules.
2026-06-15 15:20:16 +08:00
SnapOtter 9a61cb6af1 chore(deps-dev): bump the dev-deps group with 10 updates (#238)
Bumps @biomejs/biome, @testcontainers/postgresql, @testcontainers/redis,
@tailwindcss/vite, @types/node, @types/react, @types/yauzl, tailwindcss,
semantic-release, turbo.
2026-06-15 15:03:19 +08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
784f7a28cd chore(deps): bump the production-deps group with 15 updates (#237)
Bumps the production-deps group with 15 updates:

| Package | From | To |
| --- | --- | --- |
| [@scalar/fastify-api-reference](https://github.com/scalar/scalar/tree/HEAD/integrations/fastify) | `1.58.0` | `1.59.3` |
| [@sentry/node](https://github.com/getsentry/sentry-javascript) | `10.56.0` | `10.57.0` |
| [bullmq](https://github.com/taskforcesh/bullmq) | `5.78.0` | `5.78.1` |
| [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) | `5.8.0` | `5.9.0` |
| [ioredis](https://github.com/luin/ioredis) | `5.10.1` | `5.11.1` |
| [pdfkit](https://github.com/foliojs/pdfkit) | `0.18.0` | `0.19.1` |
| [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) | `5.35.14` | `5.37.0` |
| [sharp](https://github.com/lovell/sharp) | `0.34.5` | `0.35.1` |
| [@sentry/react](https://github.com/getsentry/sentry-javascript) | `10.56.0` | `10.57.0` |
| [posthog-js](https://github.com/PostHog/posthog-js) | `1.379.2` | `1.386.6` |
| [react-colorful](https://github.com/omgovich/react-colorful) | `5.6.1` | `5.7.0` |
| [react-konva](https://github.com/konvajs/react-konva) | `19.2.3` | `19.2.5` |
| [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) | `7.16.0` | `7.17.0` |
| [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) | `3.1066.0` | `3.1068.0` |
| [@aws-sdk/lib-storage](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/lib/lib-storage) | `3.1066.0` | `3.1068.0` |


Updates `@scalar/fastify-api-reference` from 1.58.0 to 1.59.3
- [Release notes](https://github.com/scalar/scalar/releases)
- [Changelog](https://github.com/scalar/scalar/blob/main/integrations/fastify/CHANGELOG.md)
- [Commits](https://github.com/scalar/scalar/commits/HEAD/integrations/fastify)

Updates `@sentry/node` from 10.56.0 to 10.57.0
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/10.56.0...10.57.0)

Updates `bullmq` from 5.78.0 to 5.78.1
- [Release notes](https://github.com/taskforcesh/bullmq/releases)
- [Commits](https://github.com/taskforcesh/bullmq/compare/v5.78.0...v5.78.1)

Updates `fast-xml-parser` from 5.8.0 to 5.9.0
- [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases)
- [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/compare/v5.8.0...v5.9.0)

Updates `ioredis` from 5.10.1 to 5.11.1
- [Release notes](https://github.com/luin/ioredis/releases)
- [Changelog](https://github.com/redis/ioredis/blob/main/CHANGELOG.md)
- [Commits](https://github.com/luin/ioredis/compare/v5.10.1...v5.11.1)

Updates `pdfkit` from 0.18.0 to 0.19.1
- [Release notes](https://github.com/foliojs/pdfkit/releases)
- [Changelog](https://github.com/foliojs/pdfkit/blob/master/CHANGELOG.md)
- [Commits](https://github.com/foliojs/pdfkit/compare/v0.18.0...v0.19.1)

Updates `posthog-node` from 5.35.14 to 5.37.0
- [Release notes](https://github.com/PostHog/posthog-js/releases)
- [Changelog](https://github.com/PostHog/posthog-js/blob/main/packages/node/CHANGELOG.md)
- [Commits](https://github.com/PostHog/posthog-js/commits/posthog-node@5.37.0/packages/node)

Updates `sharp` from 0.34.5 to 0.35.1
- [Release notes](https://github.com/lovell/sharp/releases)
- [Commits](https://github.com/lovell/sharp/compare/v0.34.5...v0.35.1)

Updates `@sentry/react` from 10.56.0 to 10.57.0
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/10.56.0...10.57.0)

Updates `posthog-js` from 1.379.2 to 1.386.6
- [Release notes](https://github.com/PostHog/posthog-js/releases)
- [Changelog](https://github.com/PostHog/posthog-js/blob/main/CHANGELOG.md)
- [Commits](https://github.com/PostHog/posthog-js/compare/posthog-js@1.379.2...posthog-js@1.386.6)

Updates `react-colorful` from 5.6.1 to 5.7.0
- [Release notes](https://github.com/omgovich/react-colorful/releases)
- [Changelog](https://github.com/omgovich/react-colorful/blob/master/CHANGELOG.md)
- [Commits](https://github.com/omgovich/react-colorful/commits/5.7.0)

Updates `react-konva` from 19.2.3 to 19.2.5
- [Release notes](https://github.com/konvajs/react-konva/releases)
- [Commits](https://github.com/konvajs/react-konva/commits)

Updates `react-router-dom` from 7.16.0 to 7.17.0
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@7.17.0/packages/react-router-dom)

Updates `@aws-sdk/client-s3` from 3.1066.0 to 3.1068.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-s3/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1068.0/clients/client-s3)

Updates `@aws-sdk/lib-storage` from 3.1066.0 to 3.1068.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/lib/lib-storage/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1068.0/lib/lib-storage)

---
updated-dependencies:
- dependency-name: "@scalar/fastify-api-reference"
  dependency-version: 1.59.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-deps
- dependency-name: "@sentry/node"
  dependency-version: 10.57.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-deps
- dependency-name: bullmq
  dependency-version: 5.78.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-deps
- dependency-name: fast-xml-parser
  dependency-version: 5.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-deps
- dependency-name: ioredis
  dependency-version: 5.11.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-deps
- dependency-name: pdfkit
  dependency-version: 0.19.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-deps
- dependency-name: posthog-node
  dependency-version: 5.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-deps
- dependency-name: sharp
  dependency-version: 0.35.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-deps
- dependency-name: "@sentry/react"
  dependency-version: 10.57.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-deps
- dependency-name: posthog-js
  dependency-version: 1.386.6
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-deps
- dependency-name: react-colorful
  dependency-version: 5.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-deps
- dependency-name: react-konva
  dependency-version: 19.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-deps
- dependency-name: react-router-dom
  dependency-version: 7.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-deps
- dependency-name: "@aws-sdk/client-s3"
  dependency-version: 3.1068.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-deps
- dependency-name: "@aws-sdk/lib-storage"
  dependency-version: 3.1068.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 15:02:25 +08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
72ad63479c chore(deps): bump the actions group with 5 updates (#236)
Bumps the actions group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [actions/checkout](https://github.com/actions/checkout) | `6.0.2` | `6.0.3` |
| [actions/upload-artifact](https://github.com/actions/upload-artifact) | `4.6.2` | `7.0.1` |
| [actions/download-artifact](https://github.com/actions/download-artifact) | `4.3.0` | `8.0.1` |
| [actions/cache](https://github.com/actions/cache) | `4.2.3` | `5.0.5` |
| [github/codeql-action](https://github.com/github/codeql-action) | `4.36.0` | `4.36.2` |


Updates `actions/checkout` from 6.0.2 to 6.0.3
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/de0fac2e4500dabe0009e67214ff5f5447ce83dd...df4cb1c069e1874edd31b4311f1884172cec0e10)

Updates `actions/upload-artifact` from 4.6.2 to 7.0.1
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4.6.2...043fb46d1a93c77aae656e7c1c64a875d1fc6a0a)

Updates `actions/download-artifact` from 4.3.0 to 8.0.1
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4.3.0...3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c)

Updates `actions/cache` from 4.2.3 to 5.0.5
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/5a3ec84eff668545956fd18022155c47e93e2684...27d5ce7f107fe9357f9df03efb73ab90386fccae)

Updates `github/codeql-action` from 4.36.0 to 4.36.2
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/7211b7c8077ea37d8641b6271f6a365a22a5fbfa...8aad20d150bbac5944a9f9d289da16a4b0d87c1e)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/download-artifact
  dependency-version: 8.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/cache
  dependency-version: 5.0.5
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: github/codeql-action
  dependency-version: 4.36.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 15:02:17 +08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
771a884ac5 chore(deps): bump debian from ed4fcc4 to 49ba348 in /docker (#235)
Bumps debian from `ed4fcc4` to `49ba348`.

---
updated-dependencies:
- dependency-name: debian
  dependency-version: bookworm
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 15:02:14 +08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
eedecbb67e chore(deps): bump mwader/static-ffmpeg from 8.0 to 8.1.1 in /docker (#234)
Bumps mwader/static-ffmpeg from 8.0 to 8.1.1.

---
updated-dependencies:
- dependency-name: mwader/static-ffmpeg
  dependency-version: 8.1.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 15:02:11 +08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
21ed984c92 chore(deps): bump node from 1031993 to 2d178f2 in /docker (#233)
Bumps node from `1031993` to `2d178f2`.

---
updated-dependencies:
- dependency-name: node
  dependency-version: 22-bookworm
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 15:02:03 +08:00
SnapOtter 507fc1c356 Merge branch 'feat/tool-first-workflow'
Resolved conflict in worker.ts: kept remote refactored worker
(v2 process, scratch paths, extra outputs, metrics) and re-applied
the auto-save removal from the feature branch.
2026-06-15 14:55:58 +08:00
SnapOtter 7858130e7f Merge branch 'feat/tool-first-workflow'
Tool-first workflow redesign: replace sidebar with top nav, rewrite
home page as tool browser with search/modality tabs/collapsible sections,
refactor tool page into 5-state flow (dropzone/settings/processing/review),
modality-based URLs, audio waveform player, server-side preview generation
for non-native formats, explicit Save to Files, file library improvements.
2026-06-15 14:53:34 +08:00
SnapOtter 857aa75562 fix: apply processedFileName fallback everywhere processedFilename is used
Three places were using currentEntry?.processedFilename directly
without the processedFileName fallback (which parses the download URL):
- Video-to-image output detection (gif/webp check)
- Browser previewable check (canBrowserPreview)
- Non-native format detection (already fixed in prior commit)

All now consistently use processedFileName as fallback when
processedFilename is empty on the file entry.
2026-06-15 14:52:02 +08:00
SnapOtter 14ded6fbd0 fix: use processedFileName fallback for non-native format detection after processing 2026-06-15 14:49:30 +08:00
SnapOtterandGitHub 3fb8164fa5 feat: add OpenTelemetry distributed tracing (enterprise) (#232)
* feat(tracing): add OpenTelemetry dependencies and --import preload flag

* feat(enterprise): add distributed_tracing feature gate

* feat(tracing): add SDK bootstrap with enterprise gating

* fix(tracing): correct test coverage for enterprise-unavailable path and prevent double-init

Test 2 now mocks @snapotter/enterprise to throw an import error, exercising
the catch block in the preload. Test 3 imports with no endpoint so the preload
is a no-op, avoiding leaked SDK from double-initialization. Added idempotency
guard to initTracing() as a safety net.

* feat(tracing): add Pino trace mixin and shared logger

When OTel tracing is active, every Pino log line now includes traceId,
spanId, and traceFlags fields for log-to-trace correlation. The mixin
is a no-op when no SDK is registered (community users).

* feat(tracing): add _otel to ToolJobData and inject trace context at enqueue

Add optional _otel carrier field to ToolJobData for W3C trace context
propagation across BullMQ job boundaries. When an active OTel span exists,
propagation.inject() writes traceparent/tracestate into the job data before
queue.add(). When no SDK is registered (community edition), the carrier
stays empty and _otel remains undefined -- zero overhead.

* feat(tracing): extract trace context and create spans in BullMQ worker

* feat(tracing): inject trace context into Python sidecar calls

* feat(tracing): add trace context extraction to Python sidecar

* feat(tracing): add shutdownTracing to graceful shutdown sequence

* feat(tracing): enrich HTTP spans with tool_id and user_id attributes

* docs: add OpenTelemetry env var documentation to .env.example

* test(tracing): add lifecycle integration tests for trace propagation

* fix(tracing): inject trace context into pipeline and batch flow jobs

* fix(tracing): add sidecar.execute Node-side span and remove unnecessary comment

Wraps PythonDispatcher.run() with a sidecar.execute span on the Node
side so traces show the full round-trip (Node span -> Python span).
Also removes an obvious comment from logger.ts.
2026-06-15 12:53:06 +08:00
SnapOtter abaae18669 fix: always show NonNativePreview for non-native formats, even after processing 2026-06-15 12:41:42 +08:00
SnapOtter b93dc95bca fix: preview generation works for processed results too
NonNativePreview now accepts either a File (pre-processing) or a src
URL (post-processing). After processing a non-native video (e.g.,
muting an AVI), the preview button fetches the processed result from
its URL and sends it for transcoding, instead of showing the broken
MediaPlayerView.
2026-06-15 12:21:12 +08:00
SnapOtter 6090688708 fix: send auth token with preview generation request 2026-06-15 12:17:26 +08:00
SnapOtter d3cd4e41b5 feat: on-demand preview generation with progress messages for non-native formats 2026-06-15 12:04:30 +08:00
SnapOtter 511e941da0 fix: clean file info cards for non-native video/media formats on tool page
Instead of showing a broken video player error for formats like AVI,
MKV, WMV that browsers can't play, show a clean file info card with
icon, filename, format, and size. Message says "Preview will be
available after processing" which is accurate -- the processed output
is typically in a browser-friendly format.

Also cleaned up the post-processing non-previewable card to match
the same visual style.
2026-06-15 11:57:27 +08:00
SnapOtter ed458d4bbd feat: redesign review panel action hierarchy
- Download stays primary (full-width button)
- Save to Files demoted to subtle text link below download
- "Adjust settings" and "New file" are side-by-side compact buttons
- "Back to Tools" faded to very subtle (breadcrumb handles this)
- Clearer visual hierarchy: primary -> secondary -> tertiary actions
2026-06-15 11:52:22 +08:00
SnapOtter e7126f9293 feat: All tab groups tools by modality with collapsible sections
The All tab now shows tools organized by modality (Image, Video,
Audio, Documents, Data) with each section showing a colored icon,
tool count, and collapse toggle. Categories are nested within each
modality. Collapse state persists in localStorage so enterprise
users can hide modalities they don't use.

Modality tabs still show the flat category view for focused browsing.
2026-06-15 11:47:58 +08:00
SnapOtter 52693fc843 feat: arrow key navigation in Files list
ArrowUp/ArrowDown to move between files, Home/End to jump to
first/last. Selected file scrolls into view automatically.
List container is focusable and keyboard-navigable.
2026-06-15 11:42:59 +08:00
SnapOtter 3decfaae3e feat: clean preview button with progress bar, add document preview support 2026-06-15 11:41:11 +08:00
SnapOtter 412a21ee4d feat: server-side preview generation for non-native video/audio formats 2026-06-15 11:33:45 +08:00
SnapOtter a70b13ad32 feat: modality-aware file preview in Files library
- Video files: inline video player with controls
- Audio files: waveform visualization with playback (wavesurfer.js)
- Image files: thumbnail preview (existing behavior)
- Documents/data: file type icon with format label
2026-06-15 11:14:51 +08:00
SnapOtter 3a5d55cf26 fix: grey out incompatible files in library instead of hiding them
When importing from Files for a specific tool, all files are shown
but incompatible ones (wrong modality) are greyed out and unclickable.
This gives users full visibility of their library while making it
clear which files can be used.
2026-06-15 11:10:35 +08:00
SnapOtter adfa53246a fix: enforce file type filtering using tool's acceptedInputs
When registryEntry.accept is undefined, fall back to tool.acceptedInputs
to derive the file filter. This ensures image tools reject audio/video
files in the file picker, and vice versa.
2026-06-14 23:42:20 +08:00
SnapOtter 826f1efc08 feat: filter files by modality when importing from tool page
When clicking "Import from Files" on an image tool, only image files
are shown in the library. Video tools see only videos, audio tools
only audio. Uses MIME type prefix filtering (image/, video/, audio/).
Document and data tools see all files since their MIME types vary.
2026-06-14 23:26:38 +08:00
SnapOtter aff9159ae0 feat: move theme toggle and language selector to top nav bar
Theme toggle (sun/moon) and language selector (globe + dropdown) now
live in the top nav's right section alongside help and avatar.
Removed the floating Footer component from the home page since its
contents are now in the nav bar.
2026-06-14 23:13:38 +08:00
SnapOtter 6bcee1e073 fix: file library upload accepts all file types, not just images
The upload endpoint was rejecting non-image files via validateImageBuffer.
Now non-image files skip validation and use the MIME type from the
multipart upload. Width/height are null for non-image files.
2026-06-14 23:11:38 +08:00
SnapOtter f4061fe4b0 fix: Save to Files shows green success state with checkmark after saving 2026-06-14 23:10:20 +08:00
SnapOtter d0795b69ea feat: disable auto-save, add Save to Files button, accept all file types in library
- Disabled auto-save of processed files to library (worker no longer
  calls autoSaveToLibrary)
- Added "Save to Files" button in the review panel for explicit saving
- Files library upload area now accepts all file types, not just images
- Removed "Drop images here" hardcoded text, replaced with i18n
- Removed image-only file filter from library upload
2026-06-14 21:53:42 +08:00
SnapOtter b8a6b2018e feat: Import from Files works, remove pipeline button from files page
- Import from Files now navigates back to the tool page with the
  selected file loaded (uses selectForTool router state)
- Tool page skips file store reset when arriving from the library
- Removed "Open in Pipeline" button from file details and file list
- Button text changes to "Select File" when in file-selection mode
2026-06-14 21:43:58 +08:00
SnapOtter b5eef644e3 feat: clean tool page nav, add Import from Files on dropzone
- Removed Automate/Editor/Files links from top nav on tool pages,
  keeping only the breadcrumb + help + avatar for a cleaner look
- Added "Import from Files" link on the tool dropzone that navigates
  to the file library, letting users import previously processed files
2026-06-14 21:37:15 +08:00
SnapOtter f32b3bd51f fix: clear file store when navigating between tools
Changed undoProcessing() to reset() on toolId change so files don't
persist when navigating from one tool to another. Previously a video
file from a video tool would carry over to an image tool.
2026-06-14 21:32:27 +08:00
SnapOtter 90acd5bcd9 fix: show image viewer for video-to-gif/webp results instead of broken video player
When a video tool produces a non-video output (gif, webp, png), the
media player can't render it. Now detects the processed file extension
and switches to ImageViewer for image outputs while keeping the video
player for the input preview.
2026-06-14 21:30:54 +08:00
SnapOtter 69300b5c83 fix: use media-player for all video/audio tools that had no-comparison or side-by-side
video-to-webp, video-to-frames, auto-subtitles, extract-subtitles,
video-metadata, and audio-metadata were using display modes that
render via ImageViewer, which can't display video/audio files
(shows "Preview not available"). Changed to media-player so they
get proper video player or waveform visualization.
2026-06-14 21:27:26 +08:00
SnapOtter d8a8e05590 feat: add audio waveform visualization with wavesurfer.js
Replace the basic HTML5 audio element with an interactive waveform
player for audio tool pages. Video tools continue using the existing
MediaPlayerView component.
2026-06-14 21:25:34 +08:00
SnapOtter 5241ccd124 fix: show tool-specific accepted formats in dropzone, remove privacy note
The inner Dropzone was showing generic "Images, Videos, Audio, PDFs..."
text. Now it shows the tool's actual accepted formats (e.g., "MP4, MOV,
WEBM, MKV..." for video tools) via the acceptDescription prop.
Removed the privacy note text per user feedback.
2026-06-14 21:18:45 +08:00
SnapOtter 207c1c348b fix: make search bar full-width to align with tabs and grid 2026-06-14 21:09:42 +08:00