141 Commits
Author SHA1 Message Date
SnapOtterandGitHub e815676505 ci(nightly): free disk before the Docker Container E2E build (#700)
The job builds a ~15GB image on three containers and has been terminated mid-run (exit 137, tests passing) on standard runners. Reclaim preinstalled toolchains before building to reduce the resource pressure correlated with the terminations.
2026-07-31 10:59:58 +08:00
SnapOtterandGitHub 734bccb674 ci(nightly): give Docker Container E2E a 90-minute budget (#698)
A from-scratch image build plus the full in-container suite runs ~65 minutes, so the 60-minute budget cancelled the job right before it finished while every test passed. Raise it to 90 minutes.
2026-07-31 09:40:23 +08:00
SnapOtterandGitHub 1b41da7615 ci(nightly): repair five job classes broken by the #649 QA hardening (#695)
Nightly has been red since 07-28; per-PR CI and main are green. Two investigations traced all five failing classes to #649: extended-matrix required AI bundles it never installs (removed), nightly SYSTEM_DEPS drifted from ci.yml (added libreoffice + a doc-binaries composite for pandoc/pdfcpu), the generated-case classifier only skipped ffmpeg (widened to pdfcpu/soffice/pandoc + excluded repo-audit specs from the lean docker image), a settings spec capped loginAttemptLimit and 429-cascaded the serial bucket (restore via API), and type-to-search refused keystrokes under a route announcer's programmatic focus (guard added). A nightly dispatch on the branch confirmed all five classes green.
2026-07-31 02:09:39 +08:00
SnapOtterandGitHub 44081e6a3f ci(ocr): verify the baked trust file, not unset image env vars (#666)
Fourth latent bug in the OCR publish chain (#649/#519 added it, never ran).
verify-ocr's trust-identity step read OCR_RUNTIME_INDEX_KEY_ID from the image env,
which the official image leaves unset by design (operator-override path;
runtime-index.ts reads the baked file when env is unset). It compared "" to
snapotter-ocr-2026-07 and failed a correct image. Now verifies the baked
/app/docker/ocr-runtime-trust.json, proven against the built release image.

A full audit of the remaining chain (verify, sign, verify-signed, publish)
reproduced the sign+verify path end-to-end inside the image and confirmed nothing
else breaks for v2.2.0. One latent non-ASCII canonicalization mismatch filed as
#667 (cannot fire on v2.2.0). Non-releasable type so the re-dispatch re-runs 2.2.0.
2026-07-29 20:21:16 +08:00
SnapOtterandGitHub 5c75a93484 ci(release): fix the archive tsx path and patch the go-tools x/text HIGH (#665)
Two latent bugs the v2.2.0 release run surfaced, both added by #649 and never run
in a real release (v2.1.0 had no archive-security job).

archive-security asserted tsx at the workspace root, but tsx is a prod dependency
of apps/api, so pnpm places its bin at apps/api/node_modules/.bin/tsx, where the
Docker CMD runs it. The root path never existed and failed the extract step on
both arches. Fixed to the apps/api path, proven against the real prebuilt-amd64
artifact.

The blocking Trivy scans would then have failed on CVE-2026-56852,
golang.org/x/text v0.38.0 -> v0.39.0, the only fixed CRITICAL/HIGH in the image,
compiled into caire and pdfcpu. Pinned to v0.39.0 in both go-tools modules,
verified building in the golang:1.25.12 toolchain with -mod=readonly and linking
v0.39.0, with a clean Trivy rescan.

Guards added for both the tsx path and the x/text pin. Non-releasable type so a
re-dispatch re-runs 2.2.0.
2026-07-29 19:05:30 +08:00
SnapOtterandGitHub 935861bced fix(release): resolve the release by id, and make the vulnerability gate cover HIGH (#661)
Two release-pipeline defects found while pre-flighting 2.2.0, plus the image
hardening that the second one exposed.

The release job would have died immediately after pushing the v2.2.0 tag.
draftRelease was turned on in #649 and never executed, and GitHub's
/releases/tags/{tag} endpoint does not return draft releases, so all nine tag
lookups in release.yml would have 404'd against the draft semantic-release had
just created. Verified against this repo with a throwaway draft: the tag
endpoint 404s while gh release view reads it and /releases/{id} returns the same
REST shape. Every site now resolves the numeric id first, so existing jq
expressions are untouched.

The unfixed-vulnerability gate was measuring almost nothing. The blocking Trivy
steps run ignore-unfixed, and trivy-unfixed-gate.mjs was meant to cover the
remainder but defaults to CRITICAL with neither call site passing --severity. An
unfixed HIGH was gated by nothing, and the arm64 image carried 79 of them while
the summary read clean.

Rather than document 79 findings, the image lost what it did not need:
libde265 1.1.1 and libheif 1.23.1 are now built from source (the old libheif pin
was itself affected by CVE-2026-3950, and Debian's libde265 1.0.11 was the
decoder every .heic upload actually reached), and xvfb, wget and openssh-client
are purged. 15 CVEs left the image outright and the HIGH gap fell to 65, each
now carrying a rationale verified against the running container.

curl gets its own section: bookworm-backports has a fixed 8.14.1, so claiming no
fix was available would have been false. It is recorded as a declined fix.

Verified on both architectures: gate exits 0, the source-built libde265 is the
one libheif links, and HEIC, RAW, ImageMagick, Sharp AVIF and headless chromium
all still work after the purge.
2026-07-28 22:49:56 +08:00
SnapOtterandGitHub 5f21588f6c chore: prepare the 2.2.0 release (#660)
Bumps every version surface to 2.2.0, fixes a latent version-coupling bug in the
OCR runtime tests, and stops an absent GPU runner from silently stalling a
release.

Version surfaces: scripts/sync-version.sh covers the 11 workspaces, APP_VERSION,
and the docs release commands across all locales. Root package.json plus the
three surfaces the script never reaches are done by hand: the DOCKERHUB.md banner
and tag table, the docker-tags.md pinning table in 21 locales, and the example
runtimeVersion in tools/image/ocr.md in 21 locales. The release-notes archive step
is deliberately not pre-run, so the notes text stays editable until the release.

Latent bug: runtime-state rejects any runtime whose compatibility.snapotterVersion
is not exactly APP_VERSION, and five fixtures pinned the literal 2.1.0. Since
semantic-release rewrites APP_VERSION on every release, the first PR after any
bump would have gone red for a reason nobody would trace to the release. The
fixtures now derive from APP_VERSION.

GPU runner: sign-ocr-index needs verify-ocr-nvidia on self-hosted hardware, and
the gated manifest job needs ai-bundles, so a missing runner queued instead of
failing and produced no image tags. preflight-gpu-runner claims the same labels
with no dependencies, so it is scheduled first and validates the GPU before the
90-minute build. An API preflight is impossible because listing self-hosted
runners needs Administration:read, which GITHUB_TOKEN cannot hold, so RELEASE.md
carries the maintainer-side check.
2026-07-27 22:09:31 +08:00
SnapOtterandGitHub d10d0f544f fix: release QA hardening across processing, media, security, and CI gates (#649)
A release-readiness QA pass over the whole product. The commits split into
defects a user would hit and gates that were reporting green while measuring
nothing.

## Fixes that change behaviour

Rate limiting was bypassable on every install: TRUST_PROXY defaulted to true, so
request.ip came from a client-set header and a forged X-Forwarded-For got past
the login limiter. The default is now a private-network trust list.

A transient Postgres outage stranded in-flight jobs, leaving finished output on
disk with no row pointing at it. A reconciler now resolves those rows and adopts
the bytes rather than dropping the work.

A Redis connection that moved to a new address wedged every read-blocked
consumer, so completions stopped signalling while health still answered 200.
Socket timeouts plus subscriber pings recover it.

Installing more than one AI bundle left the shared venv multi-versioned and
silently broke three tools. The installer now reconciles distributions to one
version each.

Converting an image to JXL at quality 1 through 4 returned a 500, because
libjxl 0.7 rejects the distance those values compute. The quality is floored at
what the encoder honours. A missing ffmpeg was also reported to the user as a
corrupt upload; it now says the engine is unavailable.

RAW uploads reached an unpatched LibRaw on arm64, so it is built from source at
0.22.2, and the release scan was split so it can fail on an unfixed critical
instead of hiding it behind ignore-unfixed.

## Gates that could not fail

Two mutation lanes ran zero mutants because Stryker crawled the gitignored docs
build; coverage discarded its whole report on any failing test; the lint gate
skipped root tests, scripts, and two workspaces; and several generated matrices
counted a host missing ffmpeg as a passing tool. Each now measures what it
claims.

Full evidence and the outstanding release items are tracked locally and are not
part of this branch.
2026-07-27 15:37:30 +08:00
SnapOtterandGitHub ada5727114 ci: take Build off the integration shard critical path (#648)
Build listed six test jobs in `needs` but only reads `needs.changes.outputs.code`, in its `if`. The rest were ordering, not data.

That queued a 55s job behind the slowest integration shard, so it added its full runtime to the tail of every green run. Build and the test jobs are independent required checks, so a green Build still can't merge past a red shard.
2026-07-26 23:12:21 +08:00
SnapOtterandGitHub 301e6eb01a test: coverage campaign and mutation testing across five packages (#628)
Coverage 83.6 to 87.36% lines, 81.63 to 84.14% branches. Mutation testing across five packages: image-engine 85, media-engine 92, doc-engine 87, shared+enterprise 86, apps/api security and jobs slice. Runs all five lanes weekly. Fixes the silently-broken mutation CI (babel pin), a redact-pdf envelope-shape test bug, an untested enterprise license valid-signature path, and an audit test that only exercised a hand-copied reproduction. Test and config only, no product code changes beyond the babel pin and one test-only oidc export. Full suite: 16,712 pass, 0 fail.
2026-07-24 17:36:57 +08:00
SnapOtterandGitHub 90483c944a fix(readme): self-host the star history chart (#625)
GitHub restricted the stargazers API to a repo's own admins/collaborators (June 2026), so star-history.com's shared token pool 503s and the README chart went blank. Generate the chart from our own stargazer timeline instead (default GITHUB_TOKEN has access), publish it to the star-history branch, and embed it by raw URL. Weekly workflow keeps it fresh.
2026-07-24 06:44:28 +00:00
SnapOtterandGitHub 44f5aea326 fix(ci): repair the chronically-failing nightly workflow (#624)
The scheduled Nightly had been red for over a week across nearly every job. This
root-causes and fixes each one. All were pre-existing: missing CI provisioning,
specs that drifted as the app grew, a job too heavy for its timeout, and a fuzz
that was never configured for file-upload endpoints. None came from the recent
security merge.

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

Verified end to end: the full Nightly workflow is green on this branch (all 14
jobs), and PR CI is green.
2026-07-24 03:54:50 +08:00
SnapOtterandGitHub 1e1efbdfb3 test(landing): fix six stale e2e specs and wire the suite into CI (#572)
The landing Playwright suite ran in no CI workflow, so six specs had drifted red on main. Five subpages navigation tests asserted bare paths while the site emits trailing-slash URLs (format: directory), and one asserted a localized tool-detail page that is English-only by design. Fix the assertions and rewrite the tool test to the real invariant, then add a test-e2e-landing job gated on a new landing path filter so the suite runs on landing-relevant PRs and can't silently rot again.
2026-07-18 12:28:25 +08:00
SnapOtterandGitHub 67f54347b2 fix(landing): keep mixed-case locale casing in emitted URLs (#562)
Astro's getRelativeLocaleUrl lowercases the locale segment by default, so landing links and hreflang for zh-CN, zh-TW, and pt-BR were emitted lowercase and 404 on case-sensitive Cloudflare Pages. Pin the casing at the localizeHref chokepoint with normalizeLocale: false, add an e2e hreflang casing guard, and add a deploy-time check that blocks the build if any lowercased locale path leaks into the output.

Closes #554
2026-07-18 10:47:53 +08:00
SnapOtterandGitHub 54073a7c50 fix(landing,docs): use build-time GitHub star count, drop per-page api.github.com fetch (#560)
Removes the client-side api.github.com fetch from the landing navbar and the docs theme; both now render a build-time star count (docs via a new VitePress data loader). deploy-docs.yml gets GITHUB_TOKEN plus a daily refresh cron, mirroring deploy-landing.yml. Fixes #555.
2026-07-18 10:45:28 +08:00
dependabot[bot]andGitHub 80330a8b07 chore(deps): bump the actions group with 5 updates (#508)
Bumps 5 GitHub Actions via Dependabot. All 17 checks green.
2026-07-17 10:58:28 +08:00
SnapOtterandGitHub 991c981529 fix: make OCR portable and reliable across AMD64 and ARM64 (#519)
* fix: make OCR portable and reliable

* fix: harden OCR installation portability

* fix: pin OCR partials across downloads

* fix: make OCR execution reliably asynchronous

* fix: harden OCR portability and docs routes

* fix: preserve decoder and docs safeguards
2026-07-15 03:34:24 +08:00
SnapOtterandGitHub b6fdabaea8 ci(release): keep image attestations in GitHub's API, drop provenance sidecars (#504)
Stop pushing SLSA attestations to the registries as OCI referrer tags (attest.yml: push-to-registry: false) and disable buildx's default provenance sidecars (release.yml: provenance: false). GHCR rendered both as package versions / unknown-unknown architecture entries on the package page. The release/publish flow (publish-images gate, RELEASE_TOKEN, web Sentry DSN) is unchanged.
2026-07-12 05:02:15 +08:00
SnapOtterandGitHub 2e91368816 ci(release): authenticate main pushes with an admin RELEASE_TOKEN (#486)
Point semantic-release's version-bump push and the docs-changelog push at a RELEASE_TOKEN admin PAT so they clear the 16 required checks (default token is rejected). Falls back to the default token, so no change until the secret is set. Documents creating/rotating the token in RELEASE.md.
2026-07-11 12:49:02 +08:00
SnapOtterandGitHub b369b4fb41 ci(release): gate image publish behind a manual approval (#482)
Attach the manifest job to a publish-images GitHub Environment so Docker Hub + GHCR tags only go live after a maintainer approves the release run. Build + Trivy scan still run automatically; approval is the last step. Adds RELEASE.md runbook.
2026-07-11 11:09:53 +08:00
SnapOtterandGitHub d572b5c33c chore(branding): reframe assets to self-hosted infrastructure positioning (#479)
Regenerate the social/OG card (200+ tools, Private file processing, self-hosted infrastructure) and sync to landing/web/docs; update banner, press kit, package + OpenAPI + Docker Hub descriptions, a leaked docs count, and the English About string.
2026-07-11 02:07:15 +08:00
SnapOtterandGitHub ae6a4c8b7c fix: error-only Sentry telemetry, storm-proof capture, and crash fixes (#476)
Removes Sentry tracing entirely (BullMQ idle polling burned 4.8M transactions in 2 days at the baked 0.1 rate), decouples PostHog sampling, and replaces the type-only error scrub with a vetted-field sanitizer plus SafeError/ToolInputError contracts. One classified capture path with per-signature throttles and a per-process ceiling makes storms impossible (NODE-1E was 4,541 events from one 30s loop). Browser errors move to a dedicated web Sentry project with their own source maps. Adds the SNAPOTTER_TELEMETRY runtime kill switch and silences test fleets.

Crash fixes: remote 204/304 SSRF process kill (NODE-20), conversion-preset boot crash loop (NODE-21), Redis version preflight + unhandled subscribe rejection (NODE-1T), Sign PDF on plain-http origins (NODE-1K/1M), wavesurfer/pdf.js teardown rejections (NODE-1P/1N), bundle-import ZlibError to 400 (NODE-1Z), chart-maker input errors declassified (NODE-1H/1J), asset requests skip the session DB lookup (NODE-1D).
2026-07-10 21:41:49 +08:00
SnapOtter 008c4e3654 ci: add SLSA provenance attestation workflow for published releases [skip ci] 2026-07-07 16:25:02 +08: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 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
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>
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 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
SnapOtter 7b6765030b ci: gate demo deploy 2026-07-04 13:45:47 +08:00
SnapOtter 7473d2d0fc ci: deploy demo automatically 2026-07-04 13:08:24 +08:00
SnapOtterandGitHub 4d37092bbe ci: move path filtering to job level so required checks stay mergeable (#415)
Workflow-level paths-ignore never creates the check runs on docs-only
PRs, which would deadlock required status checks (contexts stay
"expected" forever). A new always-on changes job diffs against the
base commit and every downstream job skips when only README,
CONTRIBUTING, branding, apps/docs, apps/landing, or docs changed.
Skipped jobs report a conclusion, which satisfies branch protection,
so main can now require the full CI matrix without blocking
docs-only changes. Fails open: unknown base (force push, dispatch,
new branch) runs the full pipeline.

Prerequisite for enabling required status checks on main.

Claude-Session: https://claude.ai/code/session_01XGB4pGvTvb7sUX4JN745U7
2026-07-04 10:39:01 +08:00
SnapOtterandGitHub f3342a1e57 fix: harden Docker image and async job responses
Harden Docker runtime packaging, preserve async job response semantics, fix Redis subscriber startup connections, clear lint warnings, and harden enterprise S3 object body handling.
2026-07-01 12:32:33 +08:00
SnapOtterandGitHub 37dc0098ba docs: sync API and documentation coverage (#379)
* docs: sync API and docs coverage

* ci: pin pandoc for sandboxed conversions
2026-06-29 22:35:05 +08:00
SnapOtter e5d4dfe6f7 chore(deps): merge github actions digest bumps (#374) 2026-06-29 16:01:37 +08:00
SnapOtterandGitHub 084a9c9faa feat: all-in-one embedded single-container mode (#377)
Embedded Postgres 17 + Redis via s6-overlay when DATABASE_URL/REDIS_URL are unset; restores the one-command docker run for 2.0. EMBEDDED=0 disables; Compose stays the production path. Verified arm64 (14/14 lifecycle + Compose regression) and amd64 (build + embedded smoke).
2026-06-29 15:49:09 +08:00
dependabot[bot]andGitHub 624795803a chore(deps): bump the actions group with 2 updates
Bumps the actions group with 2 updates: [actions/cache](https://github.com/actions/cache) and [actions/setup-python](https://github.com/actions/setup-python).


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

Updates `actions/setup-python` from 6.2.0 to 6.3.0
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/a309ff8b426b58ec0e2a45f0f869d46889d02405...ece7cb06caefa5fff74198d8649806c4678c61a1)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/setup-python
  dependency-version: 6.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-29 05:36:51 +00:00
SnapOtterandGitHub 1c202c6ef0 feat(analytics): upload web source maps to Sentry + tie release to build (#369)
* feat(analytics): upload web source maps to Sentry + tie release to build

Web crash reports were unusable: the bundle ships minified with no source
maps uploaded, and every build reported as the frozen APP_VERSION, so a
Sentry error showed an unreadable stack under a single release.

- Add @sentry/vite-plugin: emit hidden source maps and upload them by debug
  id when SENTRY_AUTH_TOKEN is present (published Docker build only), then
  delete the maps so they never ship. No-op for dev and the source archive.
- Set the Sentry release from SENTRY_RELEASE / VITE_SENTRY_RELEASE (the Docker
  build passes the release version), falling back to APP_VERSION.
- Relax beforeSend so app bundle frames keep a host-stripped path (Sentry needs
  it to match the uploaded map) while the instance hostname, error message, and
  PII stay stripped. Filesystem paths still collapse to the basename.
- Wire the Dockerfile (sentry_auth_token build secret + SENTRY_RELEASE arg/env)
  and the release docker job.

* fix(analytics): point source map upload at the snapotter org (project node)
2026-06-29 10:47:30 +08:00
SnapOtterandGitHub 9819c5885e feat(analytics): inject Sentry DSN + PostHog key from build env (#367)
* fix(analytics): bake real Sentry DSN and lower trace sampling

The bake script emitted a placeholder Sentry DSN even in on mode, so every
published image initialized Sentry against a dead endpoint and no events ever
reached the project. Point it at the real snapotter project DSN.

Also drop tracesSampleRate from 1 to 0.1. It governs only performance
transactions (errors are always captured), so 100% fleet-wide tracing would
drain Sentry quota for no benefit.

* fix(analytics): point baked Sentry DSN at the snapotter org

* refactor(analytics): inject Sentry DSN + PostHog key from build env

#336 replaced the analytics creds with placeholders but never added a way to
put real values back at build time, so any image built from the repo since then
ships dead analytics (the live fleet only still reports because publishing is
paused and it runs a pre-placeholder image).

Restore the pipeline the clean way: bake-analytics.mjs reads SNAPOTTER_SENTRY_DSN
and SNAPOTTER_POSTHOG_KEY from the environment; the official image's CI supplies
them from repo secrets via build args. A build with neither stays disabled, so
building from source never phones home. Both values are public (they ship in the
browser bundle), so this is about not making source builds report, not secrecy.

Supersedes the hardcoded DSN: real creds are no longer committed to the repo.
2026-06-29 10:41:58 +08:00
SnapOtterandGitHub e6f4ac7b13 ci: also manage the Docker Hub short description via the sync workflow (#365)
Adds a short-description input so the one-liner stays at '200+ tools' alongside the auto-synced overview, instead of drifting at the old '150+ tools'.
2026-06-28 21:51:05 +08:00
SnapOtterandGitHub 0a0c3cc458 ci: auto-sync Docker Hub overview via peter-evans/dockerhub-description (#360)
Add a workflow that publishes DOCKERHUB.md as the snapotter/snapotter repository overview on Docker Hub, on pushes to main that touch DOCKERHUB.md and via manual dispatch. Commit DOCKERHUB.md (previously gitignored) so CI can read it, and refresh its tool counts to 240 to match the README. Auth uses the DOCKER_HUB_README secret (Docker Hub account password) and the existing DOCKERHUB_USERNAME.
2026-06-28 20:41:10 +08:00
SnapOtterandGitHub 63a03d26f2 feat: pipeline templates, analytics opt-out, 83 conversion presets, positioning + e2e modernization
Lands five integrated branches: pipeline templates (#355), analytics opt-out (#354), 83 conversion presets bringing the catalog to 240 tools (#356), self-hosted positioning (#353), and e2e modernization (#351).

Integration fixes: aligned stale web analytics tests with the opt-out/allow-list model, closed 3 CodeQL incomplete-sanitization alerts in the i18n generator, resolved settings/index/docs/format-matrix conflicts, and corrected tool counts to 240.
2026-06-28 18:57:53 +08:00
SnapOtterandGitHub bbad953e79 fix(nightly): de-flake matrix timeouts, exclude browser tool from fuzz (#349)
Systematic round - attack the failure classes, not one bug at a time:

- Flakiness (Coverage/Extended Matrix flipped green<->red): the generated
  format-matrix conversion tests hardcode a 30s/60s per-test timeout that the
  jobs' VITEST_TEST_TIMEOUT can't override. Heavy conversions under coverage
  instrumentation / full matrix intermittently exceed it. Raise to 120s.
- Schemathesis: html-to-image returns 503 when its headless browser isn't
  present (always, in the fuzz env) - same expected-unavailable class as the
  AI 501s. Exclude it from the fuzz (it can't be exercised without a browser).
- Baseline regen: bump timeout 60->120 min so it can finish now that the
  route sweep (#347) lets the visual specs pass instead of failing+retrying.
2026-06-25 00:14:45 +08:00
SnapOtterandGitHub 078743d6b2 fix(nightly): qpdf in test image, NUL-byte settings, AI sub-path fuzz exclude (#348)
Third round - the prior fixes unblocked these deeper failures on the nightly:

- Docker E2E: the patches/ fix (#346) let the build finish, so tests now
  run - and fail with 'spawnSync qpdf ENOENT'. Dockerfile.test installed
  imagemagick/ghostscript/exiftool but never qpdf, which the PDF tools and
  fixture-integrity checks need. Add it.
- NUL-byte 500 (real robustness bug Schemathesis found): a settings string
  containing U+0000 hits the jobs.settings jsonb insert and Postgres rejects
  it ('invalid byte sequence for encoding UTF8: 0x00'), 500ing tools like
  html-to-image. Strip NUL bytes from settings before the insert (NUL is
  never meaningful in tool settings). api typecheck passes.
- Schemathesis: the AI exclude (#346) only anchored on the tool id at the
  path end, so AI sub-endpoints like /passport-photo/analyze were still
  fuzzed and 501'd. Extend the regex to allow an optional sub-path.
2026-06-24 21:59:02 +08:00
SnapOtterandGitHub 3dd0ebb49d test(nightly): fix Schemathesis/Coverage/Docker/fuzz root causes (#346)
Second round of nightly fixes, each root-caused from the post-fix run:

- Docker E2E (real bug): Dockerfile.test never copied patches/, so pnpm
  install hit 'ENOENT patches/gray-matter@4.0.3.patch' and exited 254.
  Copy patches/ like the prod Dockerfile does. (My earlier network-retry
  guess was a misdiagnosis; reverted.)
- fuzz-settings (real bug): the graceful-skip regex matched 'precondition'
  but fast-check v4 says 'pre-condition' (hyphenated), so 3 constrained PDF
  tools (extract/remove/organize-pages) errored instead of skipping. Match
  the hyphen. Verified locally: 3 failed -> 3 passed.
- Schemathesis: AI tool endpoints return 501 FEATURE_NOT_INSTALLED when the
  ML bundle is absent (always, in CI). That is expected, not a server bug,
  and the endpoints cannot be fuzzed without the bundle, so exclude them.
  (The ASCII spec-load fix already landed in #344.)
- Extended Matrix: bump the per-test timeout to 600s; edit-metadata over
  every format still exceeded 300s even at 2 forks.
- Coverage: tests now pass (video-speed + timeout fixes); re-baseline the
  branches/functions thresholds to the measured floor with a written reason.
2026-06-24 20:08:40 +08:00
SnapOtterandGitHub 33dfcecd6a test(nightly): stabilize the exhaustive nightly suite (#345)
Triaged the nightly failures (all pre-existing, unrelated to the analytics
work) and fixed the ones with clear root causes:

- video-speed: a 1s tiny.mp4 sped up 2x rounds to ~0.75s, flaking the +/-25%
  duration assertion under heavy CI load. Use the 8s hero.mp4 (still 44.1kHz)
  so rounding is negligible. Verified locally.
- Extended Matrix + Coverage timeouts: full-matrix / coverage-instrumented runs
  starve the heavy media tests under 4 forks at the 30s default. Make maxForks
  env-overridable (VITEST_MAX_FORKS) and run those jobs with 2 forks + a 300s
  timeout so format-matrix conversions and qr-generate stop timing out.
- Device Matrix visual baselines: the update-visual-baselines workflow could
  not start the app ('failed to create database') because it never provisioned
  Postgres/Redis. Add the same services block the e2e jobs use.
- Docker E2E: a container pnpm install network blip exits 254. Add fetch
  retries + a longer network timeout (frozen-lockfile already passes locally).
- Cross-browser: the home page is the tool catalog now (no dropzone), and the
  tool routes moved to /<section>/<toolId>. Point the upload test at a real
  tool page and fix the stale single-segment routes (/resize -> /image/resize,
  etc.).

The flaky/timeout and cross-browser fixes can only be confirmed by the nightly
(they are load- and browser-specific); a fresh nightly run will verify.
2026-06-24 18:23:23 +08:00
SnapOtterandGitHub 5d36ac06d8 feat(analytics): build-time bake + telemetry depth (#336)
Bake PostHog + Sentry into the published Docker image (SNAPOTTER_ANALYTICS
build arg, codegen script). Delete entire consent system. Move event emission
to BullMQ worker. Add cross-tier identity stitching, Sentry performance
tracing on both tiers, frontend funnel events. Fix stateful regex bug.

86 files changed, 1593 insertions(+), 3747 deletions(-)
2026-06-24 11:05:39 +08:00
SnapOtterandGitHub 0c8e2cfcde ci: harden testcontainers against Docker Hub registry flakes (#324)
Disable the redundant ryuk reaper (tests/global-setup.ts stops its containers
explicitly and CI runners are ephemeral) and pre-pull postgres/redis with retry,
so a transient Docker Hub 500 -- as hit pulling ryuk on #320's post-merge run --
cannot fail the suite. Covers ci.yml (unit + integration) and nightly.yml.
2026-06-22 22:26:47 +08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
8c2ddf8b3d chore(deps): bump actions/checkout in the actions group (#295)
Bumps the actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


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

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-22 21:42:25 +08:00
SnapOtterandGitHub 33671d2d92 feat(landing): auto-refresh live GitHub stars and image-pull stats (#292)
Fetch landing stars + image pulls at build time via a shared stats lib, refreshed by a daily cron + authenticated GITHUB_TOKEN. Image Pulls totals live Docker Hub pull_count + a maintained GHCR estimate (ghcr.io has no public pull-count API).
2026-06-22 14:11:48 +08:00
SnapOtterandGitHub 3d9ff1e0d2 fix(api): decode RAW via LibRaw first so DNG processes at full resolution (#289) (#290)
RAW (DNG) processing crashed on ImageMagick's deprecated ufraw-batch
delegate, which fails on modern formats such as iPhone ProRAW DNG.

Root cause: the dcraw_emu (LibRaw) decode tier read the wrong output path.
dcraw_emu APPENDS the output extension (raw-in-X.dng -> raw-in-X.dng.tiff)
but the code looked for raw-in-X.tiff (replaced extension), so readFile threw
on every RAW, the tier silently fell through to ufraw, and the 24MB TIFF
leaked into the temp dir on each attempt.

- Repair the dcraw_emu output path; clean it up in finally (fixes the leak)
- Prefer LibRaw full decode over embedded-preview extraction so a
  full-resolution RAW is never silently returned as a reduced-size preview
  (sample DNG: was 1024x683 preview, now 3474x2314 full)
- Add RAW decode regression tests (DNG full-resolution + all 6 RAW formats);
  these were absent, which let the bug ship
- Install libraw-bin on CI test runners so dcraw_emu is actually exercised
2026-06-22 09:54:04 +08:00