22 Commits
Author SHA1 Message Date
SnapOtterandGitHub 04ef1141fb feat(telemetry): readable Sentry errors, Python tracebacks, and diagnostic mode
Keeps a real, redacted error message instead of "Error: Error", surfaces Python tracebacks in Sentry as a vetted context, and adds an opt-in SNAPOTTER_SENTRY_DIAGNOSTIC verbose mode plus SNAPOTTER_SENTRY_DSN_OVERRIDE. The default fleet path ships nothing on the never-collect list; raw detail is reachable only via the opt-in flag. Also classifies Redis OOM/READONLY replies as operational and removes a ReDoS in stack-frame extraction.
2026-08-03 13:13:38 +08:00
SnapOtterandGitHub a75a22dd3f fix(landing): derive conversion-preset copy from real format differences (#663)
The 83 X-to-Y converter pages were generated from one template with the
format names substituted in. Three of the four features and two of the
three FAQs were byte-identical across all 82 non-override presets, every
page came out at exactly 144 words, and pages within a family sat at
roughly 61% similarity with identical shingle counts. That is the
mass-produced fingerprint, and the same shape that got the docs tool
pages deduped in #662. All 83 are indexed today, so this is prevention
rather than repair.

Copy is now derived from what actually differs between the two formats:
alpha, animation, colour depth, lossy re-encode behaviour, container
reach, and where each format is genuinely accepted. png-to-jpg explains
that transparency gets flattened; gif-to-jpg warns you keep frame one
only; psd-to-svg says to export Photoshop's real vector layers instead of
tracing them.

Mean similarity across all 3,403 pairs drops from 34.8% to 6.7%, and the
uniform 144-word length is gone (206-256 words, varying). Within-family
max is roughly flat: on the built pages, image goes 62.8% to 66.1%, video
61.3% to 66.4%, audio 60.7% to 60.6%. Generated copy has a similarity
floor, and beating it inside a family would take hand-written pages. What
has gone is the identical shingle count per family, which was the
strongest tell.

Also drops the one-off eps-to-svg override: all five vectorize presets
now get honest tracing copy, not just that one.

Verified: landing builds 798 pages, zero verbatim repeats between a
page's blurb and its own features or FAQs, biome clean.
2026-07-28 17:08:53 +08:00
SnapOtterandGitHub 42e1dc9799 fix(docs): keep the translated locale trees out of the search index (#662)
Search Console flagged four reasons on 2026-07-28: soft 404, both
duplicate-canonical variants, and noindex. All four sat on
docs.snapotter.com; the landing site was clean.

The cause was boilerplate dominance rather than a broken tag.
/tools/video/crop-video carries 1.3 KB of unique body against 3.5 KB of
identical chrome (nav, sidebar, 21-language switcher), so unrelated tool
docs measured 52-60% full-page similarity. Across 20 locales that was
3,640 of 3,822 submitted URLs. Google read the lot as one duplicate
cluster and began electing arbitrary representatives: /changelog became
the canonical for /tools/image/favicon, and /uk/guide/getting-started for
/nl/tools/image/resize. English tool docs indexed 2 of 10 and localized 4
of 10, while the landing page for those same five tools indexed 5 of 5.

Translated pages now emit `noindex, follow` with a self-canonical, and
sitemap.transformItems drops them from the sitemap. Self-canonical rather
than pointing at English, since noindex paired with a cross-canonical
sends two conflicting instructions. hreflang is removed outright: the
annotation only means something between pages that can all be indexed.

Readers see no change. The language switcher and every in-page link
behave exactly as before.

Verified against a real build: sitemap 3,822 to 182 URLs with zero
translated entries, all 3,640 translated files carrying the noindex and
no English file doing so, docs e2e 100 passed.
2026-07-28 17:08:32 +08:00
SnapOtterandGitHub 2848dd0e53 fix(help): render the help dialog from i18n instead of hardcoded English (#647)
The help dialog carried its 13 shortcut labels, its getting-started
paragraph and its version line as hardcoded English, while fully
translated strings for exactly those labels sat unused in all 21 locale
files. Every non-English user read English there. The translations did
not need writing, only reading: t.help.keyboardShortcuts already had
focusSearchBar, goToTools, processFile and the rest, in every locale.

Labels now index into t.help.keyboardShortcuts by key rather than
carrying text. Getting-started reads t.help.gettingStarted.description,
which drops the inline Kbd chip the hardcoded copy had, matching what all
21 locales already say. The version line goes through
t.help.versionLabel.

Also adds the type-to-search row that #644 left out, keyed
help.keyboardShortcuts.typeToSearch, translated into all 21 locales, and
regenerates the two darwin help-dialog baselines for the extra row.

Nothing caught the original bug because the i18n context defaults to en,
so asserting on English text passes whether or not the component reads
i18n at all. The new test mocks the context with sentinel values instead:
putting the hardcoded labels back fails 15 of its 19 cases.

Verified: 19 new unit tests, full unit suite 7576 passed, help-dialog
visual 3/3 against regenerated baselines, help accessibility e2e 7/7,
typecheck and lint clean, all 18 CI checks green.
2026-07-26 10:28:02 +08:00
SnapOtterandGitHub 025851beef fix: honor unlimited processing timeout (#638)
* fix(web): recover stalled job progress streams

* fix(ai): honor unlimited processing timeout

* fix(web): keep retrying stalled progress streams
2026-07-25 11:36:02 +08:00
SnapOtterandGitHub 841f47f6ca fix(files): decode CLI-decoded formats before URL-import preview generation (#637)
fetch-urls.ts called sharp(buffer).webp() directly on the raw, undecoded buffer for CLI-decoded formats (HEIC, RAW, PSD, ...), which throws and gets silently swallowed, so URL-imported files in those formats came back with no preview and 0x0 dimensions. Decodes once before both preview generation and dimension lookup, mirroring the pattern already used elsewhere in the codebase.

Fixes #634
2026-07-25 10:46:50 +08:00
SnapOtterandGitHub 511633fa1c fix(files): store null instead of 0x0 for undecoded upload dimensions (#636)
validateImageBuffer() intentionally reports {width: 0, height: 0} for every CLI_DECODED_FORMATS member. The file library's upload and save-result endpoints treated that 0 as a real measurement and wrote it into the DB. Adds a measuredDimensions() helper that treats non-positive width/height as unmeasured and stores null instead.

Fixes #635
2026-07-25 10:46:11 +08:00
SnapOtterandGitHub 098ed50d06 fix(image): decode real iPhone HEIC files instead of rejecting them at validation (#631)
validateImageBuffer() never listed heif in CLI_DECODED_FORMATS, so real iPhone HEIC uploads hit Sharp's own metadata probe (its bundled libheif only supports AV1/AVIF) and got rejected before reaching the working heif-convert/heif-dec decode path already wired up downstream. Adds heif to that set, same as raw/psd/tga/bmp/etc.

Also fixes the same gap on erase-object's mask input, which validates through the same function but had no matching decode step, so a HEIC mask reached an unguarded sharp() call and came back as a misclassified server error instead of a clean 422.

Fixes #622
2026-07-25 10:45:12 +08:00
SnapOtterandGitHub 330cf559e0 fix(image): image-to-pdf presets no longer 404 on 2+ files (#633)
jpg-to-pdf and its six image-to-pdf-group siblings share the base tool's
registerImageToPdfRoute, which never registers into the toolRegistry the
generic /batch endpoint reads from. The shared conversion-preset settings
component routed any 2+-file submission to /batch regardless of tool, so
these presets 404'd with `Tool "<id>" not found` past the first file, while
the base image-to-pdf tool stayed unaffected because it bypasses that
dispatch entirely with its own settings component.

MULTI_FILE_TOOLS now includes every image-to-pdf-group preset, derived from
BASE_CONFIG instead of hardcoded, and the preset settings component checks
that set before choosing batch vs. a single combined request.

Fixes #627
2026-07-25 09:18:18 +08:00
SnapOtterandGitHub 44f5aea326 fix(ci): repair the chronically-failing nightly workflow (#624)
The scheduled Nightly had been red for over a week across nearly every job. This
root-causes and fixes each one. All were pre-existing: missing CI provisioning,
specs that drifted as the app grew, a job too heavy for its timeout, and a fuzz
that was never configured for file-upload endpoints. None came from the recent
security merge.

- Coverage + Docker Container E2E: install tesseract and its language packs so
  the built-in Fast OCR tests stop throwing spawn ENOENT; gate two repo-file and
  release-workflow tests that cannot run inside the slimmed container image.
- E2E (Full, Serial, Cross-Browser, Device Matrix): refresh specs that drifted
  behind the app (tool renames, the now admin-only Tools tab, dropped About copy,
  locator collisions scoped to the right region). One real product fix rode
  along: /config/auth was refetched six times per tool-page load, so cache it
  behind a single shared fetch, dropping the tool page from 13 to 8 API calls.
- Extended Matrix + Fuzz: shard the integration suite four ways so the full
  format x tool matrix plus property fuzz fits its budget instead of overrunning
  the 90-minute ceiling every night.
- Schemathesis: exclude the tools with bespoke handlers that process
  synchronously in-request (they hang the fuzz on adversarial input) and suppress
  Hypothesis's data-generation health checks, which fire because file-upload
  endpoints reject the fuzzer's random bytes. not_a_server_error still runs on
  every generated case (5000+ per run).
- Stabilize two long-tail flakes: raise the avif matrix per-test cap from 240s to
  600s, and assert toHaveCount(0) on the deleted user row so a transient success
  toast no longer trips a strict-mode violation.

Verified end to end: the full Nightly workflow is green on this branch (all 14
jobs), and PR CI is green.
2026-07-24 03:54:50 +08:00
SnapOtterandGitHub 44d8109486 fix: enforce settings authority boundaries (#618)
Close generic settings authorization bypasses and enforce per-setting authority, validation, redaction, transactional config import, and route-local write rate limiting.
2026-07-22 20:15:38 +08:00
0467e87bfe fix(download): reset the socket when a stream is shorter than Content-Length (#617)
The download route sets Content-Length from a stat and then streams the
object; when the stat size exceeds the bytes the stream yields (#590
"cause 2"), the client hangs on keep-alive framing waiting for a tail
that never arrives. Both send paths now run through a backpressure-safe
byte-counting Transform that resets the socket on a shortfall, so the
download fails at once instead of hanging. Adds a real-socket regression
test at the generic download route, the coverage gap #590 named.

Refs #590

Co-authored-by: harshjainnn <170849281+harshjainnn@users.noreply.github.com>
2026-07-22 02:03:54 +00:00
SnapOtterandGitHub 1f8a42e548 fix: enforce role authority for user management (#616)
Centralize role-authority enforcement across user management, role management, configuration import, SCIM, GDPR, and MFA mutations. Add regression coverage for delegated custom roles and protect higher-privilege accounts from reset, deletion, or takeover.
2026-07-22 01:23:15 +08:00
SnapOtterandGitHub 991c981529 fix: make OCR portable and reliable across AMD64 and ARM64 (#519)
* fix: make OCR portable and reliable

* fix: harden OCR installation portability

* fix: pin OCR partials across downloads

* fix: make OCR execution reliably asynchronous

* fix: harden OCR portability and docs routes

* fix: preserve decoder and docs safeguards
2026-07-15 03:34:24 +08:00
SnapOtterandGitHub e7cfc00fe1 fix: preserve colored blocks in PDF-to-Word (#500) 2026-07-11 19:20:32 +08:00
SnapOtterandGitHub 3d1744aec8 feat(landing): add Product dropdown to nav (#475)
Adds a CSS-only Product dropdown (Developers, Self-hosted tools, Alternatives) to the top nav and restores the lg breakpoint. Mobile menu lists items flat. Updated homepage e2e (navbar + dropdown-hover tests, footer scope). Verified at 1024/1280px, 63/63 landing e2e.
2026-07-10 20:09:57 +08:00
SnapOtterandGitHub a08afbc80a feat(landing): restore Developers nav link, swap out Features anchor (#474)
Restores Developers to the top nav by swapping out the Features homepage anchor. Nav is now Enterprise, Pricing, Alternatives, Developers, Docs, Talk to a human. Updated two e2e assertions that checked for a Features nav link. Verified visually at 1120/1280px and 62/62 landing e2e.
2026-07-10 16:00:56 +08:00
SnapOtterandGitHub a731c3d1fe fix: reliable, self-healing AI feature-bundle installs (#472)
Make on-demand AI feature-bundle installs reliable and self-healing, closing
the failure modes behind most "some tool doesn't work" reports.

Multi-bundle installs: tools needing more than one bundle (Passport Photo,
Enhance Faces) install every required bundle from one action and stay
not-installed until all are present. Verified across all 19 AI tools.

Downloads: self-heal the accelerated Hugging Face (Xet) client so an upgraded
venv no longer silently falls back to slow urllib; restart instead of
corrupting a resumed partial when a proxy ignores Range and returns 200;
verify the completed size; fail fast on disk-full and HTTP 4xx; retry
transient errors five times; add hf_transfer fallback and document Xet egress.

Install integrity: crash-atomic venv writes so a killed or out-of-space
install can no longer tear the shared venv and break other tools; a boot
breadcrumb reseeds a torn venv to a clean state automatically; a post-install
smoke import test refuses to record a bundle whose libraries cannot load; an
install watchdog stops a wedged installer that would otherwise hold the venv
writer lock forever.

Adds unit and end-to-end tests for every failure mode above.
2026-07-10 07:32:48 +00:00
SnapOtterandGitHub ffeacd4b3c fix(landing): fix top nav overflow and text overlap (#473)
Fixes the broken top navbar (logo/Features collision, wrapping CTAs). Removes the Developers link (kept in footer), adds shrink-0/whitespace-nowrap, and shows the horizontal nav at min-[1120px] with a hamburger below. Verified visually at 1024/1120/1152/1280px; 62/62 e2e, build green. Admin merge: landing-src-only, required matrix contexts path-skip and never report.
2026-07-10 15:18:33 +08:00
SnapOtterandGitHub 958c3e8513 fix(landing): reposition residual meta framing and demote homepage tool count (#471)
Follow-up to #469. Homepage/enterprise/FAQ JSON-LD and meta now say 'file-processing infrastructure' (not 'suite'), and the visible homepage counts (ToolGrid subtitle, Pricing bullet) plus SEO meta say '200+'. Removed the orphaned toolCount/TOOLS references. Leaves the /tools catalog and ToolGrid filter-tab functional counts exact. Admin merge: landing-src-only PR, required matrix contexts path-skip and never report.
2026-07-10 15:00:55 +08:00
SnapOtterandGitHub 10ae6d1d3d fix(landing): green the landing e2e suite (#470)
Repairs 6 pre-existing landing Playwright failures: launch-banner external-link noopener, one-h1 assertion auto-wait, nav label drift to 'Talk to a human', privacy 'Analytics' heading, and pricing 'Let's talk' CTA.
2026-07-10 14:03:29 +08:00
SnapOtterandGitHub af011d5538 feat: reposition to self-hosted file-processing infrastructure (#469)
Leads public copy with self-hosted file-processing infrastructure and demotes tool count to a proof point across README, docs, llms.txt, DockerHub, and the landing site. Adds a /self-hosted hub plus 7 job-intent SEO pages with a build-time validator, deepens the flagship /alternatives pages, adds a remove.bg page, three shared components, and landing e2e coverage.
2026-07-10 13:26:41 +08:00