Commit Graph
1972 Commits
Author SHA1 Message Date
SnapOtterandGitHub e468cef6c0 fix(landing): highlight Open Source as Most Popular tier (#462)
Move the Most Popular badge and featured card treatment to the Open Source tier; Enterprise becomes the plain card.
2026-07-07 19:11:49 +08:00
SnapOtterandGitHub c2c9a9ed9b fix(landing): clarify OIDC is free, SAML is enterprise on pricing (#461)
Add an OIDC / SSO login line to the Open Source tier and narrow the Enterprise bullet to SAML SSO + SSO enforcement. OIDC login ships in the free open-source build (env-gated, no license); only SAML and SSO enforcement are licensed.
2026-07-07 19:03:08 +08:00
SnapOtter ea7a671ccf feat(landing): add dismissible 2.0 launch banner
Deep otter-brown Web 1.0 style bar pinned above the fixed navbar (offset via --lb-h), with a localStorage-remembered dismissal and a render-blocking check so returning visitors see no flash. Temporary; remove once 2.0 is old news.
2026-07-07 17:19:11 +08:00
SnapOtter 008c4e3654 ci: add SLSA provenance attestation workflow for published releases [skip ci] 2026-07-07 16:25:02 +08:00
SnapOtter 33f1f974f0 docs: add v2.0.0 changelog entry and refresh Docker Hub overview banner 2026-07-07 16:21:59 +08:00
SnapOtter 531ecca9c2 ci(trivy): ignore npm-bundled sigstore CVE-2026-48815 (build-tool internal, not runtime-reachable) [skip ci] 2026-07-07 16:08:13 +08:00
SnapOtter 88a611d28b docs(readme): merge 2.0 launch + migration callouts, add Discord note, simplify quick start [skip ci] 2026-07-07 16:00:27 +08:00
semantic-release-bot 1d21184cfe chore(release): 2.0.0 [skip ci]
# [2.0.0](https://github.com/snapotter-hq/snapotter/compare/v1.17.2...v2.0.0) (2026-07-07)

* feat!: SnapOtter 2.0.0 ([6205525](https://github.com/snapotter-hq/snapotter/commit/620552569e1b6b163df66ee824a480e6d5750abb)), closes [#254](https://github.com/snapotter-hq/snapotter/issues/254) [#261](https://github.com/snapotter-hq/snapotter/issues/261)
* feat(db)!: SnapOtter 2.0 phase 1 foundation: postgres, migrator, compose stack ([#216](https://github.com/snapotter-hq/snapotter/issues/216)) ([1c724d5](https://github.com/snapotter-hq/snapotter/commit/1c724d5d21659be24423a22c7d6737d59b3d6310))

### Bug Fixes

* adopt sharp 0.35.2+ by centralizing the FormatEnum key type ([#362](https://github.com/snapotter-hq/snapotter/issues/362)) ([9052da2](https://github.com/snapotter-hq/snapotter/commit/9052da27f3a298f950a9949a0e0929e72b569b04))
* **ai-bundles:** lock the numpy-1.x ABI closure so the OCR bundle can't strand scipy ([67c5566](https://github.com/snapotter-hq/snapotter/commit/67c55669d610801e8cd07e4d65c5642a08fc0b07))
* **ai-bundles:** lock the numpy-1.x ABI closure so the OCR bundle can't strand scipy ([#437](https://github.com/snapotter-hq/snapotter/issues/437)) ([fd39f66](https://github.com/snapotter-hq/snapotter/commit/fd39f66f464e21836e8cfd72ab38f3d5c6d3528f))
* **ai-bundles:** repair bundle build + publish pipeline (deepsafe repo, CPU provider, manifest) ([3b50bcd](https://github.com/snapotter-hq/snapotter/commit/3b50bcdc5ccb630998c60003d5eddd9803780013))
* **ai:** broaden SSRF pre-scan regex to cover srcset, poster, formaction, [@import](https://github.com/import) ([5397f9b](https://github.com/snapotter-hq/snapotter/commit/5397f9b21c5b8566fed355f13d62378035456981))
* **ai:** detect a paddle-only GPU so OCR uses PaddleOCR-GPU not Tesseract ([#439](https://github.com/snapotter-hq/snapotter/issues/439)) ([7cd514d](https://github.com/snapotter-hq/snapotter/commit/7cd514dd8cdee28b9f600722f43ba63bcdbe2928))
* **ai:** enforce the feature gate on the per-request fallback path ([#331](https://github.com/snapotter-hq/snapotter/issues/331)) ([7a70aff](https://github.com/snapotter-hq/snapotter/commit/7a70affac51153cf0bd6432a7d9bdc4e6c76bc4b)), closes [#327](https://github.com/snapotter-hq/snapotter/issues/327)
* **ai:** gate AI tools on per-framework GPU detection, not a shared boolean ([#445](https://github.com/snapotter-hq/snapotter/issues/445)) ([36dde9a](https://github.com/snapotter-hq/snapotter/commit/36dde9ad8784867270bbfe766650b4c4515dbcb9))
* **ai:** pin protobuf<5 on arm64 so mediapipe face landmarks work ([#417](https://github.com/snapotter-hq/snapotter/issues/417)) ([2c2fb65](https://github.com/snapotter-hq/snapotter/commit/2c2fb65fcaaa835ca589e59255366819cece5fdf))
* **ai:** pin rembg to 2.0.69 to keep numpy<2 compatibility ([3726335](https://github.com/snapotter-hq/snapotter/commit/3726335063984bd572a14eea36c532bda74bc1d6))
* **ai:** surface actionable fix for libGL.so.1 missing on headless installs ([7c70c60](https://github.com/snapotter-hq/snapotter/commit/7c70c60b9e3e0912b1f8be6acf87f378f8b9c080))
* **ai:** update arm64 bundle sha256/size after protobuf<5 rebuild ([#421](https://github.com/snapotter-hq/snapotter/issues/421)) ([2a36b3d](https://github.com/snapotter-hq/snapotter/commit/2a36b3dfe0e4388f7bd1c2a8183f7d87e57dc144)), closes [#417](https://github.com/snapotter-hq/snapotter/issues/417)
* allow reinstall of AI bundles with broken model files ([#214](https://github.com/snapotter-hq/snapotter/issues/214)) ([27a56c7](https://github.com/snapotter-hq/snapotter/commit/27a56c774bfd8f920355a1f5b70553639ad165df))
* always show NonNativePreview for non-native formats, even after processing ([abaae18](https://github.com/snapotter-hq/snapotter/commit/abaae18669a281a03aeafebec764f9992659c862))
* **analytics:** harden analytics opt-out and feedback surfaces ([#423](https://github.com/snapotter-hq/snapotter/issues/423)) ([23efce9](https://github.com/snapotter-hq/snapotter/commit/23efce9df0f8cedabd9aab033b896f7f3dffc768))
* **api:** batch user-files delete to eliminate N+1 queries ([e2c5714](https://github.com/snapotter-hq/snapotter/commit/e2c57144dd1f8f9a7de629228967725861cb50c7))
* **api:** decode RAW via LibRaw first so DNG processes at full resolution ([#289](https://github.com/snapotter-hq/snapotter/issues/289)) ([#290](https://github.com/snapotter-hq/snapotter/issues/290)) ([3d9ff1e](https://github.com/snapotter-hq/snapotter/commit/3d9ff1e0d2c60a88fdba03c11f45e1422eacf84d))
* **api:** drop app-logger import from media-input; update stale errors mock ([c483897](https://github.com/snapotter-hq/snapotter/commit/c483897452d0769a3a1fbca8758fdc7eaf1b3cc0))
* **api:** fix batch user-files delete recursive CTE query ([82991b6](https://github.com/snapotter-hq/snapotter/commit/82991b6a412f84dd2d1d7295379da3cb189d1440))
* **api:** isolate the factory validation scratch dir from the worker's ([7a205ee](https://github.com/snapotter-hq/snapotter/commit/7a205eeb1239f6fa0d1e3c84c744325b9e953237))
* **api:** log only genuine processing faults at error level ([965501a](https://github.com/snapotter-hq/snapotter/commit/965501aef95fff1f5924e030434ea2688c4f0e8c))
* **api:** make OpenAPI spec ASCII-only so Schemathesis can load it ([#344](https://github.com/snapotter-hq/snapotter/issues/344)) ([c2ae334](https://github.com/snapotter-hq/snapotter/commit/c2ae334c81e18561931247db8f8e68ab08c1ef74))
* **api:** narrow friendlyError matching to avoid collapsing valid messages ([6220885](https://github.com/snapotter-hq/snapotter/commit/6220885e0cfd61b57186136aba174f0042dc6457))
* **api:** propagate lenient structural-validation flag to batch + pipeline ([e52e0bb](https://github.com/snapotter-hq/snapotter/commit/e52e0bb3d53bed90a58d0daca15c9aef633d7d02)), closes [#244](https://github.com/snapotter-hq/snapotter/issues/244)
* **api:** reject non-PDF inputs in pdf-to-image ([37b9c6d](https://github.com/snapotter-hq/snapotter/commit/37b9c6d7bf0d38ffc48febc324e96288b5f59fd4))
* **api:** respect RATE_LIMIT_PER_MIN for tool routes ([#272](https://github.com/snapotter-hq/snapotter/issues/272)) ([ce02ce1](https://github.com/snapotter-hq/snapotter/commit/ce02ce13486989d51314d2afabbdcaf8e0d2a9f9)), closes [#280](https://github.com/snapotter-hq/snapotter/issues/280)
* **api:** return user-safe processing errors, keep raw stderr in logs ([4af4bfa](https://github.com/snapotter-hq/snapotter/commit/4af4bfa8eba5ba059e804af33581ada7250a7d1c))
* **api:** section override for backwards-compat alias routes (adjust-colors) ([fc205c7](https://github.com/snapotter-hq/snapotter/commit/fc205c7428f3d27d542065a70c4a948e4e9742fd))
* **api:** stop leaking raw ffprobe stderr in media validation errors ([3fc4149](https://github.com/snapotter-hq/snapotter/commit/3fc41491437a2968b2ebfd79447d59e68da7ec1d))
* **api:** support sharp 0.35 types ([6f85b3d](https://github.com/snapotter-hq/snapotter/commit/6f85b3d12acac73caf747313511c49c7bdc6c257))
* **api:** wrap role rename and delete in transactions ([b564932](https://github.com/snapotter-hq/snapotter/commit/b5649320833ad0c5d6c57c887ef8587d74564dcb))
* apply processedFileName fallback everywhere processedFilename is used ([857aa75](https://github.com/snapotter-hq/snapotter/commit/857aa75562816dbcc0827eda93125605c495af7c))
* **audio:** low-samplerate ogg encode + post-2.0 QA hygiene ([19d9ed1](https://github.com/snapotter-hq/snapotter/commit/19d9ed181ae0051e0df6a01585036628dd31700a))
* **benchmark:** use section-prefixed tool URLs for 2.0 route scheme ([76fc695](https://github.com/snapotter-hq/snapotter/commit/76fc695233eae54c803ae1730cefb9acd161db45))
* build script venv handling and lint fixes ([b1e94bd](https://github.com/snapotter-hq/snapotter/commit/b1e94bd98851d3cc6da57b343ac8d2528dfda890))
* cache useSyncExternalStore snapshot to prevent infinite re-render ([f68fcb8](https://github.com/snapotter-hq/snapotter/commit/f68fcb8ca6724be0ca83a42593192608cde1a784))
* **ci:** address spec review findings in verify-bundle.sh ([4b5b351](https://github.com/snapotter-hq/snapotter/commit/4b5b35186d925f95566b7d36c9d3994255fc95e2))
* **ci:** deploy landing from dist, not the stale Next.js out path ([#240](https://github.com/snapotter-hq/snapotter/issues/240)) ([44bfca1](https://github.com/snapotter-hq/snapotter/commit/44bfca105fc4bfa1d5d2bfc0da861ed4e0a1e496))
* **ci:** grant ai-bundles reusable call its required token scopes ([03c1a5e](https://github.com/snapotter-hq/snapotter/commit/03c1a5e36f9dd7f904882b7cb19fc27006a61f5d))
* **ci:** harden smoke tests against set -e and fix parameter issues ([efac88c](https://github.com/snapotter-hq/snapotter/commit/efac88c20517c22b3a18a9cb227350cd10bdd3c1))
* **ci:** resolve pre-existing failures making main red ([#250](https://github.com/snapotter-hq/snapotter/issues/250)) ([622c9f9](https://github.com/snapotter-hq/snapotter/commit/622c9f98a5772eae05343407b8408cd7d7539ef9)), closes [#249](https://github.com/snapotter-hq/snapotter/issues/249) [246/#248](https://github.com/snapotter-hq/snapotter/issues/248)
* **ci:** revert rembg to 2.0.62 (2.0.75 requires numpy>=2.3) ([3b8d529](https://github.com/snapotter-hq/snapotter/commit/3b8d529b4422deb4d0782a5e4d536328563afd1e))
* clean file info cards for non-native video/media formats on tool page ([511e941](https://github.com/snapotter-hq/snapotter/commit/511e941da0516e27237621be1eeaf6beb64b2d89))
* clear file store when navigating between tools ([f32b3bd](https://github.com/snapotter-hq/snapotter/commit/f32b3bd51f2107040c081d0d9bee5ac5b0450157))
* correct SAML idpCert property name for @node-saml/node-saml v5 ([b9dac59](https://github.com/snapotter-hq/snapotter/commit/b9dac597362e2fbdd90447bfd7889f85dddfff90))
* critical first-login soft-lock in usage survey overlay ([#392](https://github.com/snapotter-hq/snapotter/issues/392)) ([ca076f9](https://github.com/snapotter-hq/snapotter/commit/ca076f91fde252fd98d3014a260d8ae7534481f0))
* **deps:** close js-yaml DoS alert + document rembg non-reachability ([#286](https://github.com/snapotter-hq/snapotter/issues/286)) ([5d5117a](https://github.com/snapotter-hq/snapotter/commit/5d5117acf729d3b2c00119eede9ba20f0aa17a28))
* **deps:** drop undici override (broke jsdom@29) ([5662532](https://github.com/snapotter-hq/snapotter/commit/56625325013b6c8ac47e159a4d517f7eef29a056))
* **deps:** patch Dependabot security alerts ([8792080](https://github.com/snapotter-hq/snapotter/commit/87920809820cb0179c029ccb7234370431abff67))
* **doc-engine:** treat qpdf exit code 3 (warnings) as success, not failure ([2a2151d](https://github.com/snapotter-hq/snapotter/commit/2a2151d7dd19faa1e4d43d49540729dab33a8eed))
* **docker:** copy patches/ before pnpm install so cold builds succeed ([#300](https://github.com/snapotter-hq/snapotter/issues/300)) ([f74f648](https://github.com/snapotter-hq/snapotter/commit/f74f648e47fb76a30b9fa34cd6dc84f4c0295972))
* **docker:** make storage writable under non-root/foreign UIDs (TrueNAS, OpenShift) ([#299](https://github.com/snapotter-hq/snapotter/issues/299)) ([1fec971](https://github.com/snapotter-hq/snapotter/commit/1fec97111bf4cf4620131e38651b8e114c7b0566)), closes [#230](https://github.com/snapotter-hq/snapotter/issues/230)
* **docker:** make venv bootstrap upgrade-aware with pip-freeze stamp ([39543d6](https://github.com/snapotter-hq/snapotter/commit/39543d651219d4741868ec7ebe34186aa038a914)), closes [#85](https://github.com/snapotter-hq/snapotter/issues/85)
* **docker:** patch OS + pip image CVEs, document accepted Trivy residuals ([#288](https://github.com/snapotter-hq/snapotter/issues/288)) ([c203267](https://github.com/snapotter-hq/snapotter/commit/c203267866aa87994de80d2214f70deff72b7b5f))
* **docker:** repair copied AI venv paths ([#390](https://github.com/snapotter-hq/snapotter/issues/390)) ([7e01d36](https://github.com/snapotter-hq/snapotter/commit/7e01d3637edec10c4b7c73cd6c9d54c6414972f6)), closes [#127](https://github.com/snapotter-hq/snapotter/issues/127) [#127](https://github.com/snapotter-hq/snapotter/issues/127)
* **docker:** restore Postgres/Redis in GPU compose stack + pause image publishing ([9b64a96](https://github.com/snapotter-hq/snapotter/commit/9b64a96bcd2e60ce8e3e051a9d2b57811e98a223))
* **e2e,landing:** robust path escaping (CodeQL) + unique file-tools card ([a6837b6](https://github.com/snapotter-hq/snapotter/commit/a6837b687a096b754367223b4472481071a450b7))
* **e2e:** disable Astro Dev Toolbar during landing e2e tests ([2927e0d](https://github.com/snapotter-hq/snapotter/commit/2927e0d4be063353abb9d23494158849ac3a191a))
* **e2e:** wait for post-login redirect before forcing navigation ([#341](https://github.com/snapotter-hq/snapotter/issues/341)) ([5b75d81](https://github.com/snapotter-hq/snapotter/commit/5b75d813b8523fd378c731de76c429fe900d9d28)), closes [#340](https://github.com/snapotter-hq/snapotter/issues/340)
* **editor:** apply layer effects + object flip, add Beta badge, repair e2e specs ([3120e67](https://github.com/snapotter-hq/snapotter/commit/3120e6708dd4e0eb39c2d3af01623e8f1e9c4473)), closes [#259](https://github.com/snapotter-hq/snapotter/issues/259)
* **editor:** capture document pixels without the zoom/pan transform ([#259](https://github.com/snapotter-hq/snapotter/issues/259)) ([81e16d7](https://github.com/snapotter-hq/snapotter/commit/81e16d7ce6648e3d7acace0b1915fd42774d1096)), closes [#258](https://github.com/snapotter-hq/snapotter/issues/258)
* **editor:** fill canvas viewport, fix black rulers, add resizable panel ([#258](https://github.com/snapotter-hq/snapotter/issues/258)) ([063a2e4](https://github.com/snapotter-hq/snapotter/commit/063a2e47e298e41d1115f4e6e92a96922a607ce6))
* enforce file type filtering using tool's acceptedInputs ([adfa532](https://github.com/snapotter-hq/snapotter/commit/adfa53246ae228eebe2fdd071f23a83400f1296c))
* **enterprise:** SCIM token generation, GDPR job cancellation, MFA replay, config redaction, encryption validation, SCIM rate limit ([29dd675](https://github.com/snapotter-hq/snapotter/commit/29dd675f4262ac0380df3113ae140b4fc7330d21))
* **enterprise:** ship enterprise package in prod image + S3, analytics, tracing, queue fixes ([#342](https://github.com/snapotter-hq/snapotter/issues/342)) ([8f4235d](https://github.com/snapotter-hq/snapotter/commit/8f4235d2c664337f3b970b1be5acc6f92474aeb6)), closes [#82](https://github.com/snapotter-hq/snapotter/issues/82)
* fetch settings on home page mount so tools are visible ([cf543f6](https://github.com/snapotter-hq/snapotter/commit/cf543f6976166b179deb1e5c0fa993bb9e037f0e))
* file library upload accepts all file types, not just images ([6bcee1e](https://github.com/snapotter-hq/snapotter/commit/6bcee1e0739daaad8c66abcc4a4e1bc00bb40735))
* **files:** record the source tool in toolChain on Save to Files ([#435](https://github.com/snapotter-hq/snapotter/issues/435)) ([47a60e7](https://github.com/snapotter-hq/snapotter/commit/47a60e7fad229a9bb9c8e4f33f152cacb3f3dfb4))
* first-run QA sweep of the single-container image ([#413](https://github.com/snapotter-hq/snapotter/issues/413)) ([bf417a5](https://github.com/snapotter-hq/snapotter/commit/bf417a509e529ee6b39d2d5a13a308172b4d08aa))
* GPU deployment robustness (6 fixes from end-to-end testing on an RTX 4070) ([#334](https://github.com/snapotter-hq/snapotter/issues/334)) ([35e18d8](https://github.com/snapotter-hq/snapotter/commit/35e18d8b79861b4aba46d4cb022be868d4d3e757))
* grey out incompatible files in library instead of hiding them ([3a5d55c](https://github.com/snapotter-hq/snapotter/commit/3a5d55cf268301df158163acbeaeaf50c6625a10))
* handle non-image modalities across uploads, previews, and filenames ([#255](https://github.com/snapotter-hq/snapotter/issues/255)) ([8eee17a](https://github.com/snapotter-hq/snapotter/commit/8eee17aeea41577a55b30ead97ca36224b387c04))
* harden against three production Sentry crashes ([#328](https://github.com/snapotter-hq/snapotter/issues/328)) ([8952e9b](https://github.com/snapotter-hq/snapotter/commit/8952e9ba475000da9d2d8e0959c63f3cf2b4506e))
* harden Docker image and async job responses ([f3342a1](https://github.com/snapotter-hq/snapotter/commit/f3342a1e571db5a12f5d8f659903ffe590fa2044))
* harden install queue/dispatcher lifecycle and repair review-sweep regressions ([#395](https://github.com/snapotter-hq/snapotter/issues/395)) ([b4375e5](https://github.com/snapotter-hq/snapotter/commit/b4375e558dfc5ccf34ee6736b886e90f662b1688)), closes [#388](https://github.com/snapotter-hq/snapotter/issues/388) [#390](https://github.com/snapotter-hq/snapotter/issues/390) [#391](https://github.com/snapotter-hq/snapotter/issues/391) [#392](https://github.com/snapotter-hq/snapotter/issues/392) [#393](https://github.com/snapotter-hq/snapotter/issues/393) [#394](https://github.com/snapotter-hq/snapotter/issues/394) [#392](https://github.com/snapotter-hq/snapotter/issues/392)
* **i18n:** complete and correct Italian translation ([#231](https://github.com/snapotter-hq/snapotter/issues/231)) ([#298](https://github.com/snapotter-hq/snapotter/issues/298)) ([c0a8b36](https://github.com/snapotter-hq/snapotter/commit/c0a8b36dcbf56f49fa2e58c8d040171e9e6b63e3))
* **i18n:** complete the Italian feedback translations ([#426](https://github.com/snapotter-hq/snapotter/issues/426)) ([3ae48cc](https://github.com/snapotter-hq/snapotter/commit/3ae48cc76ad4021854a805fcea486f58b045f595)), closes [#425](https://github.com/snapotter-hq/snapotter/issues/425)
* **i18n:** complete the Italian translation update ([#438](https://github.com/snapotter-hq/snapotter/issues/438)) ([#450](https://github.com/snapotter-hq/snapotter/issues/450)) ([0fafeb2](https://github.com/snapotter-hq/snapotter/commit/0fafeb26766ed49014d13b649f9b43ffc2fffca5))
* **i18n:** restore accents in Italian loading-message arrays ([#322](https://github.com/snapotter-hq/snapotter/issues/322)) ([29fcc87](https://github.com/snapotter-hq/snapotter/commit/29fcc8770887b981e73a17e43bea79a2f13da1e5)), closes [#298](https://github.com/snapotter-hq/snapotter/issues/298)
* **i18n:** translate Arabic (ar) UI strings ([#314](https://github.com/snapotter-hq/snapotter/issues/314)) ([ab37505](https://github.com/snapotter-hq/snapotter/commit/ab37505e681602d67ad447e3ad49588e905d48d9))
* **i18n:** translate Brazilian Portuguese (pt-BR) UI strings ([#307](https://github.com/snapotter-hq/snapotter/issues/307)) ([3900350](https://github.com/snapotter-hq/snapotter/commit/3900350ad56d63e9b8ed119539c94c54faf971e0))
* **i18n:** translate Dutch (nl) UI strings ([#309](https://github.com/snapotter-hq/snapotter/issues/309)) ([d036ff6](https://github.com/snapotter-hq/snapotter/commit/d036ff6d44fa9fd8025d3c38b382127066b248c3))
* **i18n:** translate French (fr) UI strings ([#306](https://github.com/snapotter-hq/snapotter/issues/306)) ([fe5c7c8](https://github.com/snapotter-hq/snapotter/commit/fe5c7c8c8667a24cebcd0af8b68f031ddc01ef15))
* **i18n:** translate German (de) UI strings ([#308](https://github.com/snapotter-hq/snapotter/issues/308)) ([4de24b8](https://github.com/snapotter-hq/snapotter/commit/4de24b8c9e4e703b4fd51965ccb9387e1e09fa59))
* **i18n:** translate Hindi (hi) UI strings ([#316](https://github.com/snapotter-hq/snapotter/issues/316)) ([00c89e4](https://github.com/snapotter-hq/snapotter/commit/00c89e4ae9060c1cc4100340f5db223bfd768e0f))
* **i18n:** translate Indonesian (id) UI strings ([#318](https://github.com/snapotter-hq/snapotter/issues/318)) ([8a8a4f9](https://github.com/snapotter-hq/snapotter/commit/8a8a4f99a431ec10ccfdbb472672e976b5b86db2))
* **i18n:** translate Japanese (ja) UI strings ([#304](https://github.com/snapotter-hq/snapotter/issues/304)) ([d8b037c](https://github.com/snapotter-hq/snapotter/commit/d8b037c8c6e8c6cde9c4c6900c1d5689143f5d5d))
* **i18n:** translate Korean (ko) UI strings ([#305](https://github.com/snapotter-hq/snapotter/issues/305)) ([e7b412f](https://github.com/snapotter-hq/snapotter/commit/e7b412fc097d6c745037781e7478414a33d6db25))
* **i18n:** translate Polish (pl) UI strings ([#312](https://github.com/snapotter-hq/snapotter/issues/312)) ([7ce9c64](https://github.com/snapotter-hq/snapotter/commit/7ce9c64b50c5b1fa1eb153c8635384eb5ae0e03b))
* **i18n:** translate Russian (ru) UI strings ([#311](https://github.com/snapotter-hq/snapotter/issues/311)) ([eb9a14a](https://github.com/snapotter-hq/snapotter/commit/eb9a14a226824047cdf00ac94ef647e2bfdcf23a))
* **i18n:** translate Simplified Chinese (zh-CN) UI strings ([#302](https://github.com/snapotter-hq/snapotter/issues/302)) ([26d8a06](https://github.com/snapotter-hq/snapotter/commit/26d8a0685c6b2a216fd72b9431d18543abfa89ee))
* **i18n:** translate Spanish (es) UI strings ([#301](https://github.com/snapotter-hq/snapotter/issues/301)) ([b3ff8ac](https://github.com/snapotter-hq/snapotter/commit/b3ff8accec5494d2fe97a27355257b3c5fa2f91d))
* **i18n:** translate Swedish (sv) UI strings ([#310](https://github.com/snapotter-hq/snapotter/issues/310)) ([c4ce5f9](https://github.com/snapotter-hq/snapotter/commit/c4ce5f96dfe6daf8e560de34cd748b1795ed12ff))
* **i18n:** translate Thai (th) UI strings ([#319](https://github.com/snapotter-hq/snapotter/issues/319)) ([5a6368d](https://github.com/snapotter-hq/snapotter/commit/5a6368db89df5419885b3f8dc568eeaa16cd8206))
* **i18n:** translate Traditional Chinese (zh-TW) UI strings ([#303](https://github.com/snapotter-hq/snapotter/issues/303)) ([de4d2ed](https://github.com/snapotter-hq/snapotter/commit/de4d2ed166d858530dcbbb0634ddcb9c7536ab07))
* **i18n:** translate Turkish (tr) UI strings ([#315](https://github.com/snapotter-hq/snapotter/issues/315)) ([8c1c2c3](https://github.com/snapotter-hq/snapotter/commit/8c1c2c310eac95d5a75fcfd563b9026dba9a4fae))
* **i18n:** translate Ukrainian (uk) UI strings ([#313](https://github.com/snapotter-hq/snapotter/issues/313)) ([6ee1b0f](https://github.com/snapotter-hq/snapotter/commit/6ee1b0f7a1b58dd7ae113fc4268067e0ac1e0b3c))
* **i18n:** translate Vietnamese (vi) UI strings ([#317](https://github.com/snapotter-hq/snapotter/issues/317)) ([4702721](https://github.com/snapotter-hq/snapotter/commit/470272169d947e27ff826e2c0e24e1553eda0543))
* **image-engine:** support sharp 0.35 types ([2137be4](https://github.com/snapotter-hq/snapotter/commit/2137be479e1a23e163587acd1609e77058e9e134))
* **jobs:** pre-warm QueueEvents to kill first-sync-wait flake ([#285](https://github.com/snapotter-hq/snapotter/issues/285)) ([dba8a85](https://github.com/snapotter-hq/snapotter/commit/dba8a85a8013231945741a8fabd09ed86db0d963))
* **jobs:** strip internal paths from all worker SSE error frames ([fc718c1](https://github.com/snapotter-hq/snapotter/commit/fc718c168487de1213c978797c3fc84288f26513))
* **landing:** add www redirect, noindex JS chunks, and website badge ([47b6984](https://github.com/snapotter-hq/snapotter/commit/47b6984be0c8d8de07e0770a34d61a9795b71eaf))
* **lint:** biome-format tool-factory and json-xml to green apps/api lint ([#252](https://github.com/snapotter-hq/snapotter/issues/252)) ([7865339](https://github.com/snapotter-hq/snapotter/commit/7865339fc8c3b8696af5919212ddba4120c07f23)), closes [#251](https://github.com/snapotter-hq/snapotter/issues/251)
* **lint:** clear remaining biome errors (unused code, optional chains, non-null assertions, effect deps) ([19dc6ba](https://github.com/snapotter-hq/snapotter/commit/19dc6ba554a05f531db01f23ce3e71297f651f81))
* **lint:** make husky hook executable; rename backend useS3 to isS3Enabled (clears 17 false-positive useHookAtTopLevel) ([7561f2a](https://github.com/snapotter-hq/snapotter/commit/7561f2a8c3a92c412f0d38b9f1323aa8bcfcf4a4))
* **lint:** resolve [#280](https://github.com/snapotter-hq/snapotter/issues/280) Lint failures (route formatting + landing import sort) ([6e1b9cd](https://github.com/snapotter-hq/snapotter/commit/6e1b9cd3cc3b33b18724eb071f3ad4023ab1ee80))
* make search bar full-width to align with tabs and grid ([207c1c3](https://github.com/snapotter-hq/snapotter/commit/207c1c348be5af23c793a96bf07b9f7b7c26ce31))
* **media:** encode gif-to-video WebM as yuv420p ([e96c314](https://github.com/snapotter-hq/snapotter/commit/e96c314ab9b049c3b01d49ff2622d2a351962cd9))
* **migrator:** correct and harden the 1.x to 2.0 SQLite import ([#434](https://github.com/snapotter-hq/snapotter/issues/434)) ([dadf766](https://github.com/snapotter-hq/snapotter/commit/dadf76689933faa8208de9730525409e2497518f))
* **nightly:** de-flake matrix timeouts, exclude browser tool from fuzz ([#349](https://github.com/snapotter-hq/snapotter/issues/349)) ([bbad953](https://github.com/snapotter-hq/snapotter/commit/bbad953e79a132007490c17dab2b774c4b8ae697)), closes [#347](https://github.com/snapotter-hq/snapotter/issues/347)
* **nightly:** qpdf in test image, NUL-byte settings, AI sub-path fuzz exclude ([#348](https://github.com/snapotter-hq/snapotter/issues/348)) ([078743d](https://github.com/snapotter-hq/snapotter/commit/078743d6b28aa4078f213cae4c39a3b99e515d8d)), closes [#346](https://github.com/snapotter-hq/snapotter/issues/346) [#346](https://github.com/snapotter-hq/snapotter/issues/346)
* **normalize-audio:** preserve source sample rate after loudnorm ([#248](https://github.com/snapotter-hq/snapotter/issues/248)) ([1548d47](https://github.com/snapotter-hq/snapotter/commit/1548d475ca3d1ace0f14d24ecc5e9a13ca5770a2))
* offline CodeFormer face-enhance (ship RealESRGAN_x2plus in upscale-enhance bundle) ([#433](https://github.com/snapotter-hq/snapotter/issues/433)) ([cf884b5](https://github.com/snapotter-hq/snapotter/commit/cf884b52cdf4e8852e0cd24215b22ed2220230da))
* **passport-photo:** require the face-detection bundle, not just background-removal ([#329](https://github.com/snapotter-hq/snapotter/issues/329)) ([32c1192](https://github.com/snapotter-hq/snapotter/commit/32c1192d635898012af2ff14cdbd68c0acd6a3e1)), closes [#327](https://github.com/snapotter-hq/snapotter/issues/327) [#327](https://github.com/snapotter-hq/snapotter/issues/327)
* PDF tool QA sweep - library auto-save versioning, AI fileId threading, modality polish ([#251](https://github.com/snapotter-hq/snapotter/issues/251)) ([08961fc](https://github.com/snapotter-hq/snapotter/commit/08961fcc8941bada2608e386bfa6d6b12e996f35))
* post-2.0 audit bug fixes (404 route, worker logging, outpaint gate, pandoc path) ([2bd3e23](https://github.com/snapotter-hq/snapotter/commit/2bd3e2302ad11b306971d0f651f8ad1ee1e9b785))
* prevent double scrollbar by locking html/body overflow ([914ff1b](https://github.com/snapotter-hq/snapotter/commit/914ff1b1aab345237f325cd152bd3ebc693bab52))
* preview generation works for processed results too ([b93dc95](https://github.com/snapotter-hq/snapotter/commit/b93dc95bcaa197cfd4b29c030a04decaa7babcf8))
* QA sweep - tool routes, security, i18n, a11y, + AI bundle install hardening ([#393](https://github.com/snapotter-hq/snapotter/issues/393)) ([b37faed](https://github.com/snapotter-hq/snapotter/commit/b37faed95fc3d2f51c78f40954fdbb5c7b94779d)), closes [hi#resolution](https://github.com/hi/issues/resolution) [hi#severity](https://github.com/hi/issues/severity)
* reassign 4 tools to correct display modes ([619f61b](https://github.com/snapotter-hq/snapotter/commit/619f61b4700c9fd3b21f90b7c9512672fae57dab))
* release-acceptance QA follow-ups (upload crash, scipy ABI conflict, rate limit, OCR fallback) ([#458](https://github.com/snapotter-hq/snapotter/issues/458)) ([60d01ab](https://github.com/snapotter-hq/snapotter/commit/60d01ab2ddfad4bf837d30369c2f39e9734cb571)), closes [#413](https://github.com/snapotter-hq/snapotter/issues/413) [#437](https://github.com/snapotter-hq/snapotter/issues/437)
* remove all legacy redirects from routes ([fd26fa3](https://github.com/snapotter-hq/snapotter/commit/fd26fa35868b75ca7fd352115396ec9b23dd4280))
* remove automatic third-party egress of user data + optional strict offline mode (OSM tiles, Scalar fonts, editor fonts, AI model downloads) ([#422](https://github.com/snapotter-hq/snapotter/issues/422)) ([6e3a14e](https://github.com/snapotter-hq/snapotter/commit/6e3a14ec6bfc15f18aef3c7aa20cde6d4823dc29))
* remove double scrollbar on home page ([ae95976](https://github.com/snapotter-hq/snapotter/commit/ae959760e3fec115021e2ada7af579ecd13f181c))
* remove duplicated 1.x migration callout in README ([#456](https://github.com/snapotter-hq/snapotter/issues/456)) ([2a899ef](https://github.com/snapotter-hq/snapotter/commit/2a899ef9041c3c637380a91e532273a3581a19d1)), closes [#448](https://github.com/snapotter-hq/snapotter/issues/448) [#454](https://github.com/snapotter-hq/snapotter/issues/454)
* remove redundant Getting Started section from home page ([287f6f1](https://github.com/snapotter-hq/snapotter/commit/287f6f15ded4b84e9d0f019522915ccac822d083))
* repair docker validation QA tooling, dispatcher crash-accounting, and image-enhancement RAW hang ([#391](https://github.com/snapotter-hq/snapotter/issues/391)) ([bd1838e](https://github.com/snapotter-hq/snapotter/commit/bd1838e40b0d11715eaf9eff6b4152a707a29b5b))
* repair URL import (DNS-pinned fetch on Node 22 + non-image modalities) ([#246](https://github.com/snapotter-hq/snapotter/issues/246)) ([8f6312c](https://github.com/snapotter-hq/snapotter/commit/8f6312c521246215ef1a27c65475aa2005e409e0))
* resolve 18 QA-discovered bugs across tools, previews, and the AI pipeline ([#242](https://github.com/snapotter-hq/snapotter/issues/242)) ([d8cf979](https://github.com/snapotter-hq/snapotter/commit/d8cf979d4ba31b802c254f7eeb5ef50b424a56b7))
* resolve 6 bugs from enterprise audit (SIEM cursor, legal hold join, SCIM role, GDPR self-purge, export OOM, quota check) ([7f62b1b](https://github.com/snapotter-hq/snapotter/commit/7f62b1bf12d2cf1baaf5586b201458eab100a111))
* resolve hardcoded /app paths and loosen mediapipe pin for native installs ([60e3ac2](https://github.com/snapotter-hq/snapotter/commit/60e3ac2210f93a350729246cf2b4bb303ab2ebc7)), closes [community-scripts/ProxmoxVE#14720](https://github.com/community-scripts/ProxmoxVE/issues/14720)
* resolve Sharp 0.35.1 and BullMQ type incompatibilities after dep bumps ([b76dc68](https://github.com/snapotter-hq/snapotter/commit/b76dc68682c664d4c6a22cf921e786d4c010b757))
* Save to Files shows green success state with checkmark after saving ([f4061fe](https://github.com/snapotter-hq/snapotter/commit/f4061fe4b0417ba896de138a791cfc869d3b33f3))
* **security:** basename-sanitize file-preview paths (CodeQL js/path-injection) ([7a3b4e6](https://github.com/snapotter-hq/snapotter/commit/7a3b4e6b3bbdee7c382bd4ad739485b37bb45f46))
* **security:** close file-preview path-injection + tighten subtitle detection ([c1cd871](https://github.com/snapotter-hq/snapotter/commit/c1cd8712f45d9defd5321f2d683cf207ee9d3740))
* **security:** close remaining high-severity CodeQL alerts ([bdadb84](https://github.com/snapotter-hq/snapotter/commit/bdadb843d8238d738e9f849f1754765f5de82411)), closes [hi#severity](https://github.com/hi/issues/severity)
* **security:** explicit per-route rate limits (CodeQL js/missing-rate-limiting) ([ae4fc1d](https://github.com/snapotter-hq/snapotter/commit/ae4fc1decf006b15a2f747cde07d4c1e35c5de13))
* **security:** harden auth and outbound fetches ([c6319cf](https://github.com/snapotter-hq/snapotter/commit/c6319cf8a9efe8745a29339613002c22f82e5393))
* **security:** harden rate limits, Redis auth, resource caps, and error sanitization ([d612264](https://github.com/snapotter-hq/snapotter/commit/d61226496b5745ff7f57ee8138009d4f81826095))
* **security:** numeric CIDR matching for IPv6 SSRF allow/deny ([f75cc32](https://github.com/snapotter-hq/snapotter/commit/f75cc328acd18b4714291400ac348058db6d28d8))
* send auth token with preview generation request ([6090688](https://github.com/snapotter-hq/snapotter/commit/609068870842629e085a77d9d5144c5102beae19))
* set a writable HOME for the app user so PaddleOCR works in non-root deployments ([#430](https://github.com/snapotter-hq/snapotter/issues/430)) ([e0dbf2a](https://github.com/snapotter-hq/snapotter/commit/e0dbf2a5c3cc9622218d31698452697957936e00))
* settings dialog and settings API correctness bugs ([8e9452e](https://github.com/snapotter-hq/snapotter/commit/8e9452e65073c500098fde040ce021e3b1c39639))
* show all tools on All tab, remove Popular and Browse sections ([b166f47](https://github.com/snapotter-hq/snapotter/commit/b166f47de515739d87a14ae408a351f81dd37c00))
* show image viewer for video-to-gif/webp results instead of broken video player ([90acd5b](https://github.com/snapotter-hq/snapotter/commit/90acd5bcd905f461f28a5b511c619d896d4a6724))
* show modality starting points in landing command center ([#387](https://github.com/snapotter-hq/snapotter/issues/387)) ([852f6ce](https://github.com/snapotter-hq/snapotter/commit/852f6cecba106eb9d000f95fc1ae9863157a5d3b))
* show tool-specific accepted formats in dropzone, remove privacy note ([5241ccd](https://github.com/snapotter-hq/snapotter/commit/5241ccd1247bda2d9e634576354f3b14a4077c63))
* sort enterprise exports for Biome lint compliance ([3b7f44e](https://github.com/snapotter-hq/snapotter/commit/3b7f44e50e450bff62560b9f21cb12330b0b2b82))
* stamp SnapOtter as Producer on generated PDFs ([#416](https://github.com/snapotter-hq/snapotter/issues/416)) ([8b3f1e6](https://github.com/snapotter-hq/snapotter/commit/8b3f1e6884361ef080c0165059bd8fdb4a14fd79))
* sync demo theme with app ([f6f7b5a](https://github.com/snapotter-hq/snapotter/commit/f6f7b5a4bc2867f680441a91a9af9e1bd148edf4))
* test server registration gaps, Redis subscriber cleanup, atomic settings upsert ([954cfb0](https://github.com/snapotter-hq/snapotter/commit/954cfb01a657ec49293a4e10e72e23eb69e4d9b2))
* **test:** align analytics-env test with ANALYTICS_ENABLED=true default ([96764ec](https://github.com/snapotter-hq/snapotter/commit/96764ec375189ebec396d0397b116407bfd75e05))
* **test:** mock db/index.js in unit tests that import API modules ([5b3b3c7](https://github.com/snapotter-hq/snapotter/commit/5b3b3c76329d62636c886dfa798b9e14acd7cc90))
* **test:** repair integration suite after analytics column/endpoint removal ([#340](https://github.com/snapotter-hq/snapotter/issues/340)) ([6917a8b](https://github.com/snapotter-hq/snapotter/commit/6917a8b0c7864fc9b244ecda23dcdbcbf02f9733)), closes [#336](https://github.com/snapotter-hq/snapotter/issues/336)
* **test:** resolve CI failures from the overhaul ([34b006d](https://github.com/snapotter-hq/snapotter/commit/34b006ded772b96d7b4336e5ffcc67e1039253c3))
* **tests:** section-prefix tool API URLs across overhauled suite ([d4dc2ea](https://github.com/snapotter-hq/snapotter/commit/d4dc2eae5db66b098d8a4e735ab61053ddb50635)), closes [#280](https://github.com/snapotter-hq/snapotter/issues/280)
* **test:** update import paths for cleanup.test.ts after move to integration ([78679c5](https://github.com/snapotter-hq/snapotter/commit/78679c57f46d11d89dcb384373a63e28e1a75635))
* **tools:** honest content-type for edit-metadata pass-through ([#350](https://github.com/snapotter-hq/snapotter/issues/350)) ([88af8d4](https://github.com/snapotter-hq/snapotter/commit/88af8d46fb001dd019de54e48dd7c73c5feaea73))
* **tools:** surface chars count in pdf-to-text result payload ([3c44335](https://github.com/snapotter-hq/snapotter/commit/3c4433583b836e53ee2a35b79c8d9be07760992e))
* top nav respects dark mode theme ([43b1901](https://github.com/snapotter-hq/snapotter/commit/43b190134e8858301fa3bffd1882260d6928c441))
* **ui:** constrain preview dropzone height in pipeline builder ([931e15a](https://github.com/snapotter-hq/snapotter/commit/931e15a6bd04fc84d236b41dbbd7df0feefe29a9))
* **ui:** show repair UI when AI bundle models are broken ([#214](https://github.com/snapotter-hq/snapotter/issues/214)) ([edea82b](https://github.com/snapotter-hq/snapotter/commit/edea82ba27cd92aa5188144e525f9889b6ba1b9d))
* update privacy policy to reference PostgreSQL instead of SQLite ([5af1ac4](https://github.com/snapotter-hq/snapotter/commit/5af1ac4dcf0c8ff17bdcc0efc6c179afc8543168))
* use media-player for all video/audio tools that had no-comparison or side-by-side ([69300b5](https://github.com/snapotter-hq/snapotter/commit/69300b5c83f43ebde213473f66d9f255e33cf9dc))
* use processedFileName fallback for non-native format detection after processing ([14ded6f](https://github.com/snapotter-hq/snapotter/commit/14ded6fbd0babb43de8a544527bb2293b9d83b2d))
* validate non-image inputs by modality (batch + pipeline) ([#244](https://github.com/snapotter-hq/snapotter/issues/244)) ([03e7123](https://github.com/snapotter-hq/snapotter/commit/03e71236f93853e28d7ad8428fe8a9b918849773))
* **web:** add modality to fuse search keys, merge docs+files in fullscreen grid ([3114323](https://github.com/snapotter-hq/snapotter/commit/3114323d07a941cfcb261bc245250a8f3f6e8222))
* **web:** point tool keyboard shortcuts at section routes ([0689ae5](https://github.com/snapotter-hq/snapotter/commit/0689ae554fa353a231f01e4dffd3ac362afcb3ba))
* **web:** resolve feature install status sync and mutual exclusivity ([#214](https://github.com/snapotter-hq/snapotter/issues/214)) ([492da82](https://github.com/snapotter-hq/snapotter/commit/492da820f8ce298addd3038dad2f55e91cdb12bd))
* **web:** restore preview panel rendering for all file types ([1181b1f](https://github.com/snapotter-hq/snapotter/commit/1181b1fe22c7f5a998f8954f9e02a184a3513cf8))
* **web:** show modality thumbnails for non-image files in the strip ([#245](https://github.com/snapotter-hq/snapotter/issues/245)) ([aa3ae6e](https://github.com/snapotter-hq/snapotter/commit/aa3ae6ec915821d664cef0c1537e2b64d9a6fcee))
* **web:** update dropzone to accept all file types, not just images ([b8b6b0a](https://github.com/snapotter-hq/snapotter/commit/b8b6b0a44ada1670e4e41bcf6cd22f713d14c512))
* **web:** wrap modality tabs to prevent overflow in narrow sidebar ([dab31d6](https://github.com/snapotter-hq/snapotter/commit/dab31d6e32c7eee95199d7a686d507a98d58138f))

### Features

* add a Keep it free sponsor button to the top nav ([#427](https://github.com/snapotter-hq/snapotter/issues/427)) ([7b04317](https://github.com/snapotter-hq/snapotter/commit/7b04317ed20e062c273fe60a19cdcaac2b50e651))
* add AES-256-GCM encryption at rest for sensitive settings ([2520cdd](https://github.com/snapotter-hq/snapotter/commit/2520cdd556c8a5946b4eb6426aebc8811944e8d9))
* add audio waveform visualization with wavesurfer.js ([d8a8e05](https://github.com/snapotter-hq/snapotter/commit/d8a8e055906eeab9d1f9a048c4f8d5517cff8f80))
* add backup status tracking endpoints ([2aea351](https://github.com/snapotter-hq/snapotter/commit/2aea351f70cfd171befad164bc553e7feb998884))
* add bundle build script for CI ([52b9405](https://github.com/snapotter-hq/snapotter/commit/52b940585afa1788119dc50f6d44e98f32192ad3))
* add CI workflow for building and publishing AI bundles ([7dabfe2](https://github.com/snapotter-hq/snapotter/commit/7dabfe293dd171bb7b7ec6909ced85b178e67961))
* add Cmd+Enter (process) and Cmd+S (download) keyboard shortcuts ([7c9ca85](https://github.com/snapotter-hq/snapotter/commit/7c9ca85952bc155580becc8f95125482875a3ed5))
* add enterprise Phase 1-4 feature flags and new permissions ([86d6f50](https://github.com/snapotter-hq/snapotter/commit/86d6f50ea6696032bed8f24ba0fd58fe54af5887))
* add full-width dropzone state and tool branding to tool page ([f2ac057](https://github.com/snapotter-hq/snapotter/commit/f2ac05747e1f3ffde4f2e3b3bf321237ef699e85))
* add GET /api/v1/tools/popular endpoint ([1107005](https://github.com/snapotter-hq/snapotter/commit/110700520cfaf4c70e54ff11e7cdee7f7cf50f66))
* add landing tool command center ([#386](https://github.com/snapotter-hq/snapotter/issues/386)) ([3b1d484](https://github.com/snapotter-hq/snapotter/commit/3b1d484b4d4cc9e9da843ceeb442c6dbf7c8066c))
* add OpenTelemetry distributed tracing (enterprise) ([#232](https://github.com/snapotter-hq/snapotter/issues/232)) ([3fb8164](https://github.com/snapotter-hq/snapotter/commit/3fb8164fa539ef952fddcef89bcec01097db991e))
* add page-level drop overlay and paste handler on tool page ([6ea515c](https://github.com/snapotter-hq/snapotter/commit/6ea515ccdd7cd35f3727d0cf4137b81e9bdea050))
* add per-tool permission model with category and per-tool modes ([9fa23f4](https://github.com/snapotter-hq/snapotter/commit/9fa23f454322808d8c791b9d26b6bdbd1a5e9e73))
* add per-user rate limiting and concurrent job limits ([239f85f](https://github.com/snapotter-hq/snapotter/commit/239f85f098c5c6d3576531e915f57f6128bec357))
* add PostHog customer feedback ([649e65b](https://github.com/snapotter-hq/snapotter/commit/649e65b035c6efbfbe32e4b1db040aa0f9f6ac81))
* add recent tools tracking via localStorage ([6c61ae4](https://github.com/snapotter-hq/snapotter/commit/6c61ae47f496b1aaf937228fe7c55058b0b0756f))
* add request correlation IDs to audit logs and response headers ([1cf1f47](https://github.com/snapotter-hq/snapotter/commit/1cf1f47d6f0554853a501ae1220dbdc0678820cb))
* add session idle timeout and concurrent session limit ([3cc4ef6](https://github.com/snapotter-hq/snapotter/commit/3cc4ef6895ddfc904252e4e99c48d5df41b4c17e))
* add settings slide-in, review panel with size delta, start-over preserving settings ([45121c1](https://github.com/snapotter-hq/snapotter/commit/45121c1a1e8f8f4ab8cbf9601c50faf4a2237803))
* add Sign PDF tool (draw/type/upload signatures, place on a PDF) ([#370](https://github.com/snapotter-hq/snapotter/issues/370)) ([0cdd560](https://github.com/snapotter-hq/snapotter/commit/0cdd560ac46827f7e103e9f1921aec58d226a7db))
* add storage usage tracking with DB counters and reconciliation ([aaa8a37](https://github.com/snapotter-hq/snapotter/commit/aaa8a37c9b3b490a8455311be89a0bd4b858a897))
* add team-level storage quotas with enforcement on upload and save ([d064286](https://github.com/snapotter-hq/snapotter/commit/d0642865590a56769adbc2d7bc6741a12800af52))
* add TopNav and AvatarDropdown components ([f79958a](https://github.com/snapotter-hq/snapotter/commit/f79958a6229015ad64149dadc1fe27909f314a57))
* add usage onboarding survey overlay ([#388](https://github.com/snapotter-hq/snapotter/issues/388)) ([a0d1c70](https://github.com/snapotter-hq/snapotter/commit/a0d1c70172f24af0d4656b6256fc1bfcb9256e69))
* add webhook delivery module with retry and backoff ([ab88b9a](https://github.com/snapotter-hq/snapotter/commit/ab88b9ad0d9104ae69862309f9d2b35c0251d6bc))
* **ai:** add a Reset AI Environment admin feature for the upgrade gap ([#459](https://github.com/snapotter-hq/snapotter/issues/459)) ([fb96cf8](https://github.com/snapotter-hq/snapotter/commit/fb96cf874364b331f152d414b69f8831d0004d07))
* All tab groups tools by modality with collapsible sections ([e7126f9](https://github.com/snapotter-hq/snapotter/commit/e7126f92931e1c0277aca6c56ef06276d961ef64))
* all-in-one embedded single-container mode ([#377](https://github.com/snapotter-hq/snapotter/issues/377)) ([084a9c9](https://github.com/snapotter-hq/snapotter/commit/084a9c9faa48a0b090b1d74be97297a3d5d6f727))
* **analytics:** build-time bake + telemetry depth ([#336](https://github.com/snapotter-hq/snapotter/issues/336)) ([5d36ac0](https://github.com/snapotter-hq/snapotter/commit/5d36ac06d88fc4ca3dbf48294c132249921207d9))
* **analytics:** inject Sentry DSN + PostHog key from build env ([#367](https://github.com/snapotter-hq/snapotter/issues/367)) ([9819c58](https://github.com/snapotter-hq/snapotter/commit/9819c5885eab3ea9857dc3b4ca4465fb4b0994f9)), closes [#336](https://github.com/snapotter-hq/snapotter/issues/336)
* **analytics:** upload web source maps to Sentry + tie release to build ([#369](https://github.com/snapotter-hq/snapotter/issues/369)) ([1c202c6](https://github.com/snapotter-hq/snapotter/commit/1c202c6ef08943f05d31fd4d83dff817e1c1503b))
* **api:** section-prefix all custom tool route literals ([10bb73f](https://github.com/snapotter-hq/snapotter/commit/10bb73ffc116814f4dc6ba9c33ed32d8d32d912d))
* **api:** section-prefix factory + batch routes via apiToolPath (+section validation) ([68dd7da](https://github.com/snapotter-hq/snapotter/commit/68dd7dabaa7a9e6d87017b257b5c8cb0f34ae90e))
* arrow key navigation in Files list ([52693fc](https://github.com/snapotter-hq/snapotter/commit/52693fc843143b9ac2d788fb9b5321ee3a52409f))
* **audit:** add TOOL_EXECUTED logging with opt-in setting ([36f083b](https://github.com/snapotter-hq/snapotter/commit/36f083ba64bfa2063c724223c3d59ffadb626cb5))
* **audit:** capture IP address, make TRUST_PROXY configurable ([5d2f520](https://github.com/snapotter-hq/snapotter/commit/5d2f520d7823969578945acfd3e6799bfd6ff3f7))
* **audit:** extensible event type system with shared constants ([37b2b9c](https://github.com/snapotter-hq/snapotter/commit/37b2b9c2eef0e2926dbee03f6b24f16eb53f544e))
* **automate:** make the pipeline builder fully multi-modal ([#335](https://github.com/snapotter-hq/snapotter/issues/335)) ([a53038e](https://github.com/snapotter-hq/snapotter/commit/a53038ed9610a964090a98f6973a25449a1e4a9f))
* bump feature manifest to v2 with archive metadata ([a4ac7cf](https://github.com/snapotter-hq/snapotter/commit/a4ac7cf7d66240f64fc7c394711a4ab5b5036056))
* **ci:** add verify job to ai-bundles workflow ([7d09927](https://github.com/snapotter-hq/snapotter/commit/7d099274c120c28845ab40fa8b00754015552901))
* **ci:** add verify-bundle.sh for AI bundle smoke testing ([5ee948d](https://github.com/snapotter-hq/snapotter/commit/5ee948d36a998f8a39b92d31cc9e3def331535e8))
* clean preview button with progress bar, add document preview support ([3decfaa](https://github.com/snapotter-hq/snapotter/commit/3decfaae3ea0180415851cacfc88674b50418c01))
* clean tool page nav, add Import from Files on dropzone ([b5eef64](https://github.com/snapotter-hq/snapotter/commit/b5eef644e3c932b18884141055b65a7904d57665))
* copy-primary for data tools, download-all label for multi-output, batch failure display ([bc3ad2b](https://github.com/snapotter-hq/snapotter/commit/bc3ad2b008e789b92597688923690f28d5cf54ed))
* **db:** add audit integrity/requestId columns, user_preferences table, audit indexes ([cafd2d8](https://github.com/snapotter-hq/snapotter/commit/cafd2d8b6547eccdb9735acf344207534c13aeae))
* **db:** add data lifecycle columns (deleteAfter, legalHold, quotas, retention) ([7e5843c](https://github.com/snapotter-hq/snapotter/commit/7e5843cd403b2d59d844d5a1d8d3a9ccb0618899))
* **db:** add identity columns (lastActivity, toolPermissions, TOTP) ([7ed043e](https://github.com/snapotter-hq/snapotter/commit/7ed043ec5301106ad64ec0accc7ce411d818a469))
* disable auto-save, add Save to Files button, accept all file types in library ([d0795b6](https://github.com/snapotter-hq/snapotter/commit/d0795b69eaecf61cdb65377ee5561fca64c1de26))
* **docs:** Two-Doors home, otter-orange brand, Pagefind search, and enterprise SSO/SCIM/roles guides ([91ac583](https://github.com/snapotter-hq/snapotter/commit/91ac583e873d70fed01ecdb337c60a0234ebdc1d))
* **enterprise:** add audit log archival with crash-safe state machine ([c1dc27f](https://github.com/snapotter-hq/snapotter/commit/c1dc27f2484120f7f4571c8472fccd292e43f259))
* **enterprise:** add audit log export endpoint (CSV/JSON) ([913dd6b](https://github.com/snapotter-hq/snapotter/commit/913dd6bbe1518e7b709dd002329417789e103f49))
* **enterprise:** add configuration export/import with dry-run and dependency validation ([0f883fe](https://github.com/snapotter-hq/snapotter/commit/0f883fe853fb818275bcdb44248f99e24ca33083))
* **enterprise:** add GDPR user data export (async) ([b6a8226](https://github.com/snapotter-hq/snapotter/commit/b6a82268377bc331e38e65dcf80fe60aec6a2ff2))
* **enterprise:** add GDPR user/team data purge with audit redaction ([dd2a507](https://github.com/snapotter-hq/snapotter/commit/dd2a50799a255a05e732de5943a7481b91b2c86d))
* **enterprise:** add IP allowlisting with CIDR matching and Redis cache ([db6f7bf](https://github.com/snapotter-hq/snapotter/commit/db6f7bf38af677f896a69ea75c2cd68ec05bffb7))
* **enterprise:** add legal hold with cleanup bypass ([fa7da7c](https://github.com/snapotter-hq/snapotter/commit/fa7da7ce0c3bf480a59b429c1ad39ab23fe7cf18))
* **enterprise:** add per-team retention overrides with deleteAfter ([b60f550](https://github.com/snapotter-hq/snapotter/commit/b60f550b3fa54c4b10681e6281e989678b6c955a))
* **enterprise:** add SAML 2.0 SSO with SP-initiated login ([54132d1](https://github.com/snapotter-hq/snapotter/commit/54132d1833439425825954c4c55fba7975e24d0a))
* **enterprise:** add SCIM 2.0 provisioning (Users + Groups) ([a1b5c6d](https://github.com/snapotter-hq/snapotter/commit/a1b5c6d2c30c8a195a17b534a18c5fb93733135a))
* **enterprise:** add SIEM webhook forwarding with circuit breaker ([d3f30a2](https://github.com/snapotter-hq/snapotter/commit/d3f30a2f5d1d846bb4d56a3d2f0c208065258ba8))
* **enterprise:** add SSO enforcement mode with break-glass admin ([0c4468a](https://github.com/snapotter-hq/snapotter/commit/0c4468a004c228e860912dd8e3e662d270d3d113))
* **enterprise:** add tamper-resistant audit mode with HMAC integrity ([895e29e](https://github.com/snapotter-hq/snapotter/commit/895e29e93fb141a279dcd090ad0199f248faed2f))
* **enterprise:** add TOTP MFA with enrollment, verification, and recovery codes ([1787be3](https://github.com/snapotter-hq/snapotter/commit/1787be35fe4b6d3b1f313511cc36c2277df64034))
* **enterprise:** add unified webhook system with admin alerts ([d86e458](https://github.com/snapotter-hq/snapotter/commit/d86e4585e4e1069fa95aa49de951827fbdccf8f3))
* **enterprise:** add upgrade management endpoints (version, migrations, readiness) ([03e12e6](https://github.com/snapotter-hq/snapotter/commit/03e12e6f0b92308dc311a33a848cf09cd7e74bfd))
* expand alternatives comparisons ([#384](https://github.com/snapotter-hq/snapotter/issues/384)) ([174001d](https://github.com/snapotter-hq/snapotter/commit/174001d3844172fbc8d7dad8282ef1d83a8e4d7f))
* expand Prometheus metrics with request duration, storage, and auth counters ([fb14f41](https://github.com/snapotter-hq/snapotter/commit/fb14f41512e8d65417b6a7dee129d48f05285459))
* extend crash recovery for pre-built bundle artifacts ([d1e9536](https://github.com/snapotter-hq/snapotter/commit/d1e9536cffb7a53a291627bd36d173d850082a41))
* extend health endpoints with disk space, S3, storage, and backup checks ([6237684](https://github.com/snapotter-hq/snapotter/commit/6237684ec86289fdfab5ed982dd2af6a9a5e37d1))
* extend importBundleArchive for site-packages and fixups ([5d5240e](https://github.com/snapotter-hq/snapotter/commit/5d5240e48131653e3a64917fae312a044326992c))
* **feedback:** always-on nav button with GitHub/email handoff when analytics is off ([#428](https://github.com/snapotter-hq/snapotter/issues/428)) ([5dcc06a](https://github.com/snapotter-hq/snapotter/commit/5dcc06a99ea18a6b5448b3a7a1e54b40158c8b07))
* **feedback:** route failed-run Report issue through the offline handoff ([#429](https://github.com/snapotter-hq/snapotter/issues/429)) ([8cdd85a](https://github.com/snapotter-hq/snapotter/commit/8cdd85a49339c2cce3c916b756e4c6da1fe2abfc))
* filter files by modality when importing from tool page ([826f1ef](https://github.com/snapotter-hq/snapotter/commit/826f1efc087d1708ee1171b145f710e57a9d7ed2))
* **i18n:** add repair UI strings for broken AI bundles ([8e15fe7](https://github.com/snapotter-hq/snapotter/commit/8e15fe7a3f633771ba05e1b4cb2f3eae15b5bed0))
* **i18n:** raise translation coverage across all 20 locales ([#332](https://github.com/snapotter-hq/snapotter/issues/332)) ([ffbf4f9](https://github.com/snapotter-hq/snapotter/commit/ffbf4f9e10f5e8f4fde9be286e0e7a024dde7a15))
* **i18n:** raise translation coverage across all 20 locales (round 2) ([#453](https://github.com/snapotter-hq/snapotter/issues/453)) ([44e1f90](https://github.com/snapotter-hq/snapotter/commit/44e1f90e4a847b7059869cd5750280f472ef3a05)), closes [#428](https://github.com/snapotter-hq/snapotter/issues/428) [425/#426](https://github.com/snapotter-hq/snapotter/issues/426)
* Import from Files works, remove pipeline button from files page ([b8a6b20](https://github.com/snapotter-hq/snapotter/commit/b8a6b2018ed1736ab4f9f234b435bce666c10d90))
* inline error messages with recovery actions ([56cd843](https://github.com/snapotter-hq/snapotter/commit/56cd843482ab93d59f26fc360fa48d11f161c815))
* **landing,docs:** SEO fixes and query-matched tool pages ([#383](https://github.com/snapotter-hq/snapotter/issues/383)) ([ec98dfd](https://github.com/snapotter-hq/snapotter/commit/ec98dfde9a63590f513c1448a18a57f836ad1515))
* **landing:** add animated shadergradient hero background ([#411](https://github.com/snapotter-hq/snapotter/issues/411)) ([5500fac](https://github.com/snapotter-hq/snapotter/commit/5500facc5d8d38437bd976663a10b41ba71bee10))
* **landing:** auto-refresh live GitHub stars and image-pull stats ([#292](https://github.com/snapotter-hq/snapotter/issues/292)) ([33671d2](https://github.com/snapotter-hq/snapotter/commit/33671d2d9227ffbb3580e8b7d40fd260c06d7853))
* **landing:** enterprise-focused hero redesign and section polish ([#239](https://github.com/snapotter-hq/snapotter/issues/239)) ([f622a4f](https://github.com/snapotter-hq/snapotter/commit/f622a4f691dce066adcc31bd8838b1f4014b92e0))
* **landing:** generate 301 redirects for old tool URLs; fix robots sitemap reference ([0051655](https://github.com/snapotter-hq/snapotter/commit/0051655bbf0201642f97436adf5c3130af8131c0))
* **landing:** migrate from Next.js to Astro 5 ([8403222](https://github.com/snapotter-hq/snapotter/commit/8403222d08034362382a902feec509665cb32bcb))
* **landing:** one-command Docker quickstart + live-demo CTA in hero ([#455](https://github.com/snapotter-hq/snapotter/issues/455)) ([aaf39a1](https://github.com/snapotter-hq/snapotter/commit/aaf39a1b7221e6581b681e776513063aed39eed3))
* **landing:** refocus hero on privacy-sensitive teams ([#431](https://github.com/snapotter-hq/snapotter/issues/431)) ([8451f9b](https://github.com/snapotter-hq/snapotter/commit/8451f9be0883dc58ac4413bc00c6e6edd082a896))
* **landing:** section-nested tool routes, section index pages, and section-prefixed links ([f158be6](https://github.com/snapotter-hq/snapotter/commit/f158be6aaa1e4ce907e0abe93a20f6791168b310))
* **landing:** swap Try Demo for Get Started Free CTA in top nav ([#418](https://github.com/snapotter-hq/snapotter/issues/418)) ([ea004a3](https://github.com/snapotter-hq/snapotter/commit/ea004a3308de489efa45fa77585ec4da9da82180))
* **landing:** warm up the contact CTAs ([#412](https://github.com/snapotter-hq/snapotter/issues/412)) ([3d4a84d](https://github.com/snapotter-hq/snapotter/commit/3d4a84d06865f0d790d39fee50809633cc6892c8))
* make password policy configurable via admin settings ([8b349b0](https://github.com/snapotter-hq/snapotter/commit/8b349b0341b8cde518558c8ef83523263a95abae))
* merge PostHog customer feedback ([1d99acf](https://github.com/snapotter-hq/snapotter/commit/1d99acf9ee30e0fe9528b02761a3eed5e808f0b8))
* mobile layout for tool-first flow ([896acda](https://github.com/snapotter-hq/snapotter/commit/896acda7ed033a13817d3556c50cea35ec884c3c))
* modality-aware file preview in Files library ([a70b13a](https://github.com/snapotter-hq/snapotter/commit/a70b13ad3207617c794fd90bf5c5dcd85c4a90ec))
* modality-based URLs (/image/resize, /video/compress-video) ([ea83e1c](https://github.com/snapotter-hq/snapotter/commit/ea83e1c8b4af7e613b4a5184b7b3e816ba1f1376))
* **modality:** rename "file" modality label "Data" -> "Files" ([71fefc0](https://github.com/snapotter-hq/snapotter/commit/71fefc05b08bdfc344de6beb16963b7cc632fac2))
* move theme toggle and language selector to top nav bar ([aff9159](https://github.com/snapotter-hq/snapotter/commit/aff9159ae0ef251a5be46bbae5a8a20ede2c419a))
* on-demand preview generation with progress messages for non-native formats ([d3cd4e4](https://github.com/snapotter-hq/snapotter/commit/d3cd4e41b5c8fab1732c16243c0a6c5c46b0b63e))
* pin frequently-used tools to the top of the dashboard ([#440](https://github.com/snapotter-hq/snapotter/issues/440)) ([3aaaacc](https://github.com/snapotter-hq/snapotter/commit/3aaaacc7a1830d748a3f6602cc6a81efbc9a9f32))
* pipeline templates, analytics opt-out, 83 conversion presets, positioning + e2e modernization ([63a03d2](https://github.com/snapotter-hq/snapotter/commit/63a03d26f2f627578420dc4b7011e1078635651a)), closes [#355](https://github.com/snapotter-hq/snapotter/issues/355) [#354](https://github.com/snapotter-hq/snapotter/issues/354) [#356](https://github.com/snapotter-hq/snapotter/issues/356) [#353](https://github.com/snapotter-hq/snapotter/issues/353) [#351](https://github.com/snapotter-hq/snapotter/issues/351)
* polish home page UI -- card borders, colored icons, 3-col grid, Cmd+K hint ([ade79c5](https://github.com/snapotter-hq/snapotter/commit/ade79c53a59cd3a991224f5e82b9a6e3861457bf))
* redesign review panel action hierarchy ([ed458d4](https://github.com/snapotter-hq/snapotter/commit/ed458d4bbdfaeb7054fa15c1fbaf2616ed19f45f))
* remove /fullscreen route, add dynamic page titles ([f9ebb30](https://github.com/snapotter-hq/snapotter/commit/f9ebb304db1b71ad0e6149d094dd7f6cac8777db))
* replace sidebar with top nav bar across all pages ([71fef43](https://github.com/snapotter-hq/snapotter/commit/71fef439a0d41bce34890d637c40a670fb2ce443))
* request a tool when home search finds nothing ([#385](https://github.com/snapotter-hq/snapotter/issues/385)) ([c68297d](https://github.com/snapotter-hq/snapotter/commit/c68297d5a49e659645b510da245241aa26a40cb6))
* rewrite home page as tool-first browser with search and modality tabs ([0d751b7](https://github.com/snapotter-hq/snapotter/commit/0d751b7d143a9102b3fe24e94c4e49ce616ee310))
* rewrite install_feature.py for pre-built tar bundles ([a4fa3ce](https://github.com/snapotter-hq/snapotter/commit/a4fa3ce2a71930a8b57fd3f17181c51f703a2081))
* **scripts:** add segment-anchored tool-path rewrite transform ([c17a4a4](https://github.com/snapotter-hq/snapotter/commit/c17a4a422582594c6e2127429a7373c2e0ade11c))
* **scripts:** add tool-path codemod CLI ([0691457](https://github.com/snapotter-hq/snapotter/commit/0691457f94dd63a53285107ab56d9ba73c1b3b47))
* server-side preview generation for non-native video/audio formats ([412a21e](https://github.com/snapotter-hq/snapotter/commit/412a21ee4d9a68baadef817509a5cc1a675a5143))
* **shared:** add apiToolPath() and section-prefixed routes; drop MODALITY_URL_SLUG ([9ca901f](https://github.com/snapotter-hq/snapotter/commit/9ca901f8d5b23b0a03060b3384af58c60cfbbaa0))
* **shared:** add Section concept and toolSection() partition ([7907471](https://github.com/snapotter-hq/snapotter/commit/790747188afe4fa0347579d6ce2db80d68bcfd2b))
* tiered processing feedback and upload progress indicator ([433d59c](https://github.com/snapotter-hq/snapotter/commit/433d59c726d196c7b51ea2cc29b0e2d44fa11feb))
* tool-first workflow polish -- fix tests, lint cleanup ([f387e98](https://github.com/snapotter-hq/snapotter/commit/f387e98fff6049a4869cebd4e4d7a717e7c1f28e))
* **tools:** 2.0 phase 5 wave 2 - pdf depth (21 tools) ([#220](https://github.com/snapotter-hq/snapotter/issues/220)) ([2f39e38](https://github.com/snapotter-hq/snapotter/commit/2f39e3816209fe81fac4620dd1233921e5de90d4))
* **tools:** 2.0 phase 5 wave 3a - video depth (22 tools) ([#221](https://github.com/snapotter-hq/snapotter/issues/221)) ([5f98b48](https://github.com/snapotter-hq/snapotter/commit/5f98b485935f07e40c0a6e57548892b2e17fc03b))
* **tools:** 2.0 phase 5 wave 3b - audio depth (14 tools) ([#222](https://github.com/snapotter-hq/snapotter/issues/222)) ([638288e](https://github.com/snapotter-hq/snapotter/commit/638288e196f05d889b5d818c0c8a11b0697ec49c))
* **tools:** 2.0 phase 5 wave 4 - office, ebooks, data, archives (14 tools) ([#224](https://github.com/snapotter-hq/snapotter/issues/224)) ([fc7c1f8](https://github.com/snapotter-hq/snapotter/commit/fc7c1f850e36fd01db9f08caf574f6f48d606b93))
* **tools:** 2.0 phase 5 wave 5a - image gap-fill (11 tools) ([#225](https://github.com/snapotter-hq/snapotter/issues/225)) ([6e1b986](https://github.com/snapotter-hq/snapotter/commit/6e1b9865f10c50badddb693a09ec20557379fe63))
* **tools:** 2.0 phase 5 wave 5b - ai pool: ocr-pdf, transcription, background composites (5 tools) ([#226](https://github.com/snapotter-hq/snapotter/issues/226)) ([51666cd](https://github.com/snapotter-hq/snapotter/commit/51666cdd5fd207ceee4da38b63d998af9a044809))
* **web:** add data retention settings to admin UI ([3016571](https://github.com/snapotter-hq/snapotter/commit/3016571c2b2925e8fc7677d9d2daae2bd8191db1))
* **web:** add MFA login prompt and security settings UI ([d397f57](https://github.com/snapotter-hq/snapotter/commit/d397f576676d127ee23c49a2948b041a6a6a777e))
* **web:** add storage dashboard and team retention settings ([c7e9b1d](https://github.com/snapotter-hq/snapotter/commit/c7e9b1ddc6189f847633399a982ce88cb6ae4a85))
* **web:** apply Otter Orange design system and UI improvements ([bcd59c2](https://github.com/snapotter-hq/snapotter/commit/bcd59c20c7da6208e937cac0a9b9f02ecf73b4f1)), closes [#E07832](https://github.com/snapotter-hq/snapotter/issues/E07832) [#2563eb](https://github.com/snapotter-hq/snapotter/issues/2563eb)
* **web:** complete audit log viewer with IP column and all event types ([c6f9a29](https://github.com/snapotter-hq/snapotter/commit/c6f9a29687562c929d708a110eb0b7145c4dab11))
* **web:** fuzzy search with fuse.js for typo-tolerant tool discovery ([22cccd4](https://github.com/snapotter-hq/snapotter/commit/22cccd46c44c386aed5303efbd608105ddc1b30f))
* **web:** group home grid, tabs, and breadcrumb by section ([8301676](https://github.com/snapotter-hq/snapotter/commit/8301676e139fa0ed1d2423e133b11e0214bfe4cd))
* **web:** icon-only modality tabs, merge documents and files ([cc06c80](https://github.com/snapotter-hq/snapotter/commit/cc06c802c13b4d229fa2a97c3493aef4a555b1e6))
* **web:** in-canvas zoom & pan for the object eraser and split tools ([#320](https://github.com/snapotter-hq/snapotter/issues/320)) ([95d100c](https://github.com/snapotter-hq/snapotter/commit/95d100c20bd9b41cca3190a7791807516d2a17e4))
* **web:** modality filter tabs in sidebar tool panel ([8adceaf](https://github.com/snapotter-hq/snapotter/commit/8adceaf92d3effd8b4a2f15d6f8ecfc0f6c1ab8d))
* **web:** modality tabs on fullscreen grid page ([82fb798](https://github.com/snapotter-hq/snapotter/commit/82fb798142663b22d23bda69bf66d0d8cb56def9))
* **web:** section-prefix all tool API calls ([22b4b5c](https://github.com/snapotter-hq/snapotter/commit/22b4b5c3c17ea28712e2e9bf06ca35b6c5a23bd3))
* **web:** show icon + text labels on modality filter tabs ([888d243](https://github.com/snapotter-hq/snapotter/commit/888d243a653b7ecfab20830be2ea8baa961f8e71))
* **web:** stronger modality section visual hierarchy in tool catalog ([3db1b3b](https://github.com/snapotter-hq/snapotter/commit/3db1b3bafd9bfcde86c9d74037a30114437e4f71))
* wire AI bundle build into release pipeline ([d0732d4](https://github.com/snapotter-hq/snapotter/commit/d0732d4a586be25231be93e51e94db7ae05ae8ca))

### Reverts

* **deps:** keep rembg at 2.0.69 (2.0.75 conflicts with pinned numpy==1.26.4) ([4fdd10f](https://github.com/snapotter-hq/snapotter/commit/4fdd10f488642eabe671aa111796d343d0e2d358))

### BREAKING CHANGES

* SnapOtter 2.0 - the platform re-architecture (Postgres 17 +
Redis 8 + BullMQ durable jobs, 157 tools across five modalities) is the 2.0
release line, replacing the 1.x SQLite single-container architecture.
* SQLite is no longer the runtime database. Deployments now
require Postgres (and Redis, used from phase 2). Existing installs migrate
with SQLITE_MIGRATE_PATH or 'pnpm --filter @snapotter/api migrate:sqlite'.

* fix(ci): postgres service + fresh e2e database per run; ignore unfixable torch CVE-2025-3000
v2.0.0
2026-07-07 07:36:51 +00:00
SnapOtter 9d1bbe7beb chore(release): stage 2.0.0 launch (README banner, release notes, resume publishing) [skip ci] 2026-07-07 15:15:41 +08:00
SnapOtterandGitHub fb96cf8743 feat(ai): add a Reset AI Environment admin feature for the upgrade gap (#459)
Uninstalling a bundle only deletes its downloaded model weights, never the
shared venv's site-packages, so self-hosters who already hit an AI bundle
conflict (e.g. the scipy ABI strand) have no clean self-service path via
uninstall+reinstall: reinstalling just overlays corrected files on top of
stale ones. Adds POST /api/v1/admin/features/reset, which wipes
/data/ai/{venv,models,pip-cache}, resets installed.json, and reseeds a real
working venv from the image's baked /opt/venv (extracted docker/reseed-ai-venv.sh,
now shared with entrypoint.sh's existing base-venv-upgrade bootstrap instead
of duplicating that logic) -- leaving an empty venv directory here would
make the very next install fail with "spawn .../python3 ENOENT", caught by
testing this live rather than assuming it. Ships with a matching Settings UI
section (inline confirm, same pattern as per-bundle uninstall) and strings
across all 21 locales.

Verified against a real snapotter/snapotter:1.17.2 image migrated to 2.0.0,
with real multi-GB bundles installed (background-removal + OCR): confirmed
the migrated instance's inherited python3.11 venv (2.0.0 itself uses 3.12)
still imports the fixed scipy/numpy/paddleocr correctly, then reset + real
reinstall + actual tool execution (remove-background, verified output image)
all worked end-to-end.
2026-07-07 14:12:00 +08:00
SnapOtterandGitHub 60d01ab2dd fix: release-acceptance QA follow-ups (upload crash, scipy ABI conflict, rate limit, OCR fallback) (#458)
* fix(api): prevent a crash when an over-limit upload stream has no consumer yet

busboy's "limit" handler destroyed the file stream with an error but never
attached its own error listener, relying entirely on whatever consumes
part.file downstream to do so. On a fast enough connection (or a fully
buffered body, e.g. Fastify inject()), busboy can process enough bytes to
hit the size limit before the route handler's receiveUpload() call has
attached its own stream listener, leaving the resulting "error" event with
zero listeners -- which crashes the whole process by default in Node.

Surfaced by tonight's FULL_MATRIX+FUZZ integration run (880 uncaught
exceptions, all the same root cause). Reproduces deterministically in
isolation; unrelated to this release's actual code delta (file untouched
since PR #413, well before the baseline QA pass).

Fix: attach a baseline no-op error listener the moment the stream is
created, guaranteeing at least one listener always exists. EventEmitter
delivers "error" to every registered listener, so the real consumer's own
error handling is unaffected.

* fix(ai-bundles): rebuild upscale-enhance and photo-restoration to reconcile scipy ABI

upscale-enhance and photo-restoration both depend on codeformer-pip, whose
transitive closure (basicsr -> realesrgan -> gfpgan) pulls in an unpinned
scipy. Both bundles were last built ~June 18-19, before PR #437 added the
manifest's `constraints` array (numpy==1.26.4, scipy==1.12.0, etc.) to pin
exactly this kind of dependency during bundle builds. Only the ocr bundle
was rebuilt after that fix landed.

install_feature.py has no pip install step -- it's a raw tarfile extraction
with no cross-bundle conflict resolution, so installing OCR alongside either
stale bundle left three incompatible scipy versions' files mixed in the same
site-packages directory (a compiled _rotation.*.so from one release next to
Python files expecting a different release's API), breaking the `upscale`
tool and OCR's higher-quality tiers with an ImportError.

Rebuilt both bundles for amd64-gpu and arm64-cpu from the current manifest,
verified scipy/scikit-learn/scikit-image/pandas all resolve to the pinned
versions in the tarballs themselves, then verified end-to-end on real
hardware (Mac arm64 CPU and ubuntu_gpu .248 RTX 4070): installing all
affected bundles together now yields exactly one version of each constrained
package, `upscale` produces correct output, and OCR's balanced/best tiers
correctly use PaddleOCR-GPU instead of erroring out.

Published the rebuilt tarballs to the public deepsafe/feature-bundles
HuggingFace repo and updated this manifest's sha256/sizes to match.

Also adds verify-bundle-compatibility.sh: verify-bundle.sh checks each
bundle in isolation (a fresh venv per bundle), which is exactly why this
shipped twice -- nothing ever checked that bundles built at different times
agree once layered into the one shared venv real installs use. The new
script installs every bundle for an arch into one venv and asserts each
constrained package has exactly one, correct version.

Known follow-up (not fixed here, needs separate discussion): uninstalling a
bundle only removes its downloaded model weights, never the site-packages
it added, so existing installations that already hit this bug have no clean
self-service fix via uninstall+reinstall -- they need a full AI-venv wipe.

* fix(docker): bake a real rate limit default for the all-in-one one-liner

The documented single-container `docker run` install had RATE_LIMIT_PER_MIN=0
(effectively unlimited, ~50k/min) baked in, since only docker-compose.yml
carried a hardened override. A self-hoster following the one-liner path got
no meaningful throttling anywhere, including auth-adjacent routes with no
dedicated per-route limit. Bakes a generous-but-real 1000/min default into
the Dockerfile, raises both compose files' fallback to match so the two
documented install paths converge on the same posture, and updates the Zod
schema default plus docs that quoted the old value.

* fix(api): boot log undercounted tool routes by the conversion-preset total

The "Tool routes: N active" line logged before registerConversionPresets(app)
ran, so it only ever reported the base 158 tools, 83 short of the real
241-tool total. Presets have to register after the base loop (they delegate
to each base tool's own processV2), so the fix moves the log line to after
that call and has registerConversionPresets return its count instead of
reordering the dependency.

* fix(ai): forward {info}/{warning} stderr JSON instead of dropping it

The dispatcher stderr parser only recognized {ready} and {progress,stage}
shaped JSON lines; anything else that parsed as valid JSON (like ocr.py's
GPU-to-tesseract downgrade notice, an {"info": ...} line) matched neither
branch and fell through silently, never reaching docker logs. Adds explicit
{info}/{warning} handling that forwards to console.log/console.warn, same as
the existing [prefix]-tagged non-JSON path.

* fix(api): fall back to a lower OCR tier when PaddleOCR itself is unusable

ocr.ts already retries lower quality tiers on a crashed dispatcher, but the
condition only matched crash-style messages (segfault, exited unexpectedly).
ocr.py's own ImportError/exception handlers already produce messages telling
the caller to use a lower tier (e.g. on the scipy ABI conflict class of bug),
but nothing ever acted on them, so a broken PaddleOCR hard-failed with 422
instead of degrading to Tesseract like ocr-pdf effectively does. Broadens the
retry condition to also catch PaddleOCR-engine-unusable messages.

Note: ocr-pdf's tesseract-only behavior turned out to be an unrelated,
pre-existing, deliberate design choice (PaddleOCR segfaults on rasterized PDF
pages on arm64), not a graceful-fallback mechanism to copy -- the two tools
weren't actually solving the same problem, so this fixes ocr.ts's own gap
rather than trying to mirror ocr-pdf.
2026-07-07 12:18:28 +08:00
SnapOtterandGitHub 52169c7a36 test(landing): update stale homepage e2e assertions to current hero copy (#457)
Updates the homepage.spec.ts hero assertions (page title, headline, subtitle, trust badges) and the navbar contact link label to match the current landing copy. Verified 18/18 homepage e2e tests pass against the live landing.
2026-07-07 00:15:09 +08:00
SnapOtterandGitHub aaf39a1b72 feat(landing): one-command Docker quickstart + live-demo CTA in hero (#455)
Adds a click-to-copy Docker quickstart command and a small live-demo button to the landing hero, between the category cards and the stats. Removes the divider above the stats and tightens hero spacing so the stats stay above the fold. New HeroQuickstart.astro is a scoped copy-to-clipboard island (clipboard + execCommand fallback, aria-live, reduced-motion).
2026-07-06 23:58:02 +08:00
SnapOtterandGitHub 2a899ef904 fix: remove duplicated 1.x migration callout in README (#456)
Two PRs (#448 and #454) independently added the same "Coming from
1.x?" callout, and the merge landed both copies back to back.
2026-07-06 23:42:23 +08:00
SnapOtterandGitHub 865c7789bf Fix stale image-only copy and add 1.x-to-2.0 migration guide (#454)
* docs: add 1.x-to-2.0 migration guide and upgrade notice

Adds MIGRATING.md with backup and upgrade steps, plus a short
"coming from 1.x?" callout in README and the docs upgrade guide
pointing existing users at it.

* fix: replace stale image-only and pre-rename data copy across product

SnapOtter grew from an image-only tool into a 5-modality suite
(Image, Video, Audio, PDF, Files), but copy in several places never
caught up. Fixes:

- dropzone.defaultFormats (i18n): every non-English locale still had
  the pure pre-2.0 image-only format list; English omitted Files
  entirely. Corrected across all 21 locales.
- settings.about.appDescription (i18n): "document, and data" workflow
  copy updated to "PDF, and file" across all 21 locales.
- constants.ts: Files category's raw name was still "Data Files".
- Landing hero subtitle, JSON-LD schema, llms.txt, and 7 spots in the
  competitor-comparison pages.
- Docs: VitePress config, supported-formats, deployment, and an
  architecture.md modality-naming nit.
- OpenAPI description, root package.json description/keyword, and a
  GitHub issue template dropdown option.

DOCKERHUB.md's separate "v1.x, image tools only" pre-release notice
is left untouched since 2.0 hasn't published to Docker Hub yet.

* test: update dropzone format-hint assertion to match corrected copy

The expected string still had the stale image-only/duplicated
PDF-Documents text from before the dropzone.defaultFormats fix.
2026-07-06 15:20:19 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
072dff6c6c chore(deps): bump wavesurfer.js in the production-deps group (#452)
Bumps the production-deps group with 1 update: [wavesurfer.js](https://github.com/katspaugh/wavesurfer.js).


Updates `wavesurfer.js` from 7.12.9 to 7.12.10
- [Release notes](https://github.com/katspaugh/wavesurfer.js/releases)
- [Commits](https://github.com/katspaugh/wavesurfer.js/compare/7.12.9...7.12.10)

---
updated-dependencies:
- dependency-name: wavesurfer.js
  dependency-version: 7.12.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  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-07-06 21:49:22 +08:00
SnapOtterandGitHub 44e1f90e4a feat(i18n): raise translation coverage across all 20 locales (round 2) (#453)
An audit comparing every locale's leaf values against en.ts found
structural key-parity was already perfect (0 missing/extra keys), but
~2,356 leaf values across the 20 non-English locales were still
byte-identical to the English source, meaning they'd shipped untranslated.
The largest single cause: the feedback widget (PR #428) shipped with
English placeholder text in every locale except Italian, which had a
separate community translation (#425/#426).

Translates the 1,289 strings that were genuinely missing translations,
locale by locale, matching each file's own established register,
terminology, and loanword conventions (verified against already-translated
sibling strings rather than assumed). Leaves the remaining ~1,067 flagged
values untouched where they're legitimately identical to English: brand
names, format/protocol acronyms, literal URLs, hex colors, and terms this
project already treats as loanwords in that language.

Verified against current main: pnpm typecheck 0 errors (9/9 workspaces),
i18n-parity/i18n-locale/tool-i18n/template-i18n 45/45 passing (23/23
cross-locale parity), biome check clean.
2026-07-06 21:18:59 +08:00
0fafeb2676 fix(i18n): complete the Italian translation update (#438) (#450)
Lands albanobattistella's Italian translation update from #438, with
one duplicate key corrected so it passes CI.

Their PR retranslated several terms in it.ts, but a stray edit left
watermark-video with two `submit` keys (the second, "Applica
spaziatura interna", was unrelated leftover text). Removed the
duplicate so `submit` stays "Applica filigrana", consistent with the
other watermark strings in the same diff.

Original translation by albanobattistella in #438; duplicate-key fix
by the maintainer.


Claude-Session: https://claude.ai/code/session_018tNg52r7b3RMEeybv5LHCX

Co-authored-by: albanobattistella <34811668+albanobattistella@users.noreply.github.com>
2026-07-06 20:30:42 +08:00
SnapOtterandGitHub b28c9dd2da chore(deps): pin nvidia/cuda base to 12.6.x (#449)
Dependabot proposed nvidia/cuda 12.6.3 to 12.9.2 (#441), a minor bump the existing semver-major ignore did not catch. The 12.6 base is deliberate: the AI bundle wheels are cu126, libcublas-12-6 matches, and a 12.9 base requires driver R575+ so the container fails to start on common 570.x production drivers (the regression PR #334 fixed). Ignore nvidia/cuda >= 12.7 so 12.6.x patch bumps still flow while 12.7+ is held back.
2026-07-06 20:18:03 +08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
d6ae79bebb chore(deps): bump posthog-js (#447)
Bumps the production-deps group with 1 update in the / directory: [posthog-js](https://github.com/PostHog/posthog-js).


Updates `posthog-js` from 1.396.7 to 1.396.8
- [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.396.7...posthog-js@1.396.8)

---
updated-dependencies:
- dependency-name: posthog-js
  dependency-version: 1.396.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  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-07-06 19:16:44 +08:00
SnapOtterandGitHub e09feadd68 docs: add 1.x to 2.0 migration guide pointer and community note (#448)
Root MIGRATING.md pointer + README upgrading link + 1.x community note across README/MIGRATING/docs; drop stale v2.0.0 callout.

Claude-Session: https://claude.ai/code/session_01P22pYJttXCe5cbCzRyfZac
2026-07-06 18:50:27 +08:00
SnapOtter d019217969 chore(deps): bump the production-deps group 2026-07-06 18:39:27 +08:00
SnapOtterandGitHub 36dde9ad87 fix(ai): gate AI tools on per-framework GPU detection, not a shared boolean (#445)
gpu_available() answers "can ANY framework use a GPU" (torch, then ONNX, then
paddle). But torch tools consumed that shared boolean directly as
device = torch.device("cuda" if gpu_available() else "cpu"). On a GPU host where
gpu_available() is True via paddle or ONNX while torch is a CPU-only build, those
tools would route to a CUDA torch cannot use and crash. Transcription had the
mirror problem: it runs on CTranslate2 (not torch), so on a transcription-only
GPU box gpu_available() returned False and Whisper ran on CPU despite a GPU.

Add per-framework helpers to gpu.py:
- torch_gpu_available(): torch.cuda.is_available(), honoring SNAPOTTER_GPU.
- ctranslate2_gpu_available(): ctranslate2.get_cuda_device_count() > 0.

Point each tool at the helper for its own framework: upscale, noise_removal,
enhance_faces and restore use torch_gpu_available(); transcribe uses
ctranslate2_gpu_available(). ocr.py keeps gpu_available() (paddle-aware) and the
dispatcher keeps it for its startup GPU-status line. The SNAPOTTER_GPU override
check is factored into a shared _override_disables_gpu() helper.

TDD: 7 new tests in tests/test_gpu_detection.py cover both helpers (override,
CPU-only, absent framework), including the crux that torch_gpu_available() stays
False on a CPU-only torch build even when a GPU exists for another framework.

Claude-Session: https://claude.ai/code/session_01NfaRxjek8ex5nawvx3mVMf
2026-07-06 18:39:01 +08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
bc59114dcb chore(deps-dev): bump the dev-deps group with 9 updates (#444)
Bumps the dev-deps group with 9 updates:

| Package | From | To |
| --- | --- | --- |
| [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) | `2.5.1` | `2.5.2` |
| [@semantic-release/github](https://github.com/semantic-release/github) | `12.0.8` | `12.0.9` |
| [@testcontainers/postgresql](https://github.com/testcontainers/testcontainers-node) | `12.0.3` | `12.0.4` |
| [@testcontainers/redis](https://github.com/testcontainers/testcontainers-node) | `12.0.3` | `12.0.4` |
| [adm-zip](https://github.com/cthackers/adm-zip) | `0.5.17` | `0.5.18` |
| [turbo](https://github.com/vercel/turborepo) | `2.10.0` | `2.10.3` |
| [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite) | `4.3.1` | `4.3.2` |
| [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `4.3.1` | `4.3.2` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `8.0.16` | `8.1.3` |


Updates `@biomejs/biome` from 2.5.1 to 2.5.2
- [Release notes](https://github.com/biomejs/biome/releases)
- [Changelog](https://github.com/biomejs/biome/blob/main/packages/@biomejs/biome/CHANGELOG.md)
- [Commits](https://github.com/biomejs/biome/commits/@biomejs/biome@2.5.2/packages/@biomejs/biome)

Updates `@semantic-release/github` from 12.0.8 to 12.0.9
- [Release notes](https://github.com/semantic-release/github/releases)
- [Commits](https://github.com/semantic-release/github/compare/v12.0.8...v12.0.9)

Updates `@testcontainers/postgresql` from 12.0.3 to 12.0.4
- [Release notes](https://github.com/testcontainers/testcontainers-node/releases)
- [Commits](https://github.com/testcontainers/testcontainers-node/compare/v12.0.3...v12.0.4)

Updates `@testcontainers/redis` from 12.0.3 to 12.0.4
- [Release notes](https://github.com/testcontainers/testcontainers-node/releases)
- [Commits](https://github.com/testcontainers/testcontainers-node/compare/v12.0.3...v12.0.4)

Updates `adm-zip` from 0.5.17 to 0.5.18
- [Release notes](https://github.com/cthackers/adm-zip/releases)
- [Changelog](https://github.com/cthackers/adm-zip/blob/master/history.md)
- [Commits](https://github.com/cthackers/adm-zip/compare/v0.5.17...v0.5.18)

Updates `turbo` from 2.10.0 to 2.10.3
- [Release notes](https://github.com/vercel/turborepo/releases)
- [Changelog](https://github.com/vercel/turborepo/blob/main/RELEASE.md)
- [Commits](https://github.com/vercel/turborepo/compare/v2.10.0...v2.10.3)

Updates `@tailwindcss/vite` from 4.3.1 to 4.3.2
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.2/packages/@tailwindcss-vite)

Updates `tailwindcss` from 4.3.1 to 4.3.2
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.2/packages/tailwindcss)

Updates `vite` from 8.0.16 to 8.1.3
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.1.3/packages/vite)

---
updated-dependencies:
- dependency-name: "@biomejs/biome"
  dependency-version: 2.5.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-deps
- dependency-name: "@semantic-release/github"
  dependency-version: 12.0.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-deps
- dependency-name: "@testcontainers/postgresql"
  dependency-version: 12.0.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-deps
- dependency-name: "@testcontainers/redis"
  dependency-version: 12.0.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-deps
- dependency-name: adm-zip
  dependency-version: 0.5.18
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-deps
- dependency-name: turbo
  dependency-version: 2.10.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-deps
- dependency-name: "@tailwindcss/vite"
  dependency-version: 4.3.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-deps
- dependency-name: tailwindcss
  dependency-version: 4.3.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-deps
- dependency-name: vite
  dependency-version: 8.1.3
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-06 18:06:25 +08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
eb40e68274 chore(deps): bump the actions group with 8 updates (#442)
Bumps the actions group with 8 updates:

| Package | From | To |
| --- | --- | --- |
| [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) | `4.1.0` | `4.2.0` |
| [docker/login-action](https://github.com/docker/login-action) | `4.2.0` | `4.4.0` |
| [github/codeql-action/init](https://github.com/github/codeql-action) | `4.36.2` | `4.36.3` |
| [github/codeql-action/autobuild](https://github.com/github/codeql-action) | `4.36.2` | `4.36.3` |
| [github/codeql-action/analyze](https://github.com/github/codeql-action) | `4.36.2` | `4.36.3` |
| [docker/metadata-action](https://github.com/docker/metadata-action) | `6.1.0` | `6.2.0` |
| [docker/build-push-action](https://github.com/docker/build-push-action) | `7.2.0` | `7.3.0` |
| [github/codeql-action/upload-sarif](https://github.com/github/codeql-action) | `4.36.2` | `4.36.3` |


Updates `docker/setup-buildx-action` from 4.1.0 to 4.2.0
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5...bb05f3f5519dd87d3ba754cc423b652a5edd6d2c)

Updates `docker/login-action` from 4.2.0 to 4.4.0
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/650006c6eb7dba73a995cc03b0b2d7f5ca915bee...af1e73f918a031802d376d3c8bbc3fe56130a9b0)

Updates `github/codeql-action/init` from 4.36.2 to 4.36.3
- [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/8aad20d150bbac5944a9f9d289da16a4b0d87c1e...54f647b7e1bb85c95cddabcd46b0c578ec92bc1a)

Updates `github/codeql-action/autobuild` from 4.36.2 to 4.36.3
- [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/8aad20d150bbac5944a9f9d289da16a4b0d87c1e...54f647b7e1bb85c95cddabcd46b0c578ec92bc1a)

Updates `github/codeql-action/analyze` from 4.36.2 to 4.36.3
- [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/8aad20d150bbac5944a9f9d289da16a4b0d87c1e...54f647b7e1bb85c95cddabcd46b0c578ec92bc1a)

Updates `docker/metadata-action` from 6.1.0 to 6.2.0
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9...dc802804100637a589fabce1cb79ff13a1411302)

Updates `docker/build-push-action` from 7.2.0 to 7.3.0
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/f9f3042f7e2789586610d6e8b85c8f03e5195baf...53b7df96c91f9c12dcc8a07bcb9ccacbed38856a)

Updates `github/codeql-action/upload-sarif` from 4.36.2 to 4.36.3
- [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/8aad20d150bbac5944a9f9d289da16a4b0d87c1e...54f647b7e1bb85c95cddabcd46b0c578ec92bc1a)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: 4.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: docker/login-action
  dependency-version: 4.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: github/codeql-action/init
  dependency-version: 4.36.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: github/codeql-action/autobuild
  dependency-version: 4.36.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.36.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: docker/metadata-action
  dependency-version: 6.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: docker/build-push-action
  dependency-version: 7.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: github/codeql-action/upload-sarif
  dependency-version: 4.36.3
  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-07-06 18:06:00 +08:00
SnapOtterandGitHub 3aaaacc7a1 feat: pin frequently-used tools to the top of the dashboard (#440)
* feat(i18n): add pin/unpin/pinned strings, retire addToFavourites stub

Claude-Session: https://claude.ai/code/session_01Ad5LjCDJyW1tLFd3P4Hedp

* feat(web): add per-user pinned-tools store

Claude-Session: https://claude.ai/code/session_01Ad5LjCDJyW1tLFd3P4Hedp

* feat(web): add opt-in pin toggle to ToolCard

Claude-Session: https://claude.ai/code/session_01Ad5LjCDJyW1tLFd3P4Hedp

* feat(web): render Pinned section on the dashboard All tab

Claude-Session: https://claude.ai/code/session_01Ad5LjCDJyW1tLFd3P4Hedp

* test(web): cover pin toggle (component) and dashboard pin flow (e2e)

Claude-Session: https://claude.ai/code/session_01Ad5LjCDJyW1tLFd3P4Hedp
2026-07-06 17:59:30 +08:00
SnapOtter ec78d36d95 docs: merge api sync documentation branch 2026-07-06 17:55:30 +08:00
SnapOtter 6e9933446e docs: sync api documentation 2026-07-06 08:10:36 +08:00
SnapOtter b69c7362e1 docs: sync api documentation 2026-07-06 08:09:22 +08:00
SnapOtterandGitHub 7cd514dd8c fix(ai): detect a paddle-only GPU so OCR uses PaddleOCR-GPU not Tesseract (#439)
gpu_available() probed torch, then ONNX Runtime, then nvidia-smi, but never
paddle. The OCR bundle ships paddlepaddle-gpu with no torch or ONNX, so on an
OCR-only GPU host every probe missed the GPU: nvidia-smi saw it but returned
False by design, and OCR silently fell back to Tesseract (CPU, lower quality)
with no signal why.

Add a paddle probe as the last resort in gpu_available(). It runs only after
nvidia-smi confirms a GPU is physically present, and in an isolated subprocess,
because importing paddlepaddle-gpu on a GPU-less host segfaults and would wedge
the shared AI dispatcher. It returns True only when paddle reports both a CUDA
build and a visible device, signalling the result through the exit code so
paddle's own import chatter on stdout cannot corrupt the reading.

CPU-only and torch/ONNX GPU hosts are unaffected: the probe never runs on the
former (nvidia-smi finds nothing) and is never reached on the latter (the torch
step already returns True first).

Claude-Session: https://claude.ai/code/session_01NfaRxjek8ex5nawvx3mVMf
2026-07-05 22:57:36 +08:00
SnapOtterandGitHub 8574615ef5 docs(deployment): refresh hardware requirements from the 2.0 benchmark (#436)
Update the Hardware Requirements section with fresh six-machine benchmark data:
- Tiers corrected to the resource-sweep floor (2c/2G minimum; 512MB cannot start,
  1GB is single-file-only, batches need 2GB).
- Add the 64-bit-only architecture requirement (Pi 4/5 yes; 32-bit ARM and
  512MB boards no).
- Correct the AI-on-CPU viability (colorize/face-enhance are ~10s and usable, not
  "marginal to no") and add the AI RAM lever (~360MB idle without bundles vs
  ~2.6GB with all seven installed).
- Fix bogus GPU speedups (noise-removal/blur-faces are CPU-bound, ~1x, not
  13,400x/100x); real wins are upscale ~47x, face-enhance ~12x, transcribe ~4.5x,
  remove-bg ~4x; photo-restoration is CPU-bound even on a GPU.
- Call out video transcode as the one CPU-heavy tool; refresh concurrency numbers.

Claude-Session: https://claude.ai/code/session_01UvVCMNUBrgpghk8gye5gav
2026-07-05 20:19:24 +08:00
SnapOtterandGitHub fd39f66f46 fix(ai-bundles): lock the numpy-1.x ABI closure so the OCR bundle can't strand scipy (#437)
* fix(ai-bundles): lock the numpy-1.x ABI closure so the OCR bundle can't strand scipy

The OCR bundle installs paddleocr[doc-parser] 3.4, whose dependency closure drags
numpy 1.26.4 up to 2.5.1 and pulls scipy/scikit-learn/pandas wheels built against
the numpy 2.x ABI. build-bundle.sh re-pinned only numpy (basePackages), so those
numpy-2.x wheels stayed behind; the by-dir-name site-packages diff then shipped
them, and once merged onto the numpy==1.26.4 base they raise "numpy.dtype size
changed" on import.

Because the dispatcher pre-imports every ML library at startup and disables all AI
after 5 crashes in 60s, one stranded scipy takes down every AI tool, not just OCR
(observed on a CPU host: remove-background worked before the OCR bundle and broke
after). All-7 installs escaped it through last-writer-wins ordering; a subset
install did not, which is why it surfaced only intermittently.

Fix: add a manifest "constraints" list (numpy, scipy, scikit-learn, scikit-image,
pandas pinned to numpy-1.x-ABI versions) and apply it via PIP_CONSTRAINT to every
bundle pip install, so no bundle can pull a numpy-2.x wheel. paddleocr 3.4.1 still
resolves cleanly under the lock and the pinned stack imports without ABI error on
numpy 1.26.4 (validated on py3.12). Also import scipy/sklearn in the OCR path of
verify-bundle.sh so CI catches this class in isolation, and add a manifest
regression test.

Note: the published bundles must be rebuilt and republished (ai-bundles.yml) for
this to reach already-installed bases.

Claude-Session: https://claude.ai/code/session_01UvVCMNUBrgpghk8gye5gav

* chore(ai-bundles): sync OCR manifest sha256 to the rebuilt numpy-1.x bundles

Rebuilt the OCR bundle for both arches with the numpy-1.x-ABI constraints from
this PR and republished the tars to deepsafe/feature-bundles/v2.0.0, then updated
the baked manifest sha256 and sizes so installs verify against the fixed archives:

  amd64-gpu  5.93 GB  sha 2a00a3184f6a635f1fa9ae2a6517ad740a11f9e5ff58c098d2fd369a2bb1e16b
  arm64-cpu  1.98 GB  sha 6868c264069dcb74c6675c0b1f58dc1c9f60d9aa4459725e3dbde07a99a6a09a

Both tars ship scipy 1.12.0 / scikit-learn 1.4.2 / pandas 2.2.2 (numpy-1.x-ABI)
and zero numpy-2.x wheels, verified by listing the archive contents.

Stopgap note: these tars were built against the ghcr.io latest base (the 2.0.0
image is not published to GHCR), so they are not byte-identical to what the CI
build will produce. When ai-bundles.yml rebuilds at the 2.0.0 release, it will
mint fresh sha256 values and this manifest must be re-synced to them.

Claude-Session: https://claude.ai/code/session_01UvVCMNUBrgpghk8gye5gav
2026-07-05 11:52:27 +00:00
SnapOtter 48494afc1e chore(ai-bundles): sync OCR manifest sha256 to the rebuilt numpy-1.x bundles
Rebuilt the OCR bundle for both arches with the numpy-1.x-ABI constraints from
this PR and republished the tars to deepsafe/feature-bundles/v2.0.0, then updated
the baked manifest sha256 and sizes so installs verify against the fixed archives:

  amd64-gpu  5.93 GB  sha 2a00a3184f6a635f1fa9ae2a6517ad740a11f9e5ff58c098d2fd369a2bb1e16b
  arm64-cpu  1.98 GB  sha 6868c264069dcb74c6675c0b1f58dc1c9f60d9aa4459725e3dbde07a99a6a09a

Both tars ship scipy 1.12.0 / scikit-learn 1.4.2 / pandas 2.2.2 (numpy-1.x-ABI)
and zero numpy-2.x wheels, verified by listing the archive contents.

Stopgap note: these tars were built against the ghcr.io latest base (the 2.0.0
image is not published to GHCR), so they are not byte-identical to what the CI
build will produce. When ai-bundles.yml rebuilds at the 2.0.0 release, it will
mint fresh sha256 values and this manifest must be re-synced to them.

Claude-Session: https://claude.ai/code/session_01UvVCMNUBrgpghk8gye5gav
2026-07-05 19:27:15 +08:00
SnapOtter 67c55669d6 fix(ai-bundles): lock the numpy-1.x ABI closure so the OCR bundle can't strand scipy
The OCR bundle installs paddleocr[doc-parser] 3.4, whose dependency closure drags
numpy 1.26.4 up to 2.5.1 and pulls scipy/scikit-learn/pandas wheels built against
the numpy 2.x ABI. build-bundle.sh re-pinned only numpy (basePackages), so those
numpy-2.x wheels stayed behind; the by-dir-name site-packages diff then shipped
them, and once merged onto the numpy==1.26.4 base they raise "numpy.dtype size
changed" on import.

Because the dispatcher pre-imports every ML library at startup and disables all AI
after 5 crashes in 60s, one stranded scipy takes down every AI tool, not just OCR
(observed on a CPU host: remove-background worked before the OCR bundle and broke
after). All-7 installs escaped it through last-writer-wins ordering; a subset
install did not, which is why it surfaced only intermittently.

Fix: add a manifest "constraints" list (numpy, scipy, scikit-learn, scikit-image,
pandas pinned to numpy-1.x-ABI versions) and apply it via PIP_CONSTRAINT to every
bundle pip install, so no bundle can pull a numpy-2.x wheel. paddleocr 3.4.1 still
resolves cleanly under the lock and the pinned stack imports without ABI error on
numpy 1.26.4 (validated on py3.12). Also import scipy/sklearn in the OCR path of
verify-bundle.sh so CI catches this class in isolation, and add a manifest
regression test.

Note: the published bundles must be rebuilt and republished (ai-bundles.yml) for
this to reach already-installed bases.

Claude-Session: https://claude.ai/code/session_01UvVCMNUBrgpghk8gye5gav
2026-07-05 18:31:30 +08:00
SnapOtterandGitHub 47a60e7fad fix(files): record the source tool in toolChain on Save to Files (#435)
Save to Files posted only the blob, so userFiles.toolChain stayed null and the library showed "Tools Used: None". Thread the producing toolId through /api/v1/files/upload (validated optional field) and store it as a one-element toolChain, matching the pipeline path.

Claude-Session: https://claude.ai/code/session_01UvVCMNUBrgpghk8gye5gav
2026-07-05 16:05:24 +08:00
SnapOtterandGitHub cf884b52cd fix: offline CodeFormer face-enhance (ship RealESRGAN_x2plus in upscale-enhance bundle) (#433)
* fix: ship RealESRGAN_x2plus.pth in the upscale-enhance bundle for offline CodeFormer

codeformer-pip 0.0.4 downloads RealESRGAN_x2plus.pth at import of
codeformer.app, unconditionally, even though enhance_faces calls
inference_app with background_enhance=False and never uses the background
upsampler. The weight was not bundled, so explicit CodeFormer face-enhance
(enhance-faces model=codeformer) failed in strict offline mode
(SNAPOTTER_ALLOW_MODEL_DOWNLOAD=0) on a host that had never cached it -- the
guard raised before the import could complete.

Add RealESRGAN_x2plus.pth to the upscale-enhance bundle manifest (only that
bundle uses codeformer-pip; photo-restoration uses the CodeFormer ONNX path)
and link it in prepare_codeformer_weights alongside the other three weights,
replacing the download-or-error guard. Once the bundle ships it, the import
resolves offline and strict mode works.

Archive SHA256s updated in a follow-up once the bundle is rebuilt.

Claude-Session: https://claude.ai/code/session_01XGB4pGvTvb7sUX4JN745U7

* fix: require face-detection bundle for enhance-faces + point manifest at the x2plus archives

enhance-faces runs MediaPipe face detection (blaze_face_short_range.tflite)
before CodeFormer/GFPGAN. That model ships in the face-detection bundle, not
the tool's primary upscale-enhance bundle, so a standalone upscale-enhance
install failed face detection (offline: hard error; online: a surprise
download) before reaching the codeformer path. Declare the dependency in
TOOL_EXTRA_BUNDLES like passport-photo does.

Update the upscale-enhance archive SHA256/sizes to the rebuilt bundles that
include RealESRGAN_x2plus.pth (amd64-gpu + arm64-cpu), verified to install and
run enhance-faces model=codeformer in strict offline mode with zero downloads.

Claude-Session: https://claude.ai/code/session_01XGB4pGvTvb7sUX4JN745U7
2026-07-04 16:30:08 +00:00
SnapOtterandGitHub dadf766899 fix(migrator): correct and harden the 1.x to 2.0 SQLite import (#434)
* feat(api): parse DATA_DIR from env for 1.x import auto-detection

Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w

* test(migrator): build 1.17.2 fixtures by replaying legacy migrations

Discovered the legacy migrations seed a Default team (0005) and builtin roles
(0007), so the replayed fixture carries them. Seed uses a distinct custom team.

Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w

* fix(migrator): self-adjusting column copy, jobs.status map, drop sessions, advisory lock

The importer now inserts only the intersection of source and live target columns,
so the three analytics_* columns 2.x dropped no longer break the first users INSERT
(and future dropped columns are handled generically). jobs.status is mapped onto the
2.x enum (error->failed). Sessions are no longer migrated. A pg_advisory_xact_lock
serializes concurrent replicas. Includes login-after-migrate and library assertions.

Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w

* test(migrator): CI drift guard fails when a required column is unfillable from 1.17.2

Introspects every NOT-NULL-no-default column of each migrated table in the current
schema and asserts the engine can fill it from a real 1.17.2 source. Turns a future
breaking schema change into a PR-time failure instead of a production import break.

Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w

* feat(migrator): orchestrator with detection, boot states, marker, blob count

sqlite-import.ts owns source resolution (explicit path, 'off' sentinel, DATA_DIR
probe), the four boot states (import/leftover/locked/none), the persisted
sqlite_import marker, and a read-only library-blob count. runBootImport wires them
together and catches TargetNonEmptyError as a benign multi-replica skip.

Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w

* feat(api): route boot through the 1.x import orchestrator; hide marker from non-admins

index.ts now calls runBootImport (which owns detection + the four boot states)
instead of the inline SQLITE_MIGRATE_PATH block. The sqlite_import marker is added
to SENSITIVE_KEYS (but not REDACTED_KEYS) so admins see the counts for the banner
while non-admins don't see the key at all.

Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w

* feat(migrator): add analyzeSqlite + dry-run/verify CLI

analyzeSqlite is a read-only pre-flight (no live Postgres): per-table row counts,
library-blob presence, and out-of-enum job statuses. The migrate:sqlite CLI now
lives in the orchestrator and supports --dry-run/--verify (prints the analysis and
exits without writing) alongside the existing import and --force.

Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w

* docs: add 1.x to 2.0 upgrade guide; fix volume-name casing

New apps/docs upgrade guide covering auto-detect, the SQLITE_MIGRATE_PATH override +
off opt-out, the dry-run, what carries over, locked-state recovery, and non-destructive
rollback. Leads with 'back up the WHOLE /data volume, not just snapotter.db' because
1.x WAL mode leaves data in snapotter.db-wal (surfaced by the real-image upgrade test).
Standardizes README/DOCKERHUB compose volume names on the canonical SnapOtter-data
casing so they match the repo compose and don't orphan an upgrader's volume.

Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w

* feat(web): admin 1.x migration banner + 21-locale strings

A one-time admin banner reads the sqlite_import marker from /v1/settings and shows
the import result (user + saved-file counts) on success, or a warning when a 1.x
database was found but not imported. Dismissal persists to a sqlite_import.dismissedAt
settings key. shouldShowMigrationBanner/parseMigrationMarker sit in feedback.ts with
the other shouldShow helpers; strings added to en.ts and all 20 other locales.

Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w

* style(landing): biome-format Hero.astro trustBadges array

Pre-existing formatting drift on main (its Lint check was skipped on the merge that
introduced it); this PR's full Lint run surfaced it. Formatting-only, applied via
the repo's own biome formatter to unblock the required Lint check.

Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w
2026-07-04 15:15:39 +00:00
SnapOtterandGitHub dc589fd0a1 docs(readme): drop Otter.ai from the alternatives list (#432)
Otter.ai is a transcription-only service and reads as off-topic next to
the file-conversion tools in the comparison line. Remove it and fix the
list conjunction.

Claude-Session: https://claude.ai/code/session_01EhgRGhvhNGJVQHcdFdcNMa
2026-07-04 21:00:55 +08:00
SnapOtterandGitHub 8451f9be08 feat(landing): refocus hero on privacy-sensitive teams (#431)
Retitle the homepage hero to "File processing for privacy-sensitive
teams", drop the redundant infrastructure subline, and trim the trust
badges to the four foundational signals (self-hosted, open source,
air-gap capable, compliance-friendly).

Align the homepage title and meta description to the new positioning,
overriding the site-wide defaults for the homepage only.

Claude-Session: https://claude.ai/code/session_01EhgRGhvhNGJVQHcdFdcNMa
2026-07-04 20:52:59 +08:00
SnapOtterandGitHub 8cdd85a493 feat(feedback): route failed-run Report issue through the offline handoff (#429)
Apply the always-on handoff to the failed-run Report issue button too. Un-gate the two buttons in tool-page.tsx from the analytics toggle, and extend the dialog offline handoff to source=failed_job, prefilling the GitHub issue with the tool id and error category so it is actionable even with an empty message. Follows #428.

Claude-Session: https://claude.ai/code/session_01XVrHKXwzZDWBWgkGQdPZ3A
2026-07-04 18:06:16 +08:00
SnapOtterandGitHub e0dbf2a5c3 fix: set a writable HOME for the app user so PaddleOCR works in non-root deployments (#430)
The container dropped privileges to the non-root snapotter user via gosu
(external) and s6-setuidgid (embedded), both of which preserve the
environment without setting HOME. The app therefore kept root's HOME=/root,
which is not writable by snapotter, and PaddleOCR died with
PermissionError: '/root/.paddlex/temp' -- breaking the ocr tool at default
quality in every non-root deployment. Prior GPU QA ran the app as root, which
masked it.

Fix: export HOME=/data/.home (persistent, writable, hidden) at every
privilege-drop point:
- entrypoint.sh external gosu path and non-root tini path (the latter uses
  $DD/.home so a DATA_DIR override stays consistent).
- the s6 snapotter/run service (scoped there, not globally before /init, so
  postgres/redis do not inherit a snapotter-owned HOME).
The root preflight creates /data/.home and the existing chown sweep owns it as
the PUID/PGID-remapped snapotter; the dir is added to both ensure_writable
probes so an unwritable HOME fails fast with the storage-permission guidance
instead of crashing late. The Dockerfile passwd home moves from /app
(read-only) to /data/.home as the getpwuid fallback when HOME is unset.

Because bridge.ts forwards HOME to the Python sidecar, this also repairs the
expanduser("~") caches in inpaint/outpaint/restore/noise_removal/remove_bg,
not just PaddleOCR.

Also fixes a test-harness inconsistency: tool-default-settings passport-photo
countryCode "us" -> "US" (the route exact-matches uppercase PASSPORT_SPECS
codes; the UI already sends "US", so users were never affected).

Claude-Session: https://claude.ai/code/session_01XGB4pGvTvb7sUX4JN745U7
2026-07-04 09:40:04 +00:00
SnapOtterandGitHub 5dcc06a99e feat(feedback): always-on nav button with GitHub/email handoff when analytics is off (#428)
Keep the top-nav feedback button always visible (icon plus label on desktop, icon-only on mobile) instead of hiding it when an instance opts out of analytics. When analytics is off, the dialog keeps the typed message and hands off to a prefilled GitHub issue plus a contact@snapotter.com email, with no fake Thanks. Adds a feedback.yml issue template, URL builders, and feedback strings across all 21 locales.

Claude-Session: https://claude.ai/code/session_01XVrHKXwzZDWBWgkGQdPZ3A
2026-07-04 17:37:12 +08:00
SnapOtterandGitHub 7b04317ed2 feat: add a Keep it free sponsor button to the top nav (#427)
Adds a prominent Keep it free sponsor button to the top nav, linking to https://github.com/sponsors/snapotter-hq. Solid orange pill on desktop (left of the avatar), orange heart icon on mobile. Opens in a new tab with rel=noopener noreferrer, so no referrer or user data leaks, and it adds no passive network activity (offline-mode compatible). Fires an opt-in, property-less sponsor_clicked analytics event. Adds sidebar.sponsor and a11y.sponsorLink across all 21 locales.

Claude-Session: https://claude.ai/code/session_01DnYLLA5z4Uf1GDeEPENVgr
2026-07-04 08:48:16 +00:00
3ae48cc76a fix(i18n): complete the Italian feedback translations (#426)
Lands albanobattistella's Italian translation of the feedback strings from #425, with two mistyped keys corrected (great and adminCardDescription). Verified against main: typecheck 0 errors, i18n parity 23/23, Biome clean.

Co-authored-by: albanobattistella <34811668+albanobattistella@users.noreply.github.com>
2026-07-04 16:11:06 +08:00
SnapOtterandGitHub 23efce9df0 fix(analytics): harden analytics opt-out and feedback surfaces (#423)
Server stops phoning Sentry home after opt-out (release-health sessions + client reports off); settings saves diff-send only changed keys so a stale tab cannot revert an instance-wide opt-out; disabling analytics hides the feedback UI immediately; optIn resumes PostHog after re-enable; onboarding survey writes time out at 15s; inline tool-feedback prompt arms a shown-cooldown.
2026-07-04 14:02:28 +08:00
SnapOtterandGitHub 6e3a14ec6b fix: remove automatic third-party egress of user data + optional strict offline mode (OSM tiles, Scalar fonts, editor fonts, AI model downloads) (#422)
* fix: remove all automatic third-party egress (OSM tiles, Scalar fonts, editor Google Fonts, AI model download fallbacks)

Phone-home audit follow-up. The product no longer makes any automatic
third-party request; user-initiated click-outs stay, and production now
fails closed on missing AI models.

1. GPS leak via OSM tiles: the strip-metadata panel auto-loaded
   tile.openstreetmap.org tiles encoding the photo's GPS position. The
   Leaflet mini-map is gone; coordinates render as text plus an explicit
   View on map link (openstreetmap.org, opens on click only). Removed
   tile.openstreetmap.org from the CSP img-src, dropped the leaflet
   dependency, added the viewOnMap i18n key to all 21 locales.

2. Scalar docs fonts: /api/docs loaded Inter and JetBrains Mono from
   fonts.scalar.com. Scalar now renders with withDefaultFonts: false and
   both --scalar-font and --scalar-font-code pinned to system stacks;
   fonts.scalar.com removed from the docs CSP font-src. Verified by
   injecting GET /api/docs/: config carries withDefaultFonts false and
   the served page has no fonts.scalar.com reference.

3. Editor Google Fonts: the editor font picker built
   fonts.googleapis.com stylesheet URLs for 25 web fonts the served CSP
   already blocked. The remote loading path is deleted; the picker now
   offers system fonts only, with a SELF_HOSTED_FONTS seam (FontFace API,
   same origin) for bundling fonts later. Unknown families saved in old
   documents fall back to the browser default.

4. Python sidecar fails closed on model downloads: new
   packages/ai/python/offline_guard.py gates every runtime download
   fallback (inpaint, outpaint, restore, noise_removal, detect_faces,
   enhance_faces, face_landmarks, red_eye_removal, remove_bg, ocr,
   transcribe, upscale) behind SNAPOTTER_ALLOW_MODEL_DOWNLOAD=1 with an
   actionable error. Bundled models keep working untouched.

5. OCR and transcription library-internal downloads: unbundled PaddleOCR
   language and detection fallbacks now raise the guard error naming the
   language instead of resolving models over the network; faster-whisper
   gets local_files_only when downloads are off.

6. GFPGAN and CodeFormer cwd-relative weights: facexlib and
   codeformer-pip resolve helper weights relative to the process cwd and
   fetch them from GitHub when absent. They are now symlinked from the
   installed bundle files under MODELS_PATH/gfpgan/facelib before the
   libraries load, failing closed when unresolvable.

Defense in depth: HF_HUB_OFFLINE=1 and TRANSFORMERS_OFFLINE=1 are set in
the runtime image and in the sidecar spawn env; install_feature.py lifts
them for user-initiated bundle installs and restores them afterwards
(it can run in-process inside the dispatcher). SNAPOTTER_ALLOW_MODEL_DOWNLOAD
is documented in .env.example, default off.

Validation: typecheck 9/9 workspaces, Biome clean on touched files,
5178 unit tests pass, py_compile on all touched scripts, guard behavior
exercised in both dispatcher exec and per-request import modes, zero
remaining runtime references to the three hosts. Docker build and live
AI inference need post-merge verification on the GPU host.

Claude-Session: https://claude.ai/code/session_01XGB4pGvTvb7sUX4JN745U7

* fix: allow AI model downloads by default, make strict offline mode opt-in

Product call: ease of use first. The download gating from the previous
commit inverts its default: runtime model fetches (public model weights
only, never user data) are allowed out of the box so AI tools self-heal,
and SNAPOTTER_ALLOW_MODEL_DOWNLOAD=0 becomes the explicit strict offline
mode for airgapped deployments, where every fallback raises the
actionable error instead of fetching.

Changes: offline_guard blocks only on an explicit 0/false; the
unconditional HF_HUB_OFFLINE/TRANSFORMERS_OFFLINE image ENV is removed
and bridge.ts sets those flags for the sidecar only in strict mode;
.env.example documents the new default; install_feature's lift/restore
stays. All bundled-path preferences, pre-existence checks, and symlink
pre-placement remain, so installed bundles never trigger a download.
The OSM, Scalar font, and editor font fixes are unchanged.

Validation rerun: typecheck 9/9, Biome clean on touched files, 5178
unit tests pass, py_compile on touched scripts, guard behavior verified
for unset/1 (allowed) and 0/false (blocked with the new message).

Claude-Session: https://claude.ai/code/session_01XGB4pGvTvb7sUX4JN745U7
2026-07-04 05:46:52 +00:00
SnapOtter 7b6765030b ci: gate demo deploy 2026-07-04 13:45:47 +08:00
SnapOtter f6f7b5a4bc fix: sync demo theme with app 2026-07-04 13:22:33 +08:00
SnapOtterandGitHub 7e6939bf0d docs: show "200+" for the landing tool-count stat (#420)
Match the drift-proof public phrasing used elsewhere (the Languages
stat already reads "20+"). Avoids the below-hero number going stale as
tools are added.

Claude-Session: https://claude.ai/code/session_01JQ8LmV8LPLi8yNTayHzSTQ
2026-07-04 13:17:25 +08:00
SnapOtter 7473d2d0fc ci: deploy demo automatically 2026-07-04 13:08:24 +08:00