Commit Graph
1726 Commits
Author SHA1 Message Date
SnapOtter 7d099274c1 feat(ci): add verify job to ai-bundles workflow
The verify job runs between build and publish, gating HuggingFace
upload on per-bundle smoke tests. Each of the 14 bundle variants
(7 bundles x 2 arches) is verified independently: SHA256 checksum,
tarball extraction, Python import checks, and functional inference.
2026-06-20 10:03:16 +08:00
SnapOtter 4b5b35186d fix(ci): address spec review findings in verify-bundle.sh
- validate models field in bundle.json
- pipe JSON via stdin instead of triple-quote embedding (injection safety)
- add PNG magic byte validation for background-removal output
- add dimension assertion for upscale-enhance output
- add fixture existence guards before smoke tests
- use --no-index for offline fixup wheel install
2026-06-20 10:01:32 +08:00
SnapOtter 5ee948d36a feat(ci): add verify-bundle.sh for AI bundle smoke testing
Verifies bundle tarballs in 4 phases: SHA256 integrity, extraction
and install into the base venv, Python import checks per bundle,
and a functional inference smoke test per bundle.
2026-06-20 09:56:55 +08:00
SnapOtterandGitHub b847dcc2ab test(features): align manifest assertions with deepsafe repo and best-effort extractedSize
Fix the Unit Tests CI job: bundleRepo now asserts deepsafe/feature-bundles (intentional, temporary); extractedSize relaxed to >= 0 (best-effort field, build script does not measure uncompressed size). sha256 + compressedSize remain strict. Full unit suite: 4546 passed.
2026-06-19 19:15:32 +08:00
SnapOtterandGitHub 91ac583e87 feat(docs): Two-Doors home, otter-orange brand, Pagefind search, and enterprise SSO/SCIM/roles guides
Docs site redesign: Two-Doors home, otter-orange brand, Pagefind search, enterprise SSO/SCIM/roles guides.
2026-06-19 18:46:59 +08:00
SnapOtterandGitHub 35222249cb chore: remove dead landing components and a stale vitest exclude
Remove 3 unreferenced landing components (HowItWorks, ModalityExplorer, TerminalBlock) and a stale vitest exclude.
2026-06-19 18:46:55 +08:00
SnapOtterandGitHub 2bd3e2302a fix: post-2.0 audit bug fixes (404 route, worker logging, outpaint gate, pandoc path)
Surgical post-2.0 fixes: SPA 404 route, worker logging, outpaint gate, pandoc path resolution.
2026-06-19 18:46:52 +08:00
SnapOtterandGitHub 9b64a96bcd fix(docker): restore Postgres/Redis in GPU compose stack + pause image publishing
Restore Postgres 17 + Redis 8 services in docker-compose-gpu.yml; re-add if:false publish pause on the release.yml docker job (main had lost it).
2026-06-19 18:39:35 +08:00
SnapOtterandGitHub 3b50bcdc5c fix(ai-bundles): repair bundle build + publish pipeline (deepsafe repo, CPU provider, manifest)
Bundle build/publish fixes: CPUExecutionProvider in rembg build, pip/import/arm64 deps, hf-CLI publish to deepsafe/feature-bundles, real manifest sha256+sizes, installer fallback repo.
2026-06-19 18:34:33 +08:00
SnapOtter 7f8f6b5e6b docs: add v2.0.0 release delay banner to README
Let users know the latest Docker image is still v1.x and that v2.0
with all 157 tools is coming soon.
2026-06-19 18:18:36 +08:00
SnapOtterandGitHub 7579634633 test: fix 2.0 integration CI -- 202 async fallback + timeout hardening
Fixes all integration CI failures on the 2.0 branch.

## What was broken

Two independent root causes:

1. **202 assertion failures** -- Under 4-fork CI parallel load, the 30s
   `SYNC_WAIT_MS` sync window can expire before a BullMQ worker finishes a
   heavy encode (avif, heic), returning a legitimate `202 {jobId, async: true}`
   instead of `200`. Tests that hard-asserted `200` were spuriously failing.

2. **Vitest timeout race** -- `SYNC_WAIT_MS` (30s) and the default Vitest
   `testTimeout` (also 30s) fired simultaneously. Vitest won the race,
   reporting "Test timed out in 30000ms" instead of the test receiving the
   202 response.

## Fixes

- Added `isAsyncFallback()` helper to four integration test files; validates
  the `{async: true, jobId}` body shape and returns early so the synchronous
  200 path runs full assertions only when warranted.
- Set `vi.setConfig({ testTimeout: 60_000 })` at module level in
  `image-enhancement.test.ts` and `format-matrix-comprehensive.test.ts`,
  giving a 30s buffer between when `waitForJob()` returns 202 and when
  Vitest gives up.
- Bumped explicit matrix timeouts in `format-matrix.test.ts` and
  `new-formats.test.ts` from 30s to 60s for the same reason.
- Installed missing CI doc-engine binaries (qpdf, pandoc, libreoffice,
  pdfcpu) that were causing unrelated integration failures.
- Fixed E2E smoke specs for 2.0 UI changes (modality selector, tool routes,
  validation behavior).
2026-06-19 18:15:20 +08:00
SnapOtter d39091375a chore: remove temporary HF token check workflow and marker 2026-06-19 18:07:29 +08:00
SnapOtter 818ca8beb4 chore: fix YAML indentation in HF check workflow; re-trigger 2026-06-19 18:06:46 +08:00
SnapOtter 6bd8bf724e chore: run HF token CI check (push-triggered) 2026-06-19 18:05:43 +08:00
SnapOtter 837ce2fe86 chore: HF upload CI check workflow 2026-06-19 18:01:04 +08:00
SnapOtter 1473c7cffd chore: remove stale HF token test workflow 2026-06-19 18:00:52 +08:00
SnapOtter 6c779e8382 chore: temporary HF token CI smoke test (will remove) 2026-06-19 17:55:32 +08:00
SnapOtter 60e3008106 docs: deep-audit the API reference pages against code
rest.md: correct the tool response shape (JSON {jobId, downloadUrl, ...} for fast / 202 {jobId, async} for long, not the raw file), fix the SSE jobId source (body, with X-Job-Id only on batch/pipeline ZIP streams), complete the permissions list to all 17, and add ai-canvas-expand.

ai.md: reconcile ~14 AI tool parameter tables to the real Zod schemas (remove-background, upscale, ocr, enhance-faces, colorize, noise-removal, red-eye-removal, restore-photo, passport-photo, erase-object, smart-crop, content-aware-resize, transparency-fixer, image-enhancement), add the 6 missing AI tools (background-replace, blur-background, ocr-pdf, ai-canvas-expand, transcribe-audio, auto-subtitles), and fix the dispatcher script diagram and feature-bundle table.

image-engine.md: add 4 undocumented operations (sharpen, sharpen-advanced, color-blindness, edit-metadata) and fix flip/crop/rotate/convert/compress/strip-metadata/color-channels params, output-format count (13), and the info JSON example. supported-formats.md: output count 14 -> 13. Verified with a clean docs build.
2026-06-18 14:12:28 +08:00
SnapOtter 8532f3227b docs: audit all 157 tool pages against live schemas
Reconcile every tool page's parameters, defaults, and response shape against the tool's Zod settings schema and executionHint in code. Notable fixes: color-palette (add count + format params, hex output, median-cut algorithm), favicon (add 5 params, was documented as having none), qr-generate (add logoDataUri), convert (add ppm/eps/tga formats), video-loudnorm (-16 LUFS not -14), smart-crop (async 202 not sync 200), images-to-video (1080x1080 square), and several output-filename and behavior-note corrections.

Also normalize API endpoint paths to /api/v1/tools/<id> (no modality segment) and standardize curl examples on the Docker API port 1349. Verified with a clean docs build.
2026-06-18 14:12:28 +08:00
SnapOtter d24e244af4 docs: organize tool pages by modality under /tools/<modality>/
Move all 157 tool pages into image/video/audio/pdf/data subfolders so URLs read /tools/<modality>/<id> (e.g. /tools/image/crop). Nest the image sub-categories under an Image group in the sidebar so the nav reads by modality. Add public/_redirects (301, clean + .html forms) mapping every old flat /tools/<id> URL to its new path so inbound links keep working. Rewrite all internal /tools links. Verified with a clean docs build (no dead links).
2026-06-18 14:12:28 +08:00
SnapOtter 03c1a5e36f fix(ci): grant ai-bundles reusable call its required token scopes
The Release workflow failed at startup ("workflow file issue", no jobs
created) whenever dispatched. Root cause: release.yml sets top-level
`permissions: {}`, so the ai-bundles reusable-workflow call inherited an
empty scope set. ai-bundles.yml's jobs declare `contents: read` /
`packages: read`, and GitHub rejects a called workflow requesting scopes
the caller never granted -- validated at startup, before any job runs, so
the whole run fails immediately regardless of the job's `if:`.

Grant the two scopes on the call job. Confirmed on a throwaway branch: a
neutralized dispatch went from startup_failure to a clean run with
ai-bundles correctly skipped.

This unblocks every real release; main's release.yml could not start.
2026-06-17 18:22:15 +08:00
SnapOtter de8bd79b04 test: update pre-existing stale specs for 2.0 multimodal + validation behavior
Fixes a backlog of integration/unit specs that asserted pre-2.0 behavior and
were failing CI (not caused by recent feature work):
- modality-aware empty-input error is 'No file(s) provided', not /no image/i
  (rotate, border, crop, resize, smart-crop, edge-cases, adversarial-extended,
  api, tool-factory-route)
- input validation rejects pre-enqueue with a clean 400 in 'error' (was a worker
  422 in 'details'): create-zip, extract-zip, merge-csvs
- resolveToolPool defaults unknown tools to the system pool (pool-routing)
- /upload and fetch-urls accept non-image content, validated per-tool at process
  time (api, fetch-urls)
- color-adjust legacy aliases were consolidated into adjust-colors: drop the
  removed-alias tests; retarget the format-preservation tests
- xml-to-csv gracefully converts a single non-repeating record to a 1-row CSV
- dropzone is multimodal; image-only filtering is opt-in via fileFilter
- factory-multi-input: register the synthetic test tools in the catalog so they
  route correctly (file modality for concat; image for the validation-prefix test)

Verified locally: unit 4546 passed, integration 8332 passed, typecheck + lint green.
2026-06-17 15:54:48 +08:00
SnapOtter 3726335063 fix(ai): pin rembg to 2.0.69 to keep numpy<2 compatibility
rembg 2.0.70+ requires numpy>=2.3.0, but the AI bundle pins numpy==1.26.4
(mediapipe, realesrgan/basicsr, codeformer, paddle all need numpy<2). The
unresolvable rembg==2.0.75 + numpy==1.26.4 combination broke pip-audit's
dependency resolution (CI red) and the background-removal bundle build. 2.0.69
is the newest rembg with an unconstrained numpy requirement. Verified: pip-audit
resolves with no unignored vulnerabilities on Python 3.11.
2026-06-17 15:54:35 +08:00
SnapOtter d5c855d844 test: align batch empty-input assertion with modality-aware error
The batch endpoint became modality-aware, so an empty request returns the
generic "No files provided" rather than the legacy image-specific message.
The assertion still matched /no image/i (a pre-existing stale spec on main),
so it failed against the correct behavior. Match /no files/i instead.
2026-06-17 14:39:34 +08:00
SnapOtter 620552569e feat!: SnapOtter 2.0.0
Bump all workspace package versions and APP_VERSION to 2.0.0, marking the
official 2.0 release. Removes the stale 1.x .release-notes.md artifact
(semantic-release regenerates release notes). The 2.0/multimodality docs
and rebrand already landed on main via #254 and #261, so this carries only
the version designation forward from the rebrand branch.

BREAKING CHANGE: 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.
2026-06-17 14:33:09 +08:00
SnapOtter 7e3249a998 test: fix stale epub-convert/to-epub async tests
These suites only ever skipped (pandoc is absent in every test env), which hid three latent bugs once the binaries are reachable:

- epub-convert and to-epub are executionHint "long" (async: 202 + jobId), but the tests asserted a synchronous 200 and read the response downloadUrl. Rewrite to the 202 -> poll job row -> download pattern.
- The job-row reads selected schema.jobs.errorMessage, which is not a column (the field is `error` jsonb {message,details}); drizzle threw on the bad select. Read error/error.message instead. (The pdf-chain test had the same bug.)
- The SSRF test asserted the remote <img> URL passes through, but pandoc strips the unmanifested remote ref. Assert the security-relevant property instead: the conversion completes from the book's own content and never fetches+inlines the remote resource (no data: URI).

Verified by building the test image with pandoc: all epub/to-epub tests pass.
2026-06-17 14:28:41 +08:00
SnapOtter 7a205eeb12 fix(api): isolate the factory validation scratch dir from the worker's
The tool factory created its per-request validation scratch dir at tmpdir()/snapotter-scratch/<jobId> -- the same path the BullMQ worker uses for job processing (worker.ts scratchRoot()/<jobId>) whenever SCRATCH_PATH is unset. For sync tools this is harmless because the factory blocks on the worker before its finally cleanup runs. For "long" tools the factory returns 202 immediately and then rm's that dir, racing the worker that is writing the job's input there -- so tools that stage input in the worker scratch dir (epub-convert, to-epub) intermittently failed with ENOENT. Production never hit this because it sets SCRATCH_PATH, diverging the two paths. Give the factory's validation dir a -prep suffix so its cleanup can never touch the worker's job dir.
2026-06-17 14:28:41 +08:00
SnapOtter d46b82e11e test: run the docker suite without the coverage gate
pnpm test:docker ran pnpm test:ci (vitest --coverage), but the lean test image deliberately skips binary-gated tools (AI model bundles, LibreOffice, etc.), so it can never meet the host-calibrated coverage thresholds -- the container exited non-zero on coverage even with zero test failures. Point the compose command at vitest run so test:docker is a clean functional pass/fail gate; coverage stays enforced on host CI where every tool is present.
2026-06-17 14:28:41 +08:00
SnapOtter 2d2f23cff0 test: install enterprise S3 dep and open PS coders in docker test image
Two test-image gaps surfaced by a full pnpm test:ci run:

- s3-storage.test.ts imports @aws-sdk/client-s3 (an enterprise dependency) at module load, but Dockerfile.test never copied packages/enterprise/package.json before pnpm install, so the suite failed to collect. Copy it so the dep installs; the suite then skips cleanly when MinIO is absent.

- EPS batch decode returned 422: ImageMagick reads EPS through the Ghostscript PS coder, but policy.xml left PS/PS2/PS3 at rights=none (only EPS was opened), so convert refused with a policy error before Ghostscript ran. Open the PostScript coders too.
2026-06-17 14:28:41 +08:00
SnapOtter 63a2d309ce test: honor SYNC_WAIT_MS in docker tests instead of clobbering it
tests/setup/per-fork-env.ts hardcoded SYNC_WAIT_MS=30000 on every fork, overriding whatever the container set, so the docker test image could never grant heavy ops a wider sync window. A 12MP stress-image enhance takes ~34s on the macOS Docker VM (Sharp runs 2-3x slower there), just past the 30s window, so the factory returned 202 and three sync-asserting image-enhancement tests failed.

Honor a higher SYNC_WAIT_MS when provided (30s floor preserved for host/CI), raise it to 120s in docker-compose.test.yml, and make the vitest test/hook timeouts env-overridable so a slow-but-correct job returns 200 rather than tripping a framework timeout. Host and CI behavior is unchanged.
2026-06-17 14:28:41 +08:00
SnapOtter 1f5b222267 test: fix docker test-image env and container-specific test guards
Make the full pnpm test:docker suite pass the env-dependent tests (~85 failures):
- Dockerfile.test: ENV LD_LIBRARY_PATH=/usr/local/lib so the built libheif 1.21 is not shadowed by the base image's older system libheif (heif-dec failed with an undefined-symbol error -> 'No HEIF decoder found' on 72 HEIF tests); add libjxl-tools (JXL) and ghostscript + the ImageMagick policy.xml EPS allow-edit.
- docker-compose.test.yml: SYNC_WAIT_MS=30000 so sync-wait image tools do not fall back to 202 under single-container contention (10 tests).
- install_feature.py: guard tarfile.extractall(filter='data') behind Python>=3.12 (bookworm ships 3.11); the manual entry guards already protect.
- feature-status.test.ts / docker-file-secrets.test.ts: skip the two cases that cannot hold inside the container (/.dockerenv always present; root bypasses chmod). Verified on host: all still pass.
2026-06-17 14:28:41 +08:00
SnapOtter b4470cac4b test: exclude qa Playwright specs and stale landing tests from vitest
A bare 'vitest run' (pnpm test:ci) collected tests/qa/*.qa.spec.ts (Playwright specs importing @playwright/test -> 'test.describe() called here') and tests/unit/landing/*.test.tsx (React tests importing @landing/app/* and @landing/components/* paths that no longer exist after the landing Next.js->Astro migration). Exclude both. Surfaced by the full pnpm test:docker run; the host unit suite is now green.
2026-06-17 14:28:41 +08:00
SnapOtter 37b9c6d7bf fix(api): reject non-PDF inputs in pdf-to-image
acceptedInputs is [.pdf], but pdf-to-image validated by calling mupdf.openDocument(buf, 'application/pdf'); mupdf sniffs the real format and opens JPEGs/PNGs/etc. as 1-page image-documents, returning 200. So non-PDF (incl. truncated/hostile) inputs were accepted, violating the contract and the hostile-input robustness check -- the one pre-existing failure surfaced by the full integration run. Gate all three endpoints (convert/info/preview) on the %PDF- magic bytes. Verified: truncated.jpg -> 400, valid PDF -> 200; the hostile-inputs test passes.
2026-06-17 14:28:41 +08:00
SnapOtter 965501aef9 fix(api): log only genuine processing faults at error level
The earlier worker error-logging change logged every job failure at error level, including expected InputValidationErrors (e.g. 'needs at least two audio files') -- flooding error logs with non-actionable user-input rejections (visible across the integration run). Skip validation errors (matched by name, which survives the BullMQ boundary); genuine faults still log at error, and all failures still reach the OTel span.
2026-06-17 14:28:41 +08:00
SnapOtter c483897452 fix(api): drop app-logger import from media-input; update stale errors mock
The full unit suite surfaced two issues from earlier commits on this branch. (1) Importing the app logger into media-input.ts pulled logger.ts -- which builds its pino file transport at module load via join(env.LOG_DIR, ...) -- into the unit-test import graph, throwing at collection time wherever LOG_DIR is unset (integration tests set it; unit tests do not). A low-level modality handler should not depend on the app logger, and a corrupt upload is an expected user error, so drop the import and keep the clean validation message. (2) tool-factory-route.test.ts mocked errors.js without the new friendlyError export; add it.
2026-06-17 14:28:41 +08:00
SnapOtter 9c250d244f test: add regression coverage for the video QA fixes
friendlyError unit test (incl. the false-positive guard); gated integration tests for multi-file video batch and a multi-step video pipeline (regression for the modality-aware batch/pipeline fix). All pass locally; existing image batch (36) and pipeline (37) suites remain green, and the existing gif-to-video webm test now passes with the pix_fmt fix.
2026-06-17 14:28:41 +08:00
SnapOtter 6220885e0c fix(api): narrow friendlyError matching to avoid collapsing valid messages
The raw-dump detector matched broad content keywords ('conversion failed', 'pixel format', bare 'ffprobe') that appear in legitimate validation messages (e.g. 'SVG conversion failed'), which would wrongly collapse them to the generic error. Narrow it to the unambiguous 'ffmpeg/ffprobe exited N:' prefix, python tracebacks, and crashes; longer/multi-line raw dumps are still caught by the length/line-count check. Found during self-review.
2026-06-17 14:28:41 +08:00
SnapOtter e52e0bb3d5 fix(api): propagate lenient structural-validation flag to batch + pipeline
Main already reuses the per-modality input handlers in batch and pipeline
(#244), so the modality-aware reuse from the video QA sweep was redundant.
Port only the remaining unique piece: pass lenient: skipStructuralValidation
to the handler so batched/pipelined PDF tools that opt out of structural
validation behave like the single-file factory path. Keeps main's safer
explicit image-decode chain (HEIC/RAW/SVG/autoOrient) and AI fileId threading.
2026-06-17 14:28:22 +08:00
SnapOtter 3fc4149143 fix(api): stop leaking raw ffprobe stderr in media validation errors
An unrecognized/corrupt media upload threw an InputValidationError whose message embedded the raw ffprobe stderr ('ffprobe exited 1: ...'). Return a clean 'Unrecognized <kind> file' message to the client and log the raw probe failure via logger.warn instead. Found via the round-2 hostile-input gap test.
2026-06-17 14:22:16 +08:00
SnapOtter 4af4bfa8eb fix(api): return user-safe processing errors, keep raw stderr in logs
Add friendlyError() which collapses raw external-tool failure output (ffmpeg/ffprobe/LibreOffice/qpdf/etc.) into one generic sentence while preserving intentional validation messages and scrubbing internal paths. Apply it at every client-facing error surface in the tool factory and job worker (sync 422, async SSE, pipeline + batch finalize). The full error is still recorded server-side via request.log.error / logger.error and telemetry.
2026-06-17 14:22:16 +08:00
SnapOtter e96c314ab9 fix(media): encode gif-to-video WebM as yuv420p
GIFs decode to bgra/gbrap (alpha); libvpx-vp9 rejects those pixel formats so the encoder never opened (ffmpeg exit 234), breaking GIF->WebM for essentially every GIF. Flatten to yuv420p in the webm branch, matching what the mp4 branch already does.
2026-06-17 14:22:16 +08:00
SnapOtter 3120e6708d fix(editor): apply layer effects + object flip, add Beta badge, repair e2e specs
While getting the editor e2e suite green, three "stale test" failures turned
out to be real bugs (per the reporter's hunch that tests might be catching
real issues):

- Layer effects (drop shadow, glows) never applied. The panel wrote effects
  into `attrs.effects` through updateObject, but the panel and renderer both
  read the object's top-level `effects`, so the toggle never persisted. Add a
  dedicated `setObjectEffects` store action and route the panel through it.
- Object flip (transform tool) did nothing. No object renderer applied
  `scaleX`/`scaleY`, and the flip negated scale without compensating position.
  Apply scale in the renderers and flip in place: mirror points for stroke
  objects, negate scale + shift position for sized objects.

(The paint-bucket / pixel-tool coordinate bug and the broken-at-non-100%-zoom
export were fixed in the preceding #259 change.)

Also adds a small "Beta" badge to the editor (welcome heading + nav link) and
repairs ~18 stale editor e2e specs whose selectors/assertions had drifted from
the current UI: the options bar is `h-9` not `h-10` (added a stable
`data-testid`), the menu bar is `h-8`/`bg-background`, the flip button
aria-labels are lowercase, the welcome "Image Editor" heading collides with an
sr-only `<h1>`, the color-picker tabs need a role-scoped selector, and the
magic-wand / flip tests now use deterministic setup and assert the actual
effect instead of fragile screenshot diffs.
2026-06-17 14:21:35 +08:00
SnapOtter 81e16d7ce6 fix(editor): capture document pixels without the zoom/pan transform (#259)
Every editor tool that reads or writes raster pixels exported the stage with `stage.toCanvas({ x: 0, y: 0, width, height })`, which bakes in the stage's zoom/pan transform. The captured buffer was the *viewport* (the document scaled and offset by the current zoom/pan), not the document in its own coordinate space, so tools sampled and wrote the wrong pixels: the paint bucket produced a misplaced black rectangle instead of flood-filling the click, the eyedropper read the wrong colour, the magic wand selected the wrong region, and PNG/clipboard export silently produced a scaled/offset image at any zoom other than 100%.

Add `captureDocumentCanvas()`, which normalizes the stage to the document size with an identity transform, renders, captures, and restores -- all synchronously, so there is no visible flicker. Route every pixel capture through it: fill, magic wand, clone stamp, eyedropper, dodge/burn, blur/sharpen/smudge, the adjustments histogram, and the exporter.

The 'rulers render as black bars' part of #259 was fixed in the preceding editor-layout change (#258).

Adds editor-tool-coordinates.spec.ts asserting the paint bucket fills at the clicked location.
2026-06-17 14:21:35 +08:00
SnapOtter 063a2e47e2 fix(editor): fill canvas viewport, fix black rulers, add resizable panel (#258)
The image editor canvas only used part of the viewport, and the right sidebar was a fixed width that could clip its controls on shorter screens.

- Canvas: the canvas container used `flex-1`, but its parent wrapper in editor-page.tsx was not a flex container, so it collapsed to the Konva Stage's content height (~600px), leaving a large inert region below. Make the wrapper a flex container so the canvas fills the available area.

- Rulers: ruler background/ticks were set via `ctx.fillStyle = "var(--color-card)"`, which canvas 2D cannot parse, so the default black fill remained and painted the rulers as solid black bars. Resolve the theme tokens to concrete colors from computed style at draw time (theme-aware).

- Right panel: add a left-edge drag handle to resize the panel (240-480px, persisted to localStorage) and `min-h-0` so the tab content scrolls internally instead of pushing the color controls off-screen.

Adds editor-layout.spec.ts (canvas-fill + resize) and a ruler-not-black regression test. All 7 targeted editor e2e tests pass.
2026-06-17 14:21:35 +08:00
SnapOtter 609cd6b609 chore(github): redesign issue intake forms and routing
Overhaul the bug report form and restructure issue intake for 2.0:

- Bug report: split actual vs expected behavior, add Affected area and
  Specific tool fields for triage, replace the self-defeating required
  "latest version" checkbox with a Version field that accepts a tag,
  release, or commit (always answerable from any install), modernize
  Docker guidance to the Compose stack, add Host OS, and fix the
  previously required image-tag field that source users could not fill.
- Move feature requests to GitHub Discussions: delete the feature_request
  issue form, rename the orphaned discussion form to ideas.yml so it binds
  to the built-in Ideas category, and point config.yml there.
- Add Translation and Documentation issue forms routed to existing labels.
- Remove the roadmap-update discussion form (roadmap is private).
- Reconcile CONTRIBUTING.md, SUPPORT.md, and the published docs
  contributing guide with the new routing.
2026-06-17 14:21:18 +08:00
SnapOtterandGitHub ecc0a45f0e docs: per-tool reference pages for all 157 tools (+ fix docs build) (#261)
* fix(docs): keep gray-matter on js-yaml 3 so the docs site builds

The js-yaml >=4.2.0 override from #257 forced js-yaml 4 onto gray-matter (used by vitepress and vitepress-plugin-llms), which calls the removed yaml.safeLoad and broke `vitepress build`. Scope a gray-matter>js-yaml ^3.14.1 override so gray-matter keeps the v3 API (build-time, trusted frontmatter only) while app code stays on js-yaml 4.2.0+.

* docs: add per-tool reference pages for all 157 tools, with a modality sidebar

Generate /tools/<id> pages for the 104 tools that lacked one (video 29, audio 17, document 36, data 10, and 12 newer image tools), matching the existing page format (API endpoint, parameters from the OpenAPI spec, curl example, response, notes). Async/AI tools document the 202+SSE flow and feature-bundle requirement.

Sidebar: add Video / Audio / PDF & Documents / Data groups with per-tool links, fold the 12 new image tools into the existing image categories, and replace the placeholder rest.md-anchor group. Docs site builds cleanly (157 pages, no dead links).
2026-06-17 11:11:37 +08:00
SnapOtterandGitHub c3d7411c67 chore(landing): upgrade to Astro 6 (resolves the astro security advisory) (#260)
Bump astro ^5.8.0 -> ^6.4.7, @astrojs/sitemap ^3.3.0 -> ^3.7.3, @astrojs/check ^0.9.0 -> ^0.9.9.

The landing uses none of the Astro APIs removed in v6 (no Astro.glob, ViewTransitions, or content collections), so no code changes were required. Astro 6 runs on the repo's existing Vite 8 (the monorepo is already on Vite 8 via the shared override).

Verified: landing build (165 pages) + astro check (0 errors); full monorepo build/typecheck/lint all pass; homepage renders correctly.
2026-06-17 11:11:34 +08:00
SnapOtterandGitHub 79233ff19d chore(deps): patch Dependabot security advisories (esbuild, qs, uuid, yaml, js-yaml, babel, otel, rembg) (#257)
Resolve the actionable Dependabot alerts via pnpm overrides (for transitive deps) and a Python pin bump.

- pnpm overrides: esbuild >=0.28.1 (the lone high-severity alert), @babel/core >=7.29.6, @opentelemetry/core >=2.8.0, js-yaml >=4.2.0, qs >=6.15.2, uuid >=11.1.1, yaml >=2.8.3
- rembg 2.0.62 -> 2.0.75 in requirements.txt and requirements-gpu.txt

Verified: pnpm install, typecheck, lint, and full build all pass.

NOT included: the astro advisory requires Astro 5 -> 6 (a major, breaking framework upgrade), which warrants its own migration PR rather than a security bump.
2026-06-16 21:49:53 +08:00
SnapOtterandGitHub ae32c25301 docs: complete the API reference catalog with video/audio/document/data tools (#256)
rest.md and the docs sidebar still listed only image tools. The OpenAPI spec (#254) now covers all 157 tools, but the hand-written reference lagged.

- rest.md: add Video (29), Audio (17), Document (36), and Data (10) tool tables, and add the 12 newer image tools into their existing category tables (168 tool rows total)
- sidebar: add a "Video, Audio, Document & Data" group linking to the new rest.md sections, so the nav reflects all modalities without needing per-tool pages

Docs site builds cleanly (no dead links). Per-tool doc pages for non-image tools are intentionally not generated here; the REST reference + Scalar + /llms.txt cover them.
2026-06-16 21:49:48 +08:00
SnapOtterandGitHub 17726ae59d docs: multi-modality rebrand, 2.0 architecture accuracy, and full OpenAPI coverage (#254)
* docs: rebrand from image-only to multi-modality across docs and metadata

SnapOtter expanded from image-only to 157 tools across 5 modalities
(image, video, audio, document/PDF, data). Update all product-level
copy, metadata, and i18n that still framed it as an image-only tool.

- README, package.json, root llms.txt: multi-modality framing, 157 tools
- OpenAPI info + tags, generated /llms.txt tagline (docs.ts)
- VitePress docs site: hero, getting-started, architecture, security,
  deployment, configuration, developer, supported-formats
- i18n: 10 product keys across all 21 locales (hero, app description,
  privacy notes, AI features, progress messages, getting-started)
- web/demo/landing meta + privacy copy, COMMUNITY_GUIDE, .env.example

Stale tool counts (53/50+/52/70+/35) corrected to 157 throughout.
Database/container deployment claims left unchanged (out of scope).

* docs: fix stale post-rebrand test assertions and README language list

- tests/e2e-docs/homepage.spec.ts: assert the current docs homepage (file toolkit, 157 tools, 5 modalities) instead of the old image-only strings
- tests/unit/api/docs-route.test.ts: sync the reproduced llms.txt tagline with docs.ts
- README.md: 21 languages with the correct list (add Swedish and Chinese Traditional, drop Czech which is not supported)

* docs: correct 2.0 architecture references (Postgres 17 + Redis 8, 3-container stack)

The docs and metadata still described the 1.x stack (SQLite, single container, p-queue). Update them to the current 2.0 reality.

- README: replace the broken single-container `docker run` quick-start with the real Docker Compose stack (app + Postgres 17 + Redis 8); fix the "no Redis, no Postgres" feature bullet
- package.json: description no longer claims a single container
- apps/docs: rewrite database.md for Postgres; configuration.md DB_PATH -> DATABASE_URL + REDIS_URL; architecture.md SQLite/p-queue/better-sqlite3 -> Postgres/BullMQ/pg and add media-engine + doc-engine; developer/security/deployment/docker-tags/getting-started/contributing compose examples now include postgres + redis; index.md + api/ai.md AI count 16 -> 19
- SECURITY.md: Drizzle (SQLite) -> (PostgreSQL)
- landing: enterprise/FeatureHighlights single-container wording; TrustSignals/ToolGrid 150+ -> 157 (dynamic); Pricing/FAQ 15 -> 19 AI tools

* docs(api): document all video, audio, document, and data tool endpoints in OpenAPI

The spec covered only image tools; the Scalar UI and the generated /llms.txt and /llms-full.txt inherited that gap. Add the 104 missing tool endpoints so the API docs match the code.

- Video: 29 endpoints (most long/async; auto-subtitles is AI)
- Audio: 17 (transcribe-audio is AI)
- Document/PDF: 36 (ocr-pdf is AI; conversions are long/async)
- Data: 10
- Image: 12 newer tools (background-replace, blur-background AI; histogram/lqip-placeholder/sprite-sheet custom responses; barcode-generate uses a JSON body)

Each schema is derived from the tool's Zod validator and executionHint (fast -> 200, long -> 202+SSE, AI adds 501 FeatureNotInstalledError, multi-file inputs as arrays), referencing the existing shared schemas. Tool path entries: 64 -> 168. Spec parses as valid YAML with no duplicate paths and only known $refs.
2026-06-16 18:04:52 +08:00