# [2.2.0](https://github.com/snapotter-hq/snapotter/compare/v2.1.0...v2.2.0) (2026-07-29) ### Bug Fixes * **a11y:** focus indicators meet the 3:1 non-text contrast bar ([#574](https://github.com/snapotter-hq/snapotter/issues/574)) ([6339370](https://github.com/snapotter-hq/snapotter/commit/63393700939e34ad3d847043b03728e3c2907445)), closes [#A85518](https://github.com/snapotter-hq/snapotter/issues/A85518) [#F0A766](https://github.com/snapotter-hq/snapotter/issues/F0A766) * **a11y:** WCAG AA contrast retune for the Otter Orange palette ([#567](https://github.com/snapotter-hq/snapotter/issues/567)) ([5102262](https://github.com/snapotter-hq/snapotter/commit/51022628dc19d8a03525bab98473f3c23b1a2709)), closes [#E07832](https://github.com/snapotter-hq/snapotter/issues/E07832) [#1A1814](https://github.com/snapotter-hq/snapotter/issues/1A1814) * **ai-bridge:** surface sidecar exit reasons in Sentry via SafeError ([#538](https://github.com/snapotter-hq/snapotter/issues/538)) ([55e1e95](https://github.com/snapotter-hq/snapotter/commit/55e1e95f20792cae4f1f2257bafe839b83332c93)) * **ai-bundles:** stop CPU onnxruntime from clobbering onnxruntime-gpu ([#544](https://github.com/snapotter-hq/snapotter/issues/544)) ([c8629c9](https://github.com/snapotter-hq/snapotter/commit/c8629c9d22b455b6deac206e3816ae0f730f06c6)), closes [#490](https://github.com/snapotter-hq/snapotter/issues/490) * **ai:** advance the progress bar during upscale and background removal ([#608](https://github.com/snapotter-hq/snapotter/issues/608)) ([e56edc6](https://github.com/snapotter-hq/snapotter/commit/e56edc659f742430798104b52a08806a8d8fbff2)), closes [#591](https://github.com/snapotter-hq/snapotter/issues/591) * **ai:** warn that upscale and background removal are slow without a GPU ([#605](https://github.com/snapotter-hq/snapotter/issues/605)) ([d43208b](https://github.com/snapotter-hq/snapotter/commit/d43208b85e1ba1f0ac687970b8fb52ca1738ae15)), closes [#591](https://github.com/snapotter-hq/snapotter/issues/591) * **api:** contain library stored-name path traversal ([#600](https://github.com/snapotter-hq/snapotter/issues/600)) ([4333432](https://github.com/snapotter-hq/snapotter/commit/43334324c446c53eb1dc2f8ef14cbecca4e2a676)) * **api:** enforce job ownership on cancel endpoint ([#599](https://github.com/snapotter-hq/snapotter/issues/599)) ([577d74b](https://github.com/snapotter-hq/snapotter/commit/577d74bdb134d9a7d02c27ba976731839d071e63)) * **api:** gate every tool endpoint and stop ZIP streams failing quietly ([#646](https://github.com/snapotter-hq/snapotter/issues/646)) ([2d8b57c](https://github.com/snapotter-hq/snapotter/commit/2d8b57c57fad4e1d98ab7eb327198809aa64bd09)), closes [#645](https://github.com/snapotter-hq/snapotter/issues/645) [#643](https://github.com/snapotter-hq/snapotter/issues/643) [#645](https://github.com/snapotter-hq/snapotter/issues/645) * **api:** wait for Postgres and Redis at startup instead of crash-looping ([#537](https://github.com/snapotter-hq/snapotter/issues/537)) ([4ac89fe](https://github.com/snapotter-hq/snapotter/commit/4ac89fe6505e50645174e8de5fcdc40c46420cc4)) * **audio:** expose sample rate setting in Convert Audio ([#561](https://github.com/snapotter-hq/snapotter/issues/561)) ([d4eaa65](https://github.com/snapotter-hq/snapotter/commit/d4eaa655b24236da5633006871415afbf0c92d08)), closes [#558](https://github.com/snapotter-hq/snapotter/issues/558) * **auth:** close the MFA policy lockout and add self-service enrollment ([#531](https://github.com/snapotter-hq/snapotter/issues/531)) ([190d4c2](https://github.com/snapotter-hq/snapotter/commit/190d4c2a002c6d3d240ff18480e37217fcbeae12)), closes [#529](https://github.com/snapotter-hq/snapotter/issues/529) [#515](https://github.com/snapotter-hq/snapotter/issues/515) * **auth:** give OIDC/SAML logins a real MFA challenge instead of a hard block ([#536](https://github.com/snapotter-hq/snapotter/issues/536)) ([bbfcbe9](https://github.com/snapotter-hq/snapotter/commit/bbfcbe9c8212afb244bb2738a6e3643d06dcee68)), closes [#533](https://github.com/snapotter-hq/snapotter/issues/533) * **background-removal:** surface failures with a diagnosable SafeError ([#535](https://github.com/snapotter-hq/snapotter/issues/535)) ([43ddf96](https://github.com/snapotter-hq/snapotter/commit/43ddf96ccf7baf94416050ec6229efca68124679)) * **ci:** repair the chronically-failing nightly workflow ([#624](https://github.com/snapotter-hq/snapotter/issues/624)) ([44f5aea](https://github.com/snapotter-hq/snapotter/commit/44f5aea3268f022641832af4b8c60cbd2d5e2c7e)) * **compress-pdf:** land close to the target size, honestly ([#522](https://github.com/snapotter-hq/snapotter/issues/522)) ([7d938af](https://github.com/snapotter-hq/snapotter/commit/7d938af1f9e601238fd49af4c2cd157d16616266)) * **deps:** patch 4 HIGH/CRITICAL transitive CVEs (fast-uri, svgo, sharp, tar) ([#619](https://github.com/snapotter-hq/snapotter/issues/619)) ([10a2aab](https://github.com/snapotter-hq/snapotter/commit/10a2aabe589b5afd470f6c47ccdb1bf8bca15c02)) * **doc-engine:** guard sidecar JSON parsing against non-JSON stdout ([#532](https://github.com/snapotter-hq/snapotter/issues/532)) ([a2cb1a8](https://github.com/snapotter-hq/snapotter/commit/a2cb1a8261b2669ae7e656a418999b70a37f6b4f)) * **docker:** harden Postgres readiness checks in compose and startup ([#595](https://github.com/snapotter-hq/snapotter/issues/595)) ([4ba7503](https://github.com/snapotter-hq/snapotter/commit/4ba7503f15a4444c4937576dbcadafdb19a8848d)), closes [#592](https://github.com/snapotter-hq/snapotter/issues/592) * **docs:** keep the nav within the viewport on tablets and laptops ([#570](https://github.com/snapotter-hq/snapotter/issues/570)) ([69f72a6](https://github.com/snapotter-hq/snapotter/commit/69f72a6c712512e8489b23bcf78a233d872d897c)), closes [#556](https://github.com/snapotter-hq/snapotter/issues/556) * **docs:** keep the translated locale trees out of the search index ([#662](https://github.com/snapotter-hq/snapotter/issues/662)) ([42e1dc9](https://github.com/snapotter-hq/snapotter/commit/42e1dc9799f60c7dde46b9485e5f37a5a29630ae)) * **docs:** localize the docs homepage, nav, and sidebar chrome across 20 languages ([#547](https://github.com/snapotter-hq/snapotter/issues/547)) ([9247947](https://github.com/snapotter-hq/snapotter/commit/92479477042740d7026de9b9bb3c71eeb0668231)) * **download:** ask reverse proxies not to buffer file downloads ([#604](https://github.com/snapotter-hq/snapotter/issues/604)) ([89d7585](https://github.com/snapotter-hq/snapotter/commit/89d75853f45c98d6e9cae661dbda02bc8285009b)), closes [#590](https://github.com/snapotter-hq/snapotter/issues/590) * **download:** reset the socket when a stream is shorter than Content-Length ([#617](https://github.com/snapotter-hq/snapotter/issues/617)) ([0467e87](https://github.com/snapotter-hq/snapotter/commit/0467e87bfe410b313fa21d9758f8c162b12113a6)), closes [#590](https://github.com/snapotter-hq/snapotter/issues/590) [#590](https://github.com/snapotter-hq/snapotter/issues/590) [#590](https://github.com/snapotter-hq/snapotter/issues/590) * **editor:** repair rotate/flip/resize, levels/curves, filters, and layer lock ([#597](https://github.com/snapotter-hq/snapotter/issues/597)) ([a6bce68](https://github.com/snapotter-hq/snapotter/commit/a6bce6825ae27890951becf7c858d4626c513714)) * enforce role authority for user management ([#616](https://github.com/snapotter-hq/snapotter/issues/616)) ([1f8a42e](https://github.com/snapotter-hq/snapotter/commit/1f8a42e54822142132270df1aefa768f4626613f)) * enforce settings authority boundaries ([#618](https://github.com/snapotter-hq/snapotter/issues/618)) ([44d8109](https://github.com/snapotter-hq/snapotter/commit/44d8109486aa6efd9383e573f0a36deb4ffda460)) * **erase-object:** crop-based HD inpainting to remove ghosting and blur ([#501](https://github.com/snapotter-hq/snapotter/issues/501)) ([380419d](https://github.com/snapotter-hq/snapotter/commit/380419dd0603693ad542082bd6197e6505f9b1e9)), closes [#491](https://github.com/snapotter-hq/snapotter/issues/491) [hi#res](https://github.com/hi/issues/res) [#141](https://github.com/snapotter-hq/snapotter/issues/141) * **files:** decode CLI-decoded formats before URL-import preview generation ([#637](https://github.com/snapotter-hq/snapotter/issues/637)) ([841f47f](https://github.com/snapotter-hq/snapotter/commit/841f47f6ca25e79dc56dc1e735afa37f02023f99)), closes [#634](https://github.com/snapotter-hq/snapotter/issues/634) * **files:** store null instead of 0x0 for undecoded upload dimensions ([#636](https://github.com/snapotter-hq/snapotter/issues/636)) ([511633f](https://github.com/snapotter-hq/snapotter/commit/511633fa1c76f00fe9c4e2a0935a02b5601cae6a)), closes [#635](https://github.com/snapotter-hq/snapotter/issues/635) * give remove-background job timeouts an actionable failure message ([#518](https://github.com/snapotter-hq/snapotter/issues/518)) ([58121f2](https://github.com/snapotter-hq/snapotter/commit/58121f205f7ae2b34b3d789c80db37c8bf0950bf)), closes [#494](https://github.com/snapotter-hq/snapotter/issues/494) * **help:** render the help dialog from i18n instead of hardcoded English ([#647](https://github.com/snapotter-hq/snapotter/issues/647)) ([2848dd0](https://github.com/snapotter-hq/snapotter/commit/2848dd0e53d8a7c1e19ee82c1a1f2d485380d356)), closes [#644](https://github.com/snapotter-hq/snapotter/issues/644) * honor unlimited processing timeout ([#638](https://github.com/snapotter-hq/snapotter/issues/638)) ([025851b](https://github.com/snapotter-hq/snapotter/commit/025851beefaac6f8d12c1dbcdb69276965072ac9)) * **image-tools:** surface Sharp encode failures instead of "Error: Error" ([#534](https://github.com/snapotter-hq/snapotter/issues/534)) ([9cccbc9](https://github.com/snapotter-hq/snapotter/commit/9cccbc957681f8d12547e87be0e335f98e789cb7)) * **image:** decode real iPhone HEIC files instead of rejecting them at validation ([#631](https://github.com/snapotter-hq/snapotter/issues/631)) ([098ed50](https://github.com/snapotter-hq/snapotter/commit/098ed50d0693ba181fae6f934bf427079e1f8e9f)), closes [#622](https://github.com/snapotter-hq/snapotter/issues/622) * **image:** image-to-pdf presets no longer 404 on 2+ files ([#633](https://github.com/snapotter-hq/snapotter/issues/633)) ([330cf55](https://github.com/snapotter-hq/snapotter/commit/330cf559e00b6e5f1ca8df199cabee4c0fa650d0)), closes [#627](https://github.com/snapotter-hq/snapotter/issues/627) * **jobs:** classify BullMQ stall (UnrecoverableError) as operational ([#610](https://github.com/snapotter-hq/snapotter/issues/610)) ([e537cb0](https://github.com/snapotter-hq/snapotter/commit/e537cb04013d7bf4fd0fb125fb72f42f5a51917f)) * **jobs:** make timeout messages tool-agnostic and CPU-aware ([#596](https://github.com/snapotter-hq/snapotter/issues/596)) ([37c915c](https://github.com/snapotter-hq/snapotter/commit/37c915c3da6b2183b0befe1521a643bb36edcce6)), closes [#591](https://github.com/snapotter-hq/snapotter/issues/591) * **landing-i18n:** commit missed landing-seo re-translation (remove.bg comparison) ([b8c3700](https://github.com/snapotter-hq/snapotter/commit/b8c3700c15333b2eb328cab0979f8d943c15d7ab)) * **landing,docs:** use build-time GitHub star count, drop per-page api.github.com fetch ([#560](https://github.com/snapotter-hq/snapotter/issues/560)) ([54073a7](https://github.com/snapotter-hq/snapotter/commit/54073a7c501788dea49f77f6e94bdebb9b8f0f65)) * **landing:** derive conversion-preset copy from real format differences ([#663](https://github.com/snapotter-hq/snapotter/issues/663)) ([a75a22d](https://github.com/snapotter-hq/snapotter/commit/a75a22dd3ff8e675002a13da0ec12d00f91136c8)) * **landing:** keep mixed-case locale casing in emitted URLs ([#562](https://github.com/snapotter-hq/snapotter/issues/562)) ([67f5434](https://github.com/snapotter-hq/snapotter/commit/67f54347b2ca711f40495fa7fcb05e132ea8f3da)), closes [#554](https://github.com/snapotter-hq/snapotter/issues/554) * **landing:** link English-only tool-detail and self-hosted pages to un-prefixed URLs ([#553](https://github.com/snapotter-hq/snapotter/issues/553)) ([6ecc598](https://github.com/snapotter-hq/snapotter/commit/6ecc598fc4fc8fa6da5db9d9d0c66e53d2f2376d)) * **landing:** open Product dropdown below the navbar bottom line ([#571](https://github.com/snapotter-hq/snapotter/issues/571)) ([e3a01b6](https://github.com/snapotter-hq/snapotter/commit/e3a01b6be6fa077fa00c6c7245be7ba18b6e913c)) * **landing:** rework footer badge/switcher layout ([#514](https://github.com/snapotter-hq/snapotter/issues/514)) ([74e7d23](https://github.com/snapotter-hq/snapotter/commit/74e7d23abad924f04b348f00edf34e67188c4f2e)) * **landing:** stop mangling #hash fragments in localized links ([#516](https://github.com/snapotter-hq/snapotter/issues/516)) ([5bd0522](https://github.com/snapotter-hq/snapotter/commit/5bd052286c5a7920161b21cb791cc122fe3a1616)), closes [#hash](https://github.com/snapotter-hq/snapotter/issues/hash) * make OCR portable and reliable across AMD64 and ARM64 ([#519](https://github.com/snapotter-hq/snapotter/issues/519)) ([991c981](https://github.com/snapotter-hq/snapotter/commit/991c981529c81ebb719199aba084db4c2d9a7c6f)) * **ocr:** unblock and harden accurate-OCR install ([#552](https://github.com/snapotter-hq/snapotter/issues/552)) ([bda4db3](https://github.com/snapotter-hq/snapotter/commit/bda4db3f358eebd659a1f8b7892112061a82b000)) * **pdf:** flag scanned PDFs in pdf-to-text and serve text as UTF-8 ([#603](https://github.com/snapotter-hq/snapotter/issues/603)) ([7d37f6e](https://github.com/snapotter-hq/snapotter/commit/7d37f6e6f5aa68aa5954182d76bb6ccf1c6b0649)), closes [#589](https://github.com/snapotter-hq/snapotter/issues/589) * **pdf:** pdf-to-image presets no longer 404 on 2+ files ([#643](https://github.com/snapotter-hq/snapotter/issues/643)) ([a713795](https://github.com/snapotter-hq/snapotter/commit/a7137958a158309ff8128490de96313e517ae554)), closes [#627](https://github.com/snapotter-hq/snapotter/issues/627) [#633](https://github.com/snapotter-hq/snapotter/issues/633) [#632](https://github.com/snapotter-hq/snapotter/issues/632) * **pdf:** restore downloads on PDF conversion preset pages ([#629](https://github.com/snapotter-hq/snapotter/issues/629)) ([e0a7aec](https://github.com/snapotter-hq/snapotter/commit/e0a7aecde8f194b6375747a5a5f8ea4c57053813)), closes [#623](https://github.com/snapotter-hq/snapotter/issues/623) * **pdf:** stop page tools failing on short and encrypted PDFs ([#594](https://github.com/snapotter-hq/snapotter/issues/594)) ([73df107](https://github.com/snapotter-hq/snapotter/commit/73df1077580904c37d159dafe51a294b250bb074)) * preserve colored blocks in PDF-to-Word ([#500](https://github.com/snapotter-hq/snapotter/issues/500)) ([e7cfc00](https://github.com/snapotter-hq/snapotter/commit/e7cfc00fe1d294256a0a4663797b8a8cdf58d8c0)) * **readme:** self-host the star history chart ([#625](https://github.com/snapotter-hq/snapotter/issues/625)) ([90483c9](https://github.com/snapotter-hq/snapotter/commit/90483c944a4d4c912d3c5e2c09b2ee61bed3d150)) * release QA hardening across processing, media, security, and CI gates ([#649](https://github.com/snapotter-hq/snapotter/issues/649)) ([d10d0f5](https://github.com/snapotter-hq/snapotter/commit/d10d0f544f2a093d2adb13cbd69d54a31c0cbf7a)) * **release:** resolve the release by id, and make the vulnerability gate cover HIGH ([#661](https://github.com/snapotter-hq/snapotter/issues/661)) ([935861b](https://github.com/snapotter-hq/snapotter/commit/935861bced95cd778ea77d5a3741ff1da1a2ffed)), closes [#649](https://github.com/snapotter-hq/snapotter/issues/649) * **security:** bump Pillow to 12.3.0, fixes 5 disclosed CVEs ([#517](https://github.com/snapotter-hq/snapotter/issues/517)) ([30d36d7](https://github.com/snapotter-hq/snapotter/commit/30d36d7949817751b869916775138dc4fa7a34d2)) * **security:** close the gaps a full 2.0 re-audit left open ([#620](https://github.com/snapotter-hq/snapotter/issues/620)) ([079fcd2](https://github.com/snapotter-hq/snapotter/commit/079fcd2631216147db4b68acbcaca58ac4f93fb4)) * **seo:** emit clean docs URLs and de-index the demo ([#598](https://github.com/snapotter-hq/snapotter/issues/598)) ([e6718ad](https://github.com/snapotter-hq/snapotter/commit/e6718ada719e8931f643e1184aee354d4698c1da)) * **settings:** let admins relax the minimum password length to 1 ([#543](https://github.com/snapotter-hq/snapotter/issues/543)) ([846044a](https://github.com/snapotter-hq/snapotter/commit/846044a463f18f490f7752b0cb6b4b225b107367)), closes [#136](https://github.com/snapotter-hq/snapotter/issues/136) * **sign-pdf:** surface PDF load failures instead of a blank canvas ([#545](https://github.com/snapotter-hq/snapotter/issues/545)) ([1f4878a](https://github.com/snapotter-hq/snapotter/commit/1f4878ac4df417331a8cff9b8d5ab4da2edc788a)) * **telemetry:** classify environmental database errors as operational ([#540](https://github.com/snapotter-hq/snapotter/issues/540)) ([631d82e](https://github.com/snapotter-hq/snapotter/commit/631d82eaae36a15d401c7408182d52a0754b1e36)) * **telemetry:** data-quality pass (opt-in noise, onboarding split, file_count, OIDC) ([#614](https://github.com/snapotter-hq/snapotter/issues/614)) ([b20bca3](https://github.com/snapotter-hq/snapotter/commit/b20bca3c3c4ef3a63aca6ba420986d49bbeb905b)), closes [hi#level](https://github.com/hi/issues/level) * **telemetry:** fingerprint stackless uncaught errors so they stop collapsing ([#611](https://github.com/snapotter-hq/snapotter/issues/611)) ([82f5708](https://github.com/snapotter-hq/snapotter/commit/82f5708193984e863b95319e0e0ec8be47e46075)) * **telemetry:** sharpen Sentry signal for v2.1.0 residual defects ([#498](https://github.com/snapotter-hq/snapotter/issues/498)) ([b457596](https://github.com/snapotter-hq/snapotter/commit/b4575966495c1a022154149832aedcd727d411e2)) * **telemetry:** surface AI sidecar and DOMException failure reasons in Sentry ([#612](https://github.com/snapotter-hq/snapotter/issues/612)) ([6a0768b](https://github.com/snapotter-hq/snapotter/commit/6a0768b39da648d3b62b39f2370b97f9ce876da0)), closes [#535](https://github.com/snapotter-hq/snapotter/issues/535) * **tools:** classify expected input and timeout errors, not bugs ([#539](https://github.com/snapotter-hq/snapotter/issues/539)) ([39b89b9](https://github.com/snapotter-hq/snapotter/commit/39b89b9fbd4f67012b55ecdef35ddf369a3fb874)) * **video:** write faststart mp4/mov output from stabilize-video ([#593](https://github.com/snapotter-hq/snapotter/issues/593)) ([df92f7e](https://github.com/snapotter-hq/snapotter/commit/df92f7ee42b22b5b6cb3434a6b972851388c67ad)), closes [#588](https://github.com/snapotter-hq/snapotter/issues/588) * **web:** use h-dvh for app shells so mobile tool controls stay reachable ([#559](https://github.com/snapotter-hq/snapotter/issues/559)) ([3f7214b](https://github.com/snapotter-hq/snapotter/commit/3f7214bac22fe392eb7d179752e9c4583e4dbe0e)) ### Features * add landing testimonial wall, unblock the onboarding survey ([#639](https://github.com/snapotter-hq/snapotter/issues/639)) ([5cc0a85](https://github.com/snapotter-hq/snapotter/commit/5cc0a850c68e0af7a427b8442c24c55f6157416e)) * **analytics:** instance census, full capture, richer error context ([#511](https://github.com/snapotter-hq/snapotter/issues/511)) ([e1b8c24](https://github.com/snapotter-hq/snapotter/commit/e1b8c24e5d78153a62629972932a65cd1491ee77)) * clearer, disambiguated tool names across all surfaces ([#520](https://github.com/snapotter-hq/snapotter/issues/520)) ([f858c4c](https://github.com/snapotter-hq/snapotter/commit/f858c4cea00ae7165718509c8a78881fe6fc24fc)) * **docs-i18n:** translate all documentation into 20 languages ([4963ab3](https://github.com/snapotter-hq/snapotter/commit/4963ab3bbd36fe86b856a3be791f192af4582142)) * **erase-object:** add freeform lasso selection mode ([#503](https://github.com/snapotter-hq/snapotter/issues/503)) ([601557e](https://github.com/snapotter-hq/snapotter/commit/601557edaed32f331b5271334923f1c010098d91)) * **erase-object:** optional high-quality diffusion inpainting bundle ([#566](https://github.com/snapotter-hq/snapotter/issues/566)) ([1bac663](https://github.com/snapotter-hq/snapotter/commit/1bac663a2e76c98313a7a56c97231c2bbc1b36f2)), closes [hi#quality](https://github.com/hi/issues/quality) [#141](https://github.com/snapotter-hq/snapotter/issues/141) * **feedback:** gate onboarding survey on first processing, add prompt lifecycle events ([#615](https://github.com/snapotter-hq/snapotter/issues/615)) ([129e42b](https://github.com/snapotter-hq/snapotter/commit/129e42b95cd9c078eebcb3c52be10e8661dd7903)) * **files:** add save-as-new vs overwrite choice for library file edits ([#564](https://github.com/snapotter-hq/snapotter/issues/564)) ([a23158d](https://github.com/snapotter-hq/snapotter/commit/a23158d968c32e8cdf319225d73bf51120f20cbf)), closes [#495](https://github.com/snapotter-hq/snapotter/issues/495) * **i18n:** 21-language pipeline, landing/docs/API wiring, landing+API translations ([00b651c](https://github.com/snapotter-hq/snapotter/commit/00b651c9f851a8754cac755f6929ba2eeac8602b)) * **image:** add rounded-square and squircle crop tool ([#602](https://github.com/snapotter-hq/snapotter/issues/602)) ([e7ffb37](https://github.com/snapotter-hq/snapotter/commit/e7ffb37e981c553cf17db88155ac12667f104062)), closes [#601](https://github.com/snapotter-hq/snapotter/issues/601) * **landing-i18n:** recall a returning visitor's chosen language ([1bc9fe9](https://github.com/snapotter-hq/snapotter/commit/1bc9fe93ec4ca82295e77f2c8068bc700ef94367)) * **landing:** add a live system status indicator to the footer ([#641](https://github.com/snapotter-hq/snapotter/issues/641)) ([d690a6e](https://github.com/snapotter-hq/snapotter/commit/d690a6e26d82f9783cac154faa11f19481902a0e)) * **landing:** add Twelve Tools and Wired Business footer badges ([#512](https://github.com/snapotter-hq/snapotter/issues/512)) ([ce54d06](https://github.com/snapotter-hq/snapotter/commit/ce54d06e797c8f3fe25ed8c00ef42047ddf63882)) * **landing:** move language switcher from navbar to footer ([#513](https://github.com/snapotter-hq/snapotter/issues/513)) ([2dee9ea](https://github.com/snapotter-hq/snapotter/commit/2dee9ead0b1c84c02831c5e92082cd4598b7b8f5)) * **library:** wire save-mode into the five custom-client tool submitters ([#577](https://github.com/snapotter-hq/snapotter/issues/577)) ([1113c76](https://github.com/snapotter-hq/snapotter/commit/1113c761ead67664e4a7120b8bdf1b8e9da52c43)) * **onboarding:** collect install method and friction area in the usage survey ([#499](https://github.com/snapotter-hq/snapotter/issues/499)) ([430b87e](https://github.com/snapotter-hq/snapotter/commit/430b87eda0078c9afe31aa49da1f2293c928a147)) * **resize:** add aspect-ratio proportion presets ([#530](https://github.com/snapotter-hq/snapotter/issues/530)) ([d88999e](https://github.com/snapotter-hq/snapotter/commit/d88999e7a91d65adb37ea4cb3f9f7fe5650ad6c5)) * **search:** start typing anywhere to fill the search box ([#644](https://github.com/snapotter-hq/snapotter/issues/644)) ([0058fc6](https://github.com/snapotter-hq/snapotter/commit/0058fc610f4b88ddf0660889b2ea78bf33a527d1)) * **telemetry:** add a safe input_format tag to worker error reports ([#541](https://github.com/snapotter-hq/snapotter/issues/541)) ([281b4a0](https://github.com/snapotter-hq/snapotter/commit/281b4a06e39756a3a2b981e93d033454eb494c8c)) * **telemetry:** Sentry + PostHog quality pass ([#546](https://github.com/snapotter-hq/snapotter/issues/546)) ([8625143](https://github.com/snapotter-hq/snapotter/commit/86251434b5858c63049c50dda44acb8e281a4cdf)) * **tools:** remove background from animated GIFs (WebP, APNG) ([#502](https://github.com/snapotter-hq/snapotter/issues/502)) ([cb5db59](https://github.com/snapotter-hq/snapotter/commit/cb5db59f77e6b77fd9a575996b8514d6760aaf5b))
26 KiB
description
| description |
|---|
| Release notes and version history for SnapOtter. See what's new, improved, and fixed in each release. |
Changelog
v2.2.0
Most of 2.2 is hardening. A non-admin holding a delegated users:manage role could take over an instance, and every tool endpoint turned out to be missing its permission check. Upgrade if you run more than one user.
The rest is the long tail of self-hosting: downloads that started and never finished, presets that broke the moment you added a second file, containers that crash-looped while Postgres was still coming up. Two new tools landed too. Docs and the website are now translated into 21 languages, and the interface finally clears WCAG AA contrast.
Important
If you granted
users:manageto a non-admin through a custom role, that role could take over administrator accounts on 2.0.0 through 2.1.0. Details in Security.
Security
- Privilege escalation through a delegated
users:managerole (GHSA-9xgh-95qh-2x7h). A non-admin holdingusers:managethrough a custom role could reset the password of, or delete, a higher-privileged account, a built-in administrator included, and take over the instance. Password reset and account deletion now verify the caller's authority over the target, so a delegated role cannot reach above its own level. Affected: 2.0.0 through 2.1.0. Reported by 李春来 (Chunlai Li). (#616) - Every tool endpoint is now gated. Tool access was enforced route by route, so all 45 hand-written routes had to remember the same call and none of them did. A role without
tools:usecould still run image-to-pdf, erase-object, upscale, sign-pdf and the rest. The check now lives in one preHandler keyed off the matched route, so it covers sub-paths and any route added later. (#646) - Rate limiting was bypassable on Docker installs. The image shipped
TRUST_PROXY=true, so the client IP came from a request header and a forgedX-Forwarded-Forwalked past the login limiter. The default is now a private-network trust list. (#649) - Settings writes are checked per setting, not once at the door, and config import is now transactional so a rejected key cannot leave half a config applied. (#618)
- Library filenames cannot escape their storage root. A crafted stored name, which a malicious 1.x SQLite import copies verbatim, could read or delete files outside the files directory. Only reachable after an operator imports an attacker-supplied 1.x SQLite database, which is the one path that lets a stored name be chosen. Reported by @Alpastx. (GHSA-55w2-8cqf-w969, #600)
- Job cancellation enforces ownership, so an authenticated user can no longer cancel another user's job by its id. Reported by @Alpastx. (GHSA-wqxf-gj2p-689x, #599)
- A re-audit of the whole 2.0 tree caught what was still open: a captured SAML assertion could be replayed, an MFA challenge survived any number of wrong codes, the per-request API-key lookup was a full table scan,
MAX_WORKSPACE_SIZE_GBwas dead config, and the SVG sanitizer let through unquotedjavascript:hrefs. (#620) - Nine disclosed CVEs patched: fast-uri, svgo, sharp and tar transitively (#619), plus Pillow 12.3.0 (#517). RAW decoding on arm64 now builds LibRaw 0.22.2 from source instead of linking an unpatched system copy. (#649)
New Features
- Rounded Crop: rounded-square and iOS-style squircle masks, with a corner-radius control. Built for favicons and app icons. (#602)
- Remove GIF Background: strip the background from an animated GIF, WebP or APNG frame by frame and get a transparent animation back. (#502)
- Object Eraser lasso and High Quality mode: drag a freeform loop instead of painting every pixel, and optionally install a diffusion inpainting bundle for cleaner large-area fills. (#503, #566)
- Save as new, or overwrite. Editing a library file used to silently supersede the original. You now choose per edit, and the default keeps it. (#564, #577)
- Aspect-ratio presets in Resize. Pick 1:1, 4:3 or 16:9 and the two dimensions stay locked, without upscaling. (#530)
- Two-tier OCR: a fast tier is baked into the image and runs offline with no download, and a more accurate runtime installs on demand. (#519)
- Type anywhere to search. Start typing on the dashboard or the homepage and it lands in the search box. (#644)
- Documentation and website in 21 languages: the docs site, the landing pages and the API reference are all translated now, joining the app interface. A new low-resource deployment guide covers Raspberry Pi and 2 GB machines. (#548)
- Clearer tool names: 18 ambiguous names now self-qualify, so "Compress" became "Compress Image". Tool ids and routes are unchanged, so nothing bookmarked breaks. (#520)
Improvements
- WCAG AA contrast across the interface: the palette was retuned so vivid orange stays on fills while text and labels use accessible ink tokens, and all 57 focus indicators now clear the 3:1 non-text bar. (#567, #574)
- Upscale and background removal stop looking frozen: the progress bar advances during inference instead of parking at 30%, and both tools now warn that a CPU-only host is much slower. (#605, #608)
- Timeout messages name the real cause and account for CPU-only hosts instead of blaming the upload. (#596)
- SnapOtter waits for Postgres and Redis at startup instead of crash-looping, in Compose and the all-in-one image alike. A Proxmox LXC install was restarting 76 times before this. (#537, #595)
- Downloads survive a reverse proxy: SnapOtter asks nginx and compatible proxies not to buffer file responses, which is the usual reason a self-hosted download starts and never finishes. (#604, #607)
- Compress PDF lands near the target size, and says so honestly when a target is out of reach. (#522)
- Mobile tool controls stay reachable now that the app shells size to the dynamic viewport. (#559)
- Convert Audio exposes a sample-rate setting. (#561)
- Admins can relax the minimum password length, down to 1 for a trusted LAN instance. (#543)
- MFA is self-service, the policy lockout is closed, and OIDC or SAML logins get a real MFA challenge instead of a hard block. (#531, #536)
- A bare
Error: Errornow names its cause. Sharp encode failures, AI sidecar exits, non-JSON document sidecar output, and background-removal failures all used to arrive with the reason scrubbed off. (#532, #534, #535, #538, #612) - The help dialog is translated. Its shortcut labels and getting-started text were hardcoded English while finished translations sat unused in all 21 locale files. (#647)
Bug Fixes
- iPhone HEIC files were rejected as unreadable before they reached the decoder that handles them. (#631)
- Conversion presets failed on a second file: jpg-to-pdf and its image-to-pdf siblings, plus pdf-to-png, pdf-to-jpg and pdf-to-tiff, all returned
Tool not foundonce you uploaded two files. (#633, #643) - PDF conversion presets lost their download button. (#629)
- An unlimited processing timeout was not honored, and stalled progress streams now recover instead of leaving the interface waiting. (#638)
- Downloads hung instead of failing when a stored file turned out shorter than its recorded length. (#617)
- PDF page tools failed on short and encrypted files: Remove Pages defaulted to a page range no document under six pages has, and password-protected PDFs failed cryptically inside the worker. (#594)
- PDF to Text silently returned an empty file for scanned PDFs. It now points you at OCR and serves text as UTF-8. (#603)
- PDF to Word dropped colored text blocks and split them across the page. (#500)
- Object Eraser left ghost remnants and blurred small objects in high-resolution images. (#501)
- Stabilize Video wrote unplayable output without faststart. (#593)
- Image editor repairs: rotate, flip, resize, levels, curves, filters and layer lock. (#597)
- Sign PDF showed a blank canvas instead of reporting why a PDF failed to load. (#545)
- The file library recorded 0x0 dimensions and skipped previews for HEIC, RAW and PSD uploads. (#636, #637)
- Installing more than one AI bundle left the shared virtualenv multi-versioned and quietly broke three tools. (#649)
- Converting to JXL at quality 1 through 4 returned a 500, and a missing ffmpeg was reported to you as a corrupt upload. (#649)
- A transient Postgres outage stranded finished jobs, leaving output on disk with no row pointing at it. A reconciler now adopts that work instead of dropping it. (#649)
- A Redis endpoint that changed address wedged every consumer while health checks still answered 200. (#649)
- Website and docs fixes: localized links no longer drop
#fragments or lowercasezh-CN, the docs nav stays inside the viewport on tablets, and neither site calls the GitHub API from your browser any more. (#516, #562, #570, #560)
Upgrade Notes
Nothing to migrate, but two shipped defaults changed:
TRUST_PROXYnow defaults toloopback,linklocal,uniquelocalinstead of trusting every peer. Docker bridge and Compose networks sit inside that range, so most setups need no change. If your reverse proxy reaches SnapOtter from a public address, setTRUST_PROXYexplicitly, or rate limiting and audit logs will attribute every request to the proxy.MAX_AI_JOBS_PER_USERdefaults to 5 in-flight single-file AI jobs per user. Batch and pipeline AI runs stay uncapped.
New optional knobs: DB_STARTUP_TIMEOUT_MS, SUBPROCESS_MEMORY_LIMIT_MB (off by default) and GIF_BG_MAX_FRAMES.
Acknowledgements
A good part of this release started as someone else's bug report.
Code and contributions:
- @mvanhorn ❤️: Rewrote the remove-background timeout failure into a message that says what to do about it, instead of a bare timeout. (#518, #494)
- @EuanTop ❤️: Restored the download action on PDF conversion preset pages by making the synchronous route return the standard tool-result contract. (#629, #623)
- @harshjainnn ❤️: Diagnosed the download that starts and never finishes, and proposed the socket-reset direction the fix was built on. (#617, #590)
Security disclosures:
- 李春来 (Chunlai Li) ❤️ (@laijunyue): Privately disclosed the privilege escalation through a delegated
users:managerole, with a full source-to-sink analysis and a working exploit chain. (GHSA-9xgh-95qh-2x7h, #616) - @Alpastx ❤️ (Alpesh Bhagwatkar): Disclosed an authenticated IDOR on job cancellation, and a path traversal in library stored filenames reachable after a malicious 1.x SQLite import, with a working proof of concept for each. (#599, #600)
Bug reports:
- @riz467 ❤️: Real iPhone HEIC files rejected at validation, with the root cause worked out in the report. (#622)
- @coupej ❤️: PDF preset pages hanging forever, and jpg-to-pdf failing as soon as a second file was added. Two distinct bugs, correctly separated. (#623, #627)
- @linuxuser1 ❤️:
PROCESSING_TIMEOUT_S=0documented as unlimited but capped at five minutes. (#630) - @bezibaerchen ❤️: Convert Audio hid the sample-rate setting its own description promised. (#558)
- @hell-toupee ❤️: The accurate OCR bundle failing to install with a
libpaddleerror. (#505) - @Michael1260 ❤️ and @And-CSH ❤️: Confirmed that OCR install failure independently and established that manual extraction works while the in-app installer does not, which pinned the bug to the installer. (#505)
- @TomErnst1972 ❤️: Enabling the MFA-required policy locking an admin out of their own instance. (#515)
- @thokich ❤️: Object erasing producing blurry, unusable fills, which drove both the HD inpainting rewrite and the optional high-quality bundle. (#141)
- @Hennie-git ❤️: A Proxmox LXC install restarting 76 times against a Postgres that was not yet accepting connections. (community-scripts/ProxmoxVE#15796)
- MickLesk ❤️ (Proxmox VE community-scripts): Triaged that report in real time and produced the diagnosis the startup fix was built on. (#537)
Feature requests and feedback:
- @LECOQQ ❤️: Asked to relax password complexity on a home-server install. (#136)
- @killervette42 ❤️: Asked for an Unraid Community App, now published to the Unraid CA store. (#96)
- @alienatedsec ❤️: Pointed out the GPU-falls-back-to-CPU fix was buried in a closed issue and undiscoverable, which is why it is in the deployment docs now. (#490, #587)
- @neilp316 ❤️: Confirmed the Blackwell GPU failure on an RTX 5060 and mapped a working CUDA 12.8 upgrade path. (#120)
- @Roiki11 ❤️: Argued for shared storage with path references over HTTP file transfer for off-box AI compute. (#189)
Thank you as well to the community members who reported these over Discord and email, whose names we did not record: the squircle crop request (#602), the image-to-PDF download that started and never finished (#604, #607), the Delete Pages tool getting stuck (#594), Arabic text missing from pdf-to-text (#603), and the diagnosis behind the ONNX GPU fallback (#490).
v2.0.0
SnapOtter 2.0 turns the image toolkit into a full file-manipulation suite: 200+ tools across five modalities (Image, Video, Audio, PDF, and Files), rebuilt on Postgres 17 and a Redis-backed job queue, with a one-command docker run. This is a major release; read Breaking changes before upgrading from 1.x.
New features
- Four new tool modalities: Video, Audio, PDF, and Files join Image, taking the catalog to 200+ tools.
- Durable background jobs: A Redis-backed queue (BullMQ) runs every tool as a tracked job with live SSE progress.
- All-in-one single-container mode: One
docker runboots a complete instance with embedded Postgres and Redis. - On-demand AI bundles: Background removal, OCR, transcription, upscaling, face detection and enhancement, object eraser, colorize, and photo restoration install from the UI. GPU acceleration is detected per framework.
- Sign PDF: Draw, type, or upload a signature and place it on a PDF in the browser.
- Automate: A visual pipeline builder that chains tools, with nine prebuilt templates.
- 83 one-click conversion presets: Dedicated JPG-to-PNG, MP4-to-GIF, and similar converters with fuzzy search.
- Layer-based image editor: A Konva-powered editor at
/editorwith brushes, shapes, adjustments, filters, and curves. - Files library: Save any result and reuse it as input to another tool.
- Pinned tools, in-canvas zoom and pan, 21 languages, and enterprise capabilities (OIDC/SSO, SAML, SCIM, S3 storage, per-tool permissions, audit export, distributed tracing).
Improvements
- Cancel a running process. (#137)
- Full-resolution RAW decoding through LibRaw, including DNG. (#289)
- Non-root and foreign-UID deployments (TrueNAS, Unraid, OpenShift, PUID/PGID). (#230, #127)
- Accurate AI install detection and a hardened install flow. (#214, #352)
- Privacy hardening: no automatic third-party egress, plus an optional strict-offline mode.
- Always-on feedback button, even with analytics off.
Bug fixes
RATE_LIMIT_PER_MIN=0disables rate limiting for tool routes again. (#271)- Repaired AI virtualenv paths inside the Docker image. (#390)
- sharp 0.35.2+ compatibility. (#362)
- Image editor layout fixes: rulers, fill behavior, sidebar, and canvas sizing. (#258, #259)
- Completed the Italian translation. (#231, #206, #425)
- Audio normalize and loudnorm preserve the source sample rate.
- SSRF hardening: numeric IPv6 CIDR matching and a broadened URL pre-scan. (#287)
- Generated PDFs are stamped with SnapOtter as the Producer.
- mediapipe installs on Python 3.13 and Debian 13.
Breaking changes
2.0 replaces the embedded SQLite database with Postgres 17 and adds Redis 8 for the job queue. Your 1.x data migrates automatically on first boot, but the container stack changed, so back up your whole /data volume first (1.x runs SQLite in WAL mode, so the committed data usually lives in snapotter.db-wal). Then pick the single-container image (embedded Postgres and Redis, root only) or the Compose stack (app plus Postgres 17 and Redis 8). See the migration guide and the upgrade guide.
Upgrade
docker pull snapotter/snapotter:2.0.0
Or with Docker Compose:
docker compose pull && docker compose up -d
v1.17.2
New HTML to Image tool, WCAG 2.2 AA accessibility, security hardening from penetration testing, and 5 critical Docker fixes.
New features
- HTML to Image: Capture screenshots of URLs or raw HTML as PNG/JPEG/WebP. Full-page captures, custom viewports, dark mode.
- Docker _FILE secret convention: Mount sensitive env vars as files instead of plain-text. (#205)
- Enterprise licensing and S3 storage: Optional commercial license key and S3-compatible object storage.
- Shape editor improvements: Fill/stroke transparency, RGBA color picker, dash line styles.
- Pre-built release archives: Download tarballs from GitHub Releases for non-Docker installs (Proxmox, bare metal, LXC). (#202)
Improvements
- WCAG 2.2 AA accessibility: Skip navigation, focus trapping, aria-live regions, reduced motion support, correct contrast ratios. (#209)
- Mobile responsiveness: Responsive settings, SSE auto-reconnect on mobile tab switch. (#203, #204)
- Background removal quality: Edge smoothing, color decontamination, output format selection.
- Italian translation: ~145 new strings by @albanobattistella. (#206)
- Per-tool API documentation: 53 doc pages with parameters, examples, and response formats.
- AI model downloads: Retry logic with exponential backoff for HuggingFace. (#201)
Bug fixes
- Fresh Docker containers were completely unusable (rate limit blocked all requests).
- Face detection AI tools (blur-faces, red-eye-removal, enhance-faces, passport-photo) failed on all platforms.
- HEIC files broken on ARM (libheif symbol mismatch).
- Upscale and restore-photo AI bundles failed to install on ARM.
- OCR used wrong CUDA version on GPU containers.
- SSRF guard bypass via hex IPv4-mapped IPv6 addresses. (Credit: @tonghuaroot)
- iPhone HEIC decoding with auxiliary images. (#183, #199)
- Real-ESRGAN CUDA OOM on 8GB GPUs. (#200)
- 6 production Sentry errors and 7 QA bugs. (#208)
Security
- 10 penetration test findings addressed (XFF bypass, malformed JSON crashes, unbounded pipelines, audit log XSS, TRACE method, and more). (#207)
- SSRF hex IPv6 bypass blocked. (Credit: @tonghuaroot)
- Dockerfile base images pinned by digest.
Upgrade
docker pull snapotter/snapotter:1.17.2
Or with Docker Compose:
docker compose pull && docker compose up -d
v1.17.1
Live demo, per-tool landing pages, and a batch of polish fixes.
New features
- Live demo - demo.snapotter.com lets people try SnapOtter without installing anything.
- Tools index page - Browse all 50+ tools at
/toolswith search and category filters. - 50+ SEO landing pages - Every tool now has a dedicated landing page with FAQs, use cases, and comparison tables.
- Background preview - Before-after slider shows a checkered background behind transparent images.
- Strong password generator - One-click button in the Add Members form.
Bug fixes
- HEIC/HEIF info tool no longer fails (pre-decode added).
- AI model bundle install shows better error messages and respects resource limits.
- Library thumbnails load correctly (auth headers were missing).
- Dropdown menus no longer clip in People and Teams settings tables.
- Size comparison percentage hidden on non-compression tools.
- Duplicate privacy policy link removed.
- Italian translation added for AI features settings.
- Renamed Lucide icons updated (Wand2, Columns).
Infrastructure
- OpenSSF Scorecard hardened from 4.3 to ~7.0.
- CI tests parallelized into 4 shards with downsized fixtures.
- 41 dependency updates.
Upgrade
docker pull snapotter/snapotter:1.17.1
Or with Docker Compose:
docker compose pull && docker compose up -d
v1.17.0
Five new tools, a full image editor, SSO login, 20 languages. Probably should have been three separate releases, but here we are.
New features
- Image editor - Layers, brushes, shapes, adjustments, filters, curves, keyboard shortcuts. Runs in your browser, processes on your hardware.
- OIDC / SSO authentication - Login with Google, GitHub, Okta, or any OpenID Connect provider. Set a few env vars and your team uses their existing accounts.
- Meme generator - 100 built-in templates with text rendering via opentype.js. Or upload your own image.
- Beautify - Drop a screenshot in, get a polished image out. Device frames (macOS, Windows, browser), shadows, gradients, social media presets.
- Color blindness simulation - Preview how images look with protanopia, deuteranopia, tritanopia, and other color vision deficiencies.
- PNG transparency fixer - Detects fake-transparent PNGs and fixes them with BiRefNet HR-matting. Optional watermark removal via LaMa inpainting.
- AI canvas expand - Extend image boundaries with AI fill. Three quality tiers (fast, balanced, quality) depending on how much GPU time you want to trade.
- 20 languages - Arabic, Chinese (Simplified/Traditional), Czech, Dutch, French, German, Hindi, Indonesian, Italian, Japanese, Korean, Polish, Portuguese, Russian, Spanish, Thai, Turkish, Ukrainian, Vietnamese. RTL works for Arabic.
- URL import - Paste URLs into the dropzone or bulk-import from a list. Server-side fetch with SSRF protection.
- Multi-file eraser - Draw erase masks across multiple images, process them all with one click. Strokes persist per-image.
- Pipeline import/export - Save tool chains as JSON, share them with others.
- 17 new camera RAW formats via exiftool, plus QOI, JP2, EPS, DDS, CUR, DPX, FITS, PPM/PGM/PBM, SVGZ, and APNG input. New output codecs for BMP, ICO, JP2, QOI. AVIF, TIFF, GIF, JXL, and PSD export recovered from a previously lost branch.
Improvements
- Image enhancement - Replaced the old pipeline with CLAHE + normalise + gamma. New Deep Enhance toggle uses the AI model for more aggressive results.
- Restore photo - Scratch detection rewritten with 8-angle Otsu filtering. LaMa inpainting now runs at native resolution.
- Exotic formats everywhere - OCR, image-to-PDF, favicon generator, composition, stitch, and vectorize all decode HEIC, RAW, PSD now.
- Compress - Target-size tolerance tightened from 5% to 1%. Target size is the default mode. Added stepper buttons and KB/MB unit selector.
- Sentry cleanup - 644 non-actionable events filtered. Real errors now handled properly.
- GPU detection - Better diagnostics for containers where CUDA is present but nvidia-smi is not.
- Auth-disabled mode - Anonymous user is seeded in the DB with admin role. API keys, pipelines, and user files no longer break on FK constraints.
- 2,705+ new tests across unit, integration, and E2E.
Bug fixes
- Upscale on CPU no longer times out on NAS boxes and low-power hardware.
- QR code logo no longer makes the preview vanish permanently.
- Crop overflow fixed for tall portrait images.
- TIFF alpha files correctly force PNG output instead of producing corruption.
- HDR/EXR decode converts to 8-bit before CLAHE, fixing decode failures.
- Face landmarks input buffers converted to PNG before the Python sidecar, fixing crashes.
- Find duplicates handles mixed-format batches and network errors.
- Beautify preview updates in real time.
- Progress bars for stitch and vectorize.
- SVGZ handled by SVG-to-raster.
- Non-ASCII filenames fixed via percent-encoded X-File-Results header.
Upgrade
docker pull snapotter/snapotter:1.17.0
Or with Docker Compose:
docker compose pull && docker compose up -d
v1.14.0
Unified Docker image with GPU auto-detection. One image handles both CPU and GPU workloads. Simplified compose to a single file with log rotation. Model pre-downloads now include verification and a smoke test.
v1.13.0
Role-based access control (RBAC). 14 granular permissions, three built-in roles (admin, editor, user), custom role support. Permission checks on all API routes. Frontend tabs filtered by user permissions.
v1.12.0
PDF to Image tool. Convert PDF pages to PNG, JPEG, WebP, or TIFF at custom DPI. Unified Docker image with GPU auto-detection.
v1.11.0
Auto-generated llms.txt via vitepress-plugin-llms for AI-friendly documentation.
v1.10.0
Content-aware resize (seam carving) with face protection. Resize images while preserving important content.
v1.9.0
Stitch / Combine tool. Join images side by side, stacked vertically, or in a custom grid.
v1.8.0
Edit Metadata tool. View and edit EXIF, IPTC, and XMP metadata with a granular strip/keep interface.
Older releases
For the full commit-level changelog including patch releases, see GitHub Releases.