mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
dadf76689933faa8208de9730525409e2497518f
443
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
dadf766899 |
fix(migrator): correct and harden the 1.x to 2.0 SQLite import (#434)
* feat(api): parse DATA_DIR from env for 1.x import auto-detection Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w * test(migrator): build 1.17.2 fixtures by replaying legacy migrations Discovered the legacy migrations seed a Default team (0005) and builtin roles (0007), so the replayed fixture carries them. Seed uses a distinct custom team. Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w * fix(migrator): self-adjusting column copy, jobs.status map, drop sessions, advisory lock The importer now inserts only the intersection of source and live target columns, so the three analytics_* columns 2.x dropped no longer break the first users INSERT (and future dropped columns are handled generically). jobs.status is mapped onto the 2.x enum (error->failed). Sessions are no longer migrated. A pg_advisory_xact_lock serializes concurrent replicas. Includes login-after-migrate and library assertions. Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w * test(migrator): CI drift guard fails when a required column is unfillable from 1.17.2 Introspects every NOT-NULL-no-default column of each migrated table in the current schema and asserts the engine can fill it from a real 1.17.2 source. Turns a future breaking schema change into a PR-time failure instead of a production import break. Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w * feat(migrator): orchestrator with detection, boot states, marker, blob count sqlite-import.ts owns source resolution (explicit path, 'off' sentinel, DATA_DIR probe), the four boot states (import/leftover/locked/none), the persisted sqlite_import marker, and a read-only library-blob count. runBootImport wires them together and catches TargetNonEmptyError as a benign multi-replica skip. Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w * feat(api): route boot through the 1.x import orchestrator; hide marker from non-admins index.ts now calls runBootImport (which owns detection + the four boot states) instead of the inline SQLITE_MIGRATE_PATH block. The sqlite_import marker is added to SENSITIVE_KEYS (but not REDACTED_KEYS) so admins see the counts for the banner while non-admins don't see the key at all. Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w * feat(migrator): add analyzeSqlite + dry-run/verify CLI analyzeSqlite is a read-only pre-flight (no live Postgres): per-table row counts, library-blob presence, and out-of-enum job statuses. The migrate:sqlite CLI now lives in the orchestrator and supports --dry-run/--verify (prints the analysis and exits without writing) alongside the existing import and --force. Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w * docs: add 1.x to 2.0 upgrade guide; fix volume-name casing New apps/docs upgrade guide covering auto-detect, the SQLITE_MIGRATE_PATH override + off opt-out, the dry-run, what carries over, locked-state recovery, and non-destructive rollback. Leads with 'back up the WHOLE /data volume, not just snapotter.db' because 1.x WAL mode leaves data in snapotter.db-wal (surfaced by the real-image upgrade test). Standardizes README/DOCKERHUB compose volume names on the canonical SnapOtter-data casing so they match the repo compose and don't orphan an upgrader's volume. Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w * feat(web): admin 1.x migration banner + 21-locale strings A one-time admin banner reads the sqlite_import marker from /v1/settings and shows the import result (user + saved-file counts) on success, or a warning when a 1.x database was found but not imported. Dismissal persists to a sqlite_import.dismissedAt settings key. shouldShowMigrationBanner/parseMigrationMarker sit in feedback.ts with the other shouldShow helpers; strings added to en.ts and all 20 other locales. Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w * style(landing): biome-format Hero.astro trustBadges array Pre-existing formatting drift on main (its Lint check was skipped on the merge that introduced it); this PR's full Lint run surfaced it. Formatting-only, applied via the repo's own biome formatter to unblock the required Lint check. Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w |
||
|
|
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 |
||
|
|
7b04317ed2 |
feat: add a Keep it free sponsor button to the top nav (#427)
Adds a prominent Keep it free sponsor button to the top nav, linking to https://github.com/sponsors/snapotter-hq. Solid orange pill on desktop (left of the avatar), orange heart icon on mobile. Opens in a new tab with rel=noopener noreferrer, so no referrer or user data leaks, and it adds no passive network activity (offline-mode compatible). Fires an opt-in, property-less sponsor_clicked analytics event. Adds sidebar.sponsor and a11y.sponsorLink across all 21 locales. Claude-Session: https://claude.ai/code/session_01DnYLLA5z4Uf1GDeEPENVgr |
||
|
|
3ae48cc76a |
fix(i18n): complete the Italian feedback translations (#426)
Lands albanobattistella's Italian translation of the feedback strings from #425, with two mistyped keys corrected (great and adminCardDescription). Verified against main: typecheck 0 errors, i18n parity 23/23, Biome clean. Co-authored-by: albanobattistella <34811668+albanobattistella@users.noreply.github.com> |
||
|
|
6e3a14ec6b |
fix: remove automatic third-party egress of user data + optional strict offline mode (OSM tiles, Scalar fonts, editor fonts, AI model downloads) (#422)
* fix: remove all automatic third-party egress (OSM tiles, Scalar fonts, editor Google Fonts, AI model download fallbacks) Phone-home audit follow-up. The product no longer makes any automatic third-party request; user-initiated click-outs stay, and production now fails closed on missing AI models. 1. GPS leak via OSM tiles: the strip-metadata panel auto-loaded tile.openstreetmap.org tiles encoding the photo's GPS position. The Leaflet mini-map is gone; coordinates render as text plus an explicit View on map link (openstreetmap.org, opens on click only). Removed tile.openstreetmap.org from the CSP img-src, dropped the leaflet dependency, added the viewOnMap i18n key to all 21 locales. 2. Scalar docs fonts: /api/docs loaded Inter and JetBrains Mono from fonts.scalar.com. Scalar now renders with withDefaultFonts: false and both --scalar-font and --scalar-font-code pinned to system stacks; fonts.scalar.com removed from the docs CSP font-src. Verified by injecting GET /api/docs/: config carries withDefaultFonts false and the served page has no fonts.scalar.com reference. 3. Editor Google Fonts: the editor font picker built fonts.googleapis.com stylesheet URLs for 25 web fonts the served CSP already blocked. The remote loading path is deleted; the picker now offers system fonts only, with a SELF_HOSTED_FONTS seam (FontFace API, same origin) for bundling fonts later. Unknown families saved in old documents fall back to the browser default. 4. Python sidecar fails closed on model downloads: new packages/ai/python/offline_guard.py gates every runtime download fallback (inpaint, outpaint, restore, noise_removal, detect_faces, enhance_faces, face_landmarks, red_eye_removal, remove_bg, ocr, transcribe, upscale) behind SNAPOTTER_ALLOW_MODEL_DOWNLOAD=1 with an actionable error. Bundled models keep working untouched. 5. OCR and transcription library-internal downloads: unbundled PaddleOCR language and detection fallbacks now raise the guard error naming the language instead of resolving models over the network; faster-whisper gets local_files_only when downloads are off. 6. GFPGAN and CodeFormer cwd-relative weights: facexlib and codeformer-pip resolve helper weights relative to the process cwd and fetch them from GitHub when absent. They are now symlinked from the installed bundle files under MODELS_PATH/gfpgan/facelib before the libraries load, failing closed when unresolvable. Defense in depth: HF_HUB_OFFLINE=1 and TRANSFORMERS_OFFLINE=1 are set in the runtime image and in the sidecar spawn env; install_feature.py lifts them for user-initiated bundle installs and restores them afterwards (it can run in-process inside the dispatcher). SNAPOTTER_ALLOW_MODEL_DOWNLOAD is documented in .env.example, default off. Validation: typecheck 9/9 workspaces, Biome clean on touched files, 5178 unit tests pass, py_compile on all touched scripts, guard behavior exercised in both dispatcher exec and per-request import modes, zero remaining runtime references to the three hosts. Docker build and live AI inference need post-merge verification on the GPU host. Claude-Session: https://claude.ai/code/session_01XGB4pGvTvb7sUX4JN745U7 * fix: allow AI model downloads by default, make strict offline mode opt-in Product call: ease of use first. The download gating from the previous commit inverts its default: runtime model fetches (public model weights only, never user data) are allowed out of the box so AI tools self-heal, and SNAPOTTER_ALLOW_MODEL_DOWNLOAD=0 becomes the explicit strict offline mode for airgapped deployments, where every fallback raises the actionable error instead of fetching. Changes: offline_guard blocks only on an explicit 0/false; the unconditional HF_HUB_OFFLINE/TRANSFORMERS_OFFLINE image ENV is removed and bridge.ts sets those flags for the sidecar only in strict mode; .env.example documents the new default; install_feature's lift/restore stays. All bundled-path preferences, pre-existence checks, and symlink pre-placement remain, so installed bundles never trigger a download. The OSM, Scalar font, and editor font fixes are unchanged. Validation rerun: typecheck 9/9, Biome clean on touched files, 5178 unit tests pass, py_compile on touched scripts, guard behavior verified for unset/1 (allowed) and 0/false (blocked with the new message). Claude-Session: https://claude.ai/code/session_01XGB4pGvTvb7sUX4JN745U7 |
||
|
|
8b3f1e6884 |
fix: stamp SnapOtter as Producer on generated PDFs (#416)
Conversion engines wrote their own names into PDF metadata: LibreOffice, Ghostscript, pdfcpu, WeasyPrint, and PDFKit all stamped Producer/Creator on generated files. A new doc_scrub_meta docs-profile script (PyMuPDF) rewrites both fields to SnapOtter and drops the stale XMP copy; the worker applies it to the 25 PDF-generating tools before outputs reach object storage. Best effort by design: any failure keeps the original bytes and only logs a warning. Deliberately untouched: tools that edit the user's own PDF and preserve its metadata (qpdf edits, sign, flatten), encrypted outputs (copied through), and pdfa-convert, where a metadata rewrite risks PDF/A conformance. Claude-Session: https://claude.ai/code/session_01XGB4pGvTvb7sUX4JN745U7 |
||
|
|
b4375e558d |
fix: harden install queue/dispatcher lifecycle and repair review-sweep regressions (#395)
Fixes 15 defects found by a max-effort multi-agent review of the last 6 merged PRs (#388, #390, #391, #392, #393, #394), all adversarially verified before fixing. Install queue + dispatcher (the serious cluster): - features.ts: finalize the installer child exactly once. A failed spawn fires both "error" and "close", and the second event released the file lock and active slot that pump() had just handed to the next queued bundle, letting two pip processes write the same venv concurrently. Outcome recording now happens before pump() so the next bundle's first progress frame cannot race the previous install's bookkeeping. - feature-status.ts: keep failed-install errors in a per-bundle map instead of the single progress slot. With the queue auto-starting the next install, the slot was overwritten within seconds and a failed install vanished without ever surfacing to GET /features. - bridge.ts: scope child lifecycle per process (stopped-children set + request generation tags) instead of an instance-wide shuttingDown flag that the next spawn reset. A stale SIGTERMed child's late close event could record a phantom crash (5 of which permanently disable the dispatcher), null out the freshly spawned child, and reject the new child's pending requests. The request-timeout kill path still counts as a real crash. - install_feature.py: the pre-write disk re-check measured ai_dir's filesystem even when budgeting the cross-filesystem copy that lands on the venv's disk; now each budget is checked against the filesystem the bytes actually land on, so ENOSPC cannot strike mid-write and leave site-packages half overwritten. Behavior regressions: - embed-subtitles: preserve pre-existing subtitle tracks (0:s?) and MKV attachments (0:t?) that the -map 0:v:0/0:a? rewrite silently dropped; data streams stay unmapped on purpose (the actual MPEG remux fix). The new subtitle maps first so the language tag hits the right stream. - usage-survey-overlay: fail closed when the settings fetch fails; the fail-open path rendered the blocking survey against an unhealthy API and soft-locked admins, the lock-out class #392 fixed. - features-store: queued bundles poll instead of each holding an SSE connection (Install All could pin 7 EventSources and exhaust the browser's 6-per-origin HTTP/1.1 limit, hanging the whole app); listenToProgress closes any prior stream and stops any poll before subscribing; installAll skips bundles already installing or queued. Contracts, tests, i18n: - openapi.yaml: add "queued" to the features status enum and document downloadBytes/installedBytes (Schemathesis conformance). - feature-lifecycle e2e: queue transcription (~0.5 GB) instead of ocr (~6 GB) and give the test a budget that covers both install drains (the stacked waits exceeded the old 900s timeout). - docker-compose.qa.yml: parameterize the host port (QA_APP_PORT) so QA_PROJECT_NAME concurrent stacks can actually bind. - compare + watermark-image: restore per-input error attribution ("Invalid first/second image", "Invalid watermark image") lost in the shared-handler migration. - ai-features-section: the "{size} on disk" suffix now goes through i18n; key added to all 21 locales. - watermark-image + content-aware-resize: migrate to the shared inputHandlerFor("image") chain like compare/vectorize/compose, fixing drift in the inline copies (no SVG sanitize, no RAW extension hint, no AVIF probe). Verified: typecheck across 9 workspaces, Biome clean on all changed files, 584 targeted unit tests and 249 integration tests green (including real-ffmpeg embed-subtitles runs). One unit test updated to the new poll-while-queued contract with a single-EventSource assertion. Claude-Session: https://claude.ai/code/session_017mR1HiHaf3a1BmUtrHX4j3 |
||
|
|
b37faed95f |
fix: QA sweep - tool routes, security, i18n, a11y, + AI bundle install hardening (#393)
* fix(api): correct format/filename/container handling across tool routes Found during a comprehensive QA sweep exercising every tool against its full accepted-format matrix: - watermark-image, compose: preserve the requested output format and a matching download filename/extension instead of always emitting the source format - compose: crop oversized overlays to the visible base area instead of crashing Sharp's composite, and reject only overlays fully outside the base image instead of any oversized one - compare, vectorize: switch to the shared image input handler so filenames and formats like .svgz/.tga/RAW survive validation instead of being rejected pre-processing - tool-factory, images-to-video: normalize frames through Sharp before handing them to FFmpeg, fixing GIF/AVIF/RAW image-to-video jobs that previously failed or hung - media-tool, replace-audio, embed-subtitles: fix legacy container MIME/codec handling for MPEG sources and subtitle remux cases - files: expand download MIME mapping for text/data/document/video/audio outputs that were falling back to a generic content type - convert-document/presentation/spreadsheet: same-format conversions now return the original validated file instead of erroring or producing corrupt tiny output Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * fix(web): dropzone a11y, stale localStorage getter, dead code - dropzone: stop making the whole drop-zone section clickable/focusable. A section acting as an interactive element around a real upload button is a nested-interactive-element anti-pattern that confuses screen readers; drag-and-drop doesn't need focus semantics, only the button fallback does. Keeps that button semantic and keyboard-reachable. Updates the two e2e call sites that clicked the section directly. - api, use-auth: read through window.localStorage via the existing API storage helper instead of the bare global, which resolves to Node's experimental localStorage getter under Vitest and threw - find-duplicates-settings, info-settings, login-page: remove dead code (unused zip-download handler, a stale mount-only effect dependency that left cached info stuck at reused indices, an unused response variable) Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * fix(i18n): pt-BR, zh-CN, zh-TW were silently falling back to English The locale loader looked up dynamic-import exports by the raw locale code (mod["pt-BR"], mod["zh-CN"], mod["zh-TW"]), but those three modules export camelCased bindings (ptBR, zhCN, zhTW) since identifiers can't contain hyphens. The lookup returned undefined and every consumer silently fell back to English for these three locales. Replaces the generic lookup with explicit per-locale loaders so the mapping can't drift out of sync again. Also updates the dropzone helper copy across all 21 locales to match the drag-only dropzone wording from the previous commit. Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * fix(docs): clear build warnings in the VitePress site - config.mts: add an onwarn handler for the @vueuse INVALID_ANNOTATION warnings emitted during the docs build - deployment.md: the caddyfile code fence language isn't a shiki grammar VitePress ships with, so it warned on every build; use txt instead Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * test(qa): update QA harness for the drag-only dropzone and regen metadata - api-sweep, qa-helpers, verify-ai: add JSON-body tools, multi-input secondary fixtures, async polling for slow valid jobs, 501 FEATURE_NOT_INSTALLED skip handling, and safer per-tool settings - input-preview, pipeline-ui specs: update upload flow for the drag-only dropzone surface - add tests/fixtures/data/valid/chart.json, a valid chart fixture the updated helpers route to - regenerate tools-meta.json against current TOOLS[] Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * fix(security): close a login timing side-channel, harden zip-slip tests Found during a black-box security sweep of the real auth-enabled production container: a nonexistent username returned 401 in ~3-10ms, while a wrong password for a real user took ~35-42ms, because scrypt verification only ran when a user row existed. That timing gap lets an attacker enumerate valid usernames without ever guessing a password. Now runs verification against a cached dummy hash on the unknown-user path too, so both cases cost the same regardless of outcome. extract-zip already had a relative-traversal regression test (../evil.txt), but its absolute-path rejection branches (name.startsWith("/") / startsWith("\\")) had none. Added the three missing cases: deep relative traversal, absolute Unix path, and Windows-style absolute path. Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * test(qa): add UI-driven AI bundle install scripts QA_PROMPT.md's Phase 2 requires installing AI models the way a user does -- through the UI, on demand from HuggingFace -- and treats the curl-based admin install endpoint as fallback-only. Nothing in the harness actually drove that flow; tests/qa/seed-ai-models.sh installs via docker exec + pip, which is further from a real user than even the API fallback. install-ai-bundles-ui.mts logs in, opens Settings > AI Features, screenshots the pre-install state, clicks Install All, and screenshots progress -- then exits, since installs continue server-side once triggered. verify-ai-install-complete.mts polls bundle status, screenshots the completed state, and runs one real tool per installed bundle to prove the freshly-downloaded model actually executes. Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * fix(qa): correct the apiToolPath import in the AI verify script Dynamic import of the package name failed under tsx's module resolution from apps/api's node_modules context; use the same relative-path import api-sweep.mts already uses successfully. Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * fix(web): correct AI bundle size estimates shown before install Measured real downloads during GPU-node QA verification: photo-restoration pulls ~4.4GB (was advertised as 800MB-1GB, off by 4-5x) and ocr pulls ~5.5GB (was advertised as 3-4GB). Both estimates only accounted for model weights, not the pip dependencies (torch/paddle) that come down with them. Updated to reflect actual total download size, since that's what a user deciding whether they have the disk/bandwidth actually needs to know. Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * fix(web): make desktop Settings reachable when auth is disabled AvatarDropdown (the only desktop entry point to Settings) was gated behind `!isMobile && authEnabled`. With AUTH_ENABLED=false the synthetic anonymous admin user should have full Settings access per how auth.ts documents this mode -- and the mobile bottom nav already worked this way, showing Settings unconditionally. Desktop just had a stray extra gate the component doesn't need: AvatarDropdown already resolves its own username internally (falling back to "admin") and reads authEnabled itself where it actually matters (hiding the Logout button). Removed the outer gate; verified end-to-end against a fresh AUTH_ENABLED=false instance -- avatar now renders, Settings opens, shows the anonymous/Admin identity correctly. Also documents (not changes) a related finding in install_feature.py: detect_arch() always resolves amd64 hosts to the GPU-bundled archive variant regardless of actual GPU presence, since no CPU-only amd64 archive is published to the bundle repo yet. Left as a code comment rather than a behavior change, since requesting an unpublished archive key would hard-fail installs entirely -- worse than the current oversized-but-working download. Full detail in the QA report. Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * fix(ai): stop logging expected dispatcher reloads as crashes After each AI bundle install the Python dispatcher reloads because the venv changed, and after every app shutdown it's SIGTERMed. Both took the close handler's `code !== 0` branch (SIGTERM makes the exit code null), so they were counted as crashes -- producing an alarming "crash" line in the logs and a pointless ~1s recovery backoff after each of 7 installs. A `stopping` flag set in shutdown() lets the close handler tell an intentional stop apart from a real crash. The request-timeout kill path deliberately does not set it, so a genuinely hung script still records a crash and the 5-in-60s permanent-disable threshold is untouched. Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * fix(api): return a clean message when content-aware resize times out Carving a very high-resolution image down to a tiny target could exceed the caire subprocess timeout, and the raw error forwarded to the user was caire's terminal output -- ANSI color codes and progress-spinner control characters -- instead of anything actionable. Now: the timeout path throws a clear "timed out; try a smaller image or larger target" message (keeping the raw stderr as `cause` for server logs); friendlyError() strips ANSI/control chars centrally so any subprocess dump surfaced through the shared sanitizer is plain text; and the content-aware-resize route (a custom route that bypassed the sanitizer) now routes its error paths through friendlyError like every other tool. Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * fix(ai): stop bundle installs from exhausting host disk Installing an AI bundle on a tight-disk host could push the root filesystem to zero bytes free after the preflight check had already passed. Two root causes: - move_tree used copytree+rmtree, so during the move the extracted payload existed in both staging and the venv at once -- a full transient doubling on disk. Rewrote it to rename entries (a cheap metadata op on the same filesystem, no copy), falling back to a copy only across filesystems. - the preflight budget used the manifest's extractedSize verbatim, which is 0 for several archives, collapsing the estimate to just the compressed size. Added a conservative fallback (3x compressed) so a missing value can't under-reserve. Also added a real-on-disk re-check immediately before the first destructive venv write (measuring the actual extracted payload and whether the move needs extra space for a cross-filesystem copy), which also now covers the offline-import path that previously skipped the disk check entirely; wrapped the moves so an out-of-space failure returns a clean actionable error instead of a traceback; and made the disk check resolve the nearest existing ancestor so it never throws on a not-yet-created venv path. Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * feat(web): show the real per-arch AI bundle download size The bundle cards and install prompt showed a hardcoded, architecture-blind estimatedSize string. That's misleading: amd64 hosts always pull the CUDA-inclusive archive (there's no CPU-only amd64 variant published), so a bundle labelled "1-2 GB" can actually download several times that, while arm64 pulls a much smaller archive for the same label. The manifest already carries the real per-arch compressedSize (and extractedSize where measured), so surface those: a new optional downloadBytes/installedBytes on FeatureBundleState, populated in getFeatureStates() for this host's arch (resolver mirrors install_feature.py detect_arch), shown by the UI when present with estimatedSize kept as the fallback label. Also nudged upscale-enhance's fallback string (4-5 -> 5-6 GB) to match its real compressed size, consistent with the earlier photo-restoration/ocr fixes. Fields are optional so demo/mock and existing tests stay compiling; the manifest's extractedSize is 0 for a few archives, which now surfaces as null rather than a bogus 0. Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * fix(web): move the AI install queue to the server so it survives tab close Installing multiple bundles could silently lose all but the first. The server rejected a concurrent install with 409, so the client worked around it by queueing the rest in browser-local state and only POSTing each once it saw the previous finish. A single POSTed install is durable (the installer child is detached from the request), but a queued one had zero server footprint -- close the tab mid-queue and those installs vanished with no error, while the UI still showed them "Queued". The client "mutex" didn't even serialize: the queued bundles' local waits all resolved at once and raced into concurrent POSTs that 409'd each other. Now the queue lives on the server (a small in-memory FIFO leaf module). The install endpoint enqueues instead of 409-ing and returns 202 {jobId, queued}; a pump starts the next bundle when the current one's child exits (and after an offline import releases the lock), all behind the existing venv + file locks, which are unchanged. The client just POSTs every bundle immediately and reflects the server-reported queued/installing status; Install All fires all POSTs and lets the server serialize them, keeping the one-shot retry-on-failure. Adds "queued" to FeatureStatus (the bundle card already rendered that state) and surfaces it from getFeatureStates. In-memory is deliberate: it matches the existing contract (survives a tab close, not a server restart, which already clears the lock on boot). Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * fix(qa): don't log env-derived credentials in the AI-install script CodeQL flagged clear-text logging of sensitive information: the login status line interpolated the QA base URL and username (both read from the process environment) into a console.log. Replaced with a static message. QA helper only, but it's a real hygiene issue and cleared the high-severity code-scanning alert on the PR. Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG |
||
|
|
ca076f91fd |
fix: critical first-login soft-lock in usage survey overlay (#392)
* fix: prevent UsageSurveyOverlay from soft-locking the first-login password-change flow Claude-Session: https://claude.ai/code/session_01KAC9Lbx8AmebAnj9WQZXHp * fix: prevent double feedback submission when the settings write fails Claude-Session: https://claude.ai/code/session_01KAC9Lbx8AmebAnj9WQZXHp * refactor: consolidate feedback enums into packages/shared as a single source of truth Claude-Session: https://claude.ai/code/session_01KAC9Lbx8AmebAnj9WQZXHp * feat: add ARIA semantics, dismiss-button guard, and shared auth-route list to UsageSurveyOverlay Claude-Session: https://claude.ai/code/session_01KAC9Lbx8AmebAnj9WQZXHp * test: cover the submit-failure retry path and a persona-only minimal payload Claude-Session: https://claude.ai/code/session_01KAC9Lbx8AmebAnj9WQZXHp |
||
|
|
bd1838e40b |
fix: repair docker validation QA tooling, dispatcher crash-accounting, and image-enhancement RAW hang (#391)
Found and fixed during a full local Docker build validation (amd64/arm64, all four fleet targets, AI bundle installs, QA harness) and the follow-up bug sweep requested afterward. None of the affected scripts run in CI, so these had been silently broken indefinitely. - docker/feature-manifest.json: pythonVersion was a flat "3.11", but the amd64 base (Ubuntu 24.04) ships Python 3.12 while arm64 (Debian bookworm) ships 3.11. Changed to a per-arch object matching the file's existing convention. - tests/qa/api-sweep.mts and verify-ai.mts: bare "@snapotter/shared" import can't resolve since tests/ is not a pnpm workspace member, making both silently unrunnable via their own documented command on any fresh checkout. Switched to a relative import. - tests/qa/generate-ledger.mts: wrote to docs/qa/ without creating the directory first; docs/ is gitignored except COMMUNITY_GUIDE.md, so a fresh checkout threw ENOENT. - Seven QA Playwright spec files (input-preview, settings, settings-extended, multifile, output-preview, pipeline-ui, smoke) had ~115 fixture() calls using directory names that don't exist. Resolved every call programmatically against the real fixture tree. - packages/ai/src/bridge.ts: AI dispatcher restart (happens on every bundle install) was falsely counted as a crash, risking permanent dispatcher disable after enough legitimate restarts within the crash window. Added a shuttingDown flag checked at all three recordCrash() call sites. - packages/image-engine/src/operations/auto-enhance.ts: image-enhancement hung 40+ seconds on large RAW photos (confirmed on a real 20.2MP file) in Sharp's .clahe() step, whose cost scales with total pixel count regardless of tile size. Added a 16-megapixel cap above which CLAHE is skipped; verified against the real file (40+s -> 2.0s) with no regression to other RAW formats or normal-sized images. Fixing this surfaced a second, smaller bug where the saturation step's CLAHE compensation boost was keyed off the raw toggle instead of whether CLAHE actually ran. - Two QA-harness robustness gaps closed per "fix everything, even the small bugs": the passport-photo/erase-object input-preview tests now skip cleanly with a clear reason on a container without their AI bundle installed, and docker-compose.qa.yml's hardcoded project/container name (the actual root cause of a mid-validation container swap between two concurrent sessions) is now parameterized via QA_PROJECT_NAME. Full validation report is local-only per repo convention. |
||
|
|
a0d1c70172 |
feat: add usage onboarding survey overlay (#388)
* feat: add usage-survey feedback types and gating function Claude-Session: https://claude.ai/code/session_01KAC9Lbx8AmebAnj9WQZXHp * feat: add onboarding usage-survey i18n strings to all locales Relabels three ambiguous feedback.usageTypes values (personal/team_internal/ business_workflow) and adds a new onboarding namespace (4 keys) across the reference locale and all 20 translations, so the tree compiles at every commit instead of only after both locale groups land. Claude-Session: https://claude.ai/code/session_01KAC9Lbx8AmebAnj9WQZXHp * feat: add UsageSurveyOverlay component * feat: mount UsageSurveyOverlay inside AuthGuard Claude-Session: https://claude.ai/code/session_01KAC9Lbx8AmebAnj9WQZXHp * fix: use text-start instead of text-left for RTL support in UsageSurveyOverlay * refactor: drop redundant usage-type field from the admin feedback dialog Claude-Session: https://claude.ai/code/session_01KAC9Lbx8AmebAnj9WQZXHp * feat: accept onboarding source and survey id in the feedback route Claude-Session: https://claude.ai/code/session_01KAC9Lbx8AmebAnj9WQZXHp * test: cover the onboarding source in the feedback route integration test Claude-Session: https://claude.ai/code/session_01KAC9Lbx8AmebAnj9WQZXHp * chore: remove orphaned usageTypeLabel i18n key * refactor: derive feedback source/survey_id enums from a single source of truth * perf: skip the settings fetch in UsageSurveyOverlay for non-admin users |
||
|
|
9052da27f3 |
fix: adopt sharp 0.35.2+ by centralizing the FormatEnum key type (#362)
Centralizes the sharp format key type into a single `SharpFormat` alias derived from `toFormat()`'s signature (exported from @snapotter/image-engine, imported by the API consumers), replacing the duplicated `keyof FormatEnum` definitions. Adds convert/compress format round-trip tests covering webp/avif/png/jpeg. Part of #325. |
||
|
|
c68297d5a4 |
feat: request a tool when home search finds nothing (#385)
Adds a prefilled 'Request a tool' affordance to the home search empty state and beneath weak results. Opens the in-app feedback dialog with a new search_miss source and a structured search_query when analytics is on; links to a prefilled GitHub Discussions (Ideas) post when off, so a request is never silently dropped. Reuses the existing feedback pipe, dialog, and analytics gate; no new storage. i18n across all 21 locales. |
||
|
|
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. |
||
|
|
1d99acf9ee | feat: merge PostHog customer feedback | ||
|
|
649e65b035 | feat: add PostHog customer feedback | ||
|
|
c6319cf8a9 | fix(security): harden auth and outbound fetches | ||
|
|
2137be479e | fix(image-engine): support sharp 0.35 types | ||
|
|
af7cd77e84 |
chore(deps): bump the production-deps group with 16 updates
Bumps the production-deps group with 16 updates: | Package | From | To | | --- | --- | --- | | [@scalar/fastify-api-reference](https://github.com/scalar/scalar/tree/HEAD/integrations/fastify) | `1.60.0` | `1.62.0` | | [@sentry/node](https://github.com/getsentry/sentry-javascript) | `10.59.0` | `10.62.0` | | [bullmq](https://github.com/taskforcesh/bullmq) | `5.79.1` | `5.79.2` | | [fastify](https://github.com/fastify/fastify) | `5.8.5` | `5.9.0` | | [js-yaml](https://github.com/nodeca/js-yaml) | `4.2.0` | `4.3.0` | | [playwright](https://github.com/microsoft/playwright) | `1.61.0` | `1.61.1` | | [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) | `5.38.2` | `5.38.6` | | [sharp](https://github.com/lovell/sharp) | `0.35.1` | `0.35.2` | | [tar](https://github.com/isaacs/node-tar) | `7.5.16` | `7.5.19` | | [lucide](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide) | `1.21.0` | `1.22.0` | | [@sentry/react](https://github.com/getsentry/sentry-javascript) | `10.59.0` | `10.62.0` | | [pdfjs-dist](https://github.com/mozilla/pdf.js) | `6.0.227` | `6.1.200` | | [posthog-js](https://github.com/PostHog/posthog-js) | `1.391.9` | `1.395.0` | | [react-hotkeys-hook](https://github.com/JohannesKlauss/react-keymap-hook) | `5.3.2` | `5.3.3` | | [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) | `3.1073.0` | `3.1075.0` | | [@aws-sdk/lib-storage](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/lib/lib-storage) | `3.1073.0` | `3.1075.0` | Updates `@scalar/fastify-api-reference` from 1.60.0 to 1.62.0 - [Release notes](https://github.com/scalar/scalar/releases) - [Changelog](https://github.com/scalar/scalar/blob/main/integrations/fastify/CHANGELOG.md) - [Commits](https://github.com/scalar/scalar/commits/HEAD/integrations/fastify) Updates `@sentry/node` from 10.59.0 to 10.62.0 - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/10.59.0...10.62.0) Updates `bullmq` from 5.79.1 to 5.79.2 - [Release notes](https://github.com/taskforcesh/bullmq/releases) - [Commits](https://github.com/taskforcesh/bullmq/compare/v5.79.1...v5.79.2) Updates `fastify` from 5.8.5 to 5.9.0 - [Release notes](https://github.com/fastify/fastify/releases) - [Commits](https://github.com/fastify/fastify/compare/v5.8.5...v5.9.0) Updates `js-yaml` from 4.2.0 to 4.3.0 - [Changelog](https://github.com/nodeca/js-yaml/blob/4.3.0/CHANGELOG.md) - [Commits](https://github.com/nodeca/js-yaml/compare/4.2.0...4.3.0) Updates `playwright` from 1.61.0 to 1.61.1 - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](https://github.com/microsoft/playwright/compare/v1.61.0...v1.61.1) Updates `posthog-node` from 5.38.2 to 5.38.6 - [Release notes](https://github.com/PostHog/posthog-js/releases) - [Changelog](https://github.com/PostHog/posthog-js/blob/main/packages/node/CHANGELOG.md) - [Commits](https://github.com/PostHog/posthog-js/commits/posthog-node@5.38.6/packages/node) Updates `sharp` from 0.35.1 to 0.35.2 - [Release notes](https://github.com/lovell/sharp/releases) - [Commits](https://github.com/lovell/sharp/compare/v0.35.1...v0.35.2) Updates `tar` from 7.5.16 to 7.5.19 - [Release notes](https://github.com/isaacs/node-tar/releases) - [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md) - [Commits](https://github.com/isaacs/node-tar/compare/v7.5.16...v7.5.19) Updates `lucide` from 1.21.0 to 1.22.0 - [Release notes](https://github.com/lucide-icons/lucide/releases) - [Commits](https://github.com/lucide-icons/lucide/commits/1.22.0/packages/lucide) Updates `@sentry/react` from 10.59.0 to 10.62.0 - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/10.59.0...10.62.0) Updates `pdfjs-dist` from 6.0.227 to 6.1.200 - [Release notes](https://github.com/mozilla/pdf.js/releases) - [Commits](https://github.com/mozilla/pdf.js/compare/v6.0.227...v6.1.200) Updates `posthog-js` from 1.391.9 to 1.395.0 - [Release notes](https://github.com/PostHog/posthog-js/releases) - [Changelog](https://github.com/PostHog/posthog-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/PostHog/posthog-js/compare/posthog-js@1.391.9...posthog-js@1.395.0) Updates `react-hotkeys-hook` from 5.3.2 to 5.3.3 - [Release notes](https://github.com/JohannesKlauss/react-keymap-hook/releases) - [Changelog](https://github.com/JohannesKlauss/react-hotkeys-hook/blob/main/CHANGELOG.md) - [Commits](https://github.com/JohannesKlauss/react-keymap-hook/compare/v.5.3.2...v5.3.3) Updates `@aws-sdk/client-s3` from 3.1073.0 to 3.1075.0 - [Release notes](https://github.com/aws/aws-sdk-js-v3/releases) - [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-s3/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1075.0/clients/client-s3) Updates `@aws-sdk/lib-storage` from 3.1073.0 to 3.1075.0 - [Release notes](https://github.com/aws/aws-sdk-js-v3/releases) - [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/lib/lib-storage/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1075.0/lib/lib-storage) --- updated-dependencies: - dependency-name: "@scalar/fastify-api-reference" dependency-version: 1.62.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: "@sentry/node" dependency-version: 10.62.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: bullmq dependency-version: 5.79.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-deps - dependency-name: fastify dependency-version: 5.9.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: js-yaml dependency-version: 4.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: playwright dependency-version: 1.61.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-deps - dependency-name: posthog-node dependency-version: 5.38.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-deps - dependency-name: sharp dependency-version: 0.35.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-deps - dependency-name: tar dependency-version: 7.5.19 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-deps - dependency-name: lucide dependency-version: 1.22.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: "@sentry/react" dependency-version: 10.62.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: pdfjs-dist dependency-version: 6.1.200 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: posthog-js dependency-version: 1.395.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: react-hotkeys-hook dependency-version: 5.3.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-deps - dependency-name: "@aws-sdk/client-s3" dependency-version: 3.1075.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: "@aws-sdk/lib-storage" dependency-version: 3.1075.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps ... Signed-off-by: dependabot[bot] <support@github.com> |
||
|
|
0cdd560ac4 |
feat: add Sign PDF tool (draw/type/upload signatures, place on a PDF) (#370)
Draw, type, or upload a signature and place resizable/rotatable copies across PDF pages; output flattened server-side with PyMuPDF. Visual electronic signature, not cryptographic. New interactive-sign display mode (pdf.js + Konva) and a custom docs-pool route. |
||
|
|
9a556d4851 |
chore(i18n): complete the 200+ tools sweep across the remaining 19 locales (#366)
PR #364 only changed en/nl; the appDescription and rotating-phrase tool count was still '240' (translated) in the other 19 locales (ar, de, es, fr, hi, id, it, ja, ko, pl, pt-BR, ru, sv, th, tr, uk, vi, zh-CN, zh-TW). Replaces the number in each; surrounding translations untouched. My earlier English-only grep missed these. |
||
|
|
19b515dc5f |
chore: use "200+ tools" for the tool-count claim across public surfaces (#364)
* fix(landing): correct PDF tool count to 28 in alternatives copy
The pdf section has 28 tools (section.test.ts asserts bySection('pdf')=28). PR #363 corrected the docs breakdown but the alternatives pages still said 40 PDF tools (the document-modality count, not the pdf section) with 200 for the rest. Update to 28 PDF tools and 212 for the non-PDF remainder.
* chore: use "200+ tools" for the tool-count claim across public surfaces
Replaces the exact '240 tools' count (which drifts as tools are added) with the stable '200+ tools' on README, the Docker Hub overview, landing pages, the docs site (meta, homepage, search), the API self-description, the demo OG tag, llms.txt, package.json, the branding readme, and the en/nl app strings. Per-modality breakdown tables stay exact. Leaves the architecture doc's technical 'tool routes' figure, an internal vitest comment, and a QA report line unchanged. Updates the two tests that assert the docs strings.
|
||
|
|
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. |
||
|
|
8f4235d2c6 |
fix(enterprise): ship enterprise package in prod image + S3, analytics, tracing, queue fixes (#342)
* fix(enterprise): ship enterprise pkg in prod image, full license features, tracing key fallback docker/Dockerfile: COPY packages/enterprise manifest+src into the production stage. Without it, apps/api's workspace link to @snapotter/enterprise dangles and every import() throws (silently caught), so all 19 enterprise features failed closed (enterprise.active=false) regardless of a valid license. scripts/generate-license.mjs: sync PLAN_FEATURES with packages/enterprise/src/license.ts so a --plan enterprise license unlocks all 19 features (was 8) and team unlocks 8. apps/api/src/tracing.ts: accept SNAPOTTER_LICENSE_KEY as a fallback to LICENSE_KEY so distributed_tracing activates with the same key as the rest of the app. * fix(docker): keep scripts/bake-analytics.mjs in build context .dockerignore excluded the whole scripts/ dir (PR #82, V1 hardening), but docker/Dockerfile later added 'COPY scripts/bake-analytics.mjs' for the analytics bake step. A clean production image build therefore fails with 'scripts/bake-analytics.mjs: not found'. The published image build is gated off in CI so this latent break went unnoticed. Exclude scripts/* but re-include the one file the Dockerfile needs. * fix: S3 upload stream, analytics bake reaches API, dedupe retention field, reconcile orphan jobs storage-s3.ts: wrap the upload AsyncIterable in Readable.from() so @aws-sdk/lib-storage accepts it. STORAGE_MODE=s3 file uploads failed with 'Body Data is unsupported format' for every tool because a bare async generator is not a Readable. docker/Dockerfile: COPY the builder-baked analytics baked.ts into the API runtime stage. The API re-copied the committed (off) baked.ts from the build context, so the SNAPOTTER_ANALYTICS build arg had no effect on the API -- and since the SPA reads /api/v1/config/analytics, analytics was off everywhere regardless of the arg. settings-dialog.tsx: remove the duplicate tempFileMaxAgeHours control under Data Retention; it bound the same setting key as the File Management control with a different default, so editing either silently overwrote the other. apps/api/src/index.ts: reconcile orphaned job rows (empty tool_id, never enqueued to BullMQ) at boot so they don't sit in processing/queued forever and inflate the per-user concurrent-job count and the upgrade-check in-flight gate. * fix(web): style the SSO login buttons (they referenced undefined theme tokens) The OIDC/SAML 'Sign in with <provider>' buttons used bg-secondary / text-secondary-foreground, which the web theme never defines (it has primary, background, foreground, muted, border, card, primary-subtle). Those classes resolved to nothing, so the buttons rendered as bare unstyled text on the login page. Restyle: the optional (non-enforced) buttons become white-card outline buttons with a key icon and an orange hover tint, secondary to the primary Login button; the SSO-enforced buttons become solid primary with the icon. * fix: gate S3 behind license, custom-role enterprise perms, wire retention UI, cleanup S3 is a licensed feature, but shipping packages/enterprise in every image removed the implicit gate, so STORAGE_MODE=s3 worked without a license. Enforce isFeatureEnabled('s3_storage') at boot and fail fast if unlicensed. Custom roles can now be granted security:manage / compliance:manage / webhooks:manage (roles.ts ALL_PERMISSIONS + the Roles UI) so admins can build least-privilege compliance/security roles instead of only the built-in admin role. retentionSweep now reads the jobsRetentionDays / auditRetentionDays DB settings the System Settings UI writes (env vars become the fallback default), mirroring how the temp-file sweep reads tempFileMaxAgeHours. Previously those two UI controls were no-ops. Cleanup: drop the never-set snapotter_storage_bytes gauge and the unused MAX_WORKSPACE_SIZE_GB env var; emit tool_client_error to PostHog from the web ErrorBoundary (client crashes were not reaching analytics); add the Python OpenTelemetry packages so the innermost sidecar.<script> span exports; fix the stale 'only local storage' line in the docs; delete two e2e-analytics specs that tested the removed consent UI. * fix(env): restore MAX_WORKSPACE_SIZE_GB default security-auth-hardening.test.ts asserts env.MAX_WORKSPACE_SIZE_GB defaults to 10, so the var is an intentional (tested) default, not dead code. Removing it in the cleanup commit broke that unit test. Keep the declaration. |
||
|
|
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(-) |
||
|
|
a53038ed96 |
feat(automate): make the pipeline builder fully multi-modal (#335)
* feat(shared): add outputModality to Tool metadata for crossing tools * feat(shared): add modalityForExtension, toolInputModality, toolOutputModality * fix(pipeline): route finalize and parent jobs to the pipeline's modality pool * feat(automate): add ConvertAudioControls pipeline step (exemplar) * feat(automate): add video tool settings controls to pipelines * feat(automate): add audio tool settings controls to pipelines * feat(automate): add document tool settings controls to pipelines * feat(automate): add chart-maker settings control to pipelines * feat(automate): warn on modality-incompatible pipeline steps * feat(automate): add single-file download button for pipeline results * refactor(automate): modality-aware icons, nav handler rename, mobile size bar * test(pipeline): cover audio, document, file, and cross-modality chains * i18n(automate): translate the modality-warning tooltip * test(pipeline): gate media-pool routing assertion on ffmpeg availability |
||
|
|
35e18d8b79 |
fix: GPU deployment robustness (6 fixes from end-to-end testing on an RTX 4070) (#334)
* fix(docker): pin CUDA base to 12.6 so the GPU image starts on R560+ drivers The amd64 base nvidia/cuda:12.9.2-cudnn-runtime bakes a cuda>=12.9 driver gate enforced by nvidia-container-toolkit at container start, so the image fails to launch on common production drivers (e.g. 570.x / CUDA 12.8). The AI bundles are all cu126 wheels and the image installs libcublas-12-6, so 12.9 was misaligned with the workload. Pin to nvidia/cuda:12.6.3-cudnn-runtime-ubuntu24.04 to match the wheels and lower the driver floor to R560+. * fix(ai): broaden OOM detection so the rembg lighter-model fallback fires onnxruntime/CUDA allocation failures surface as 'Failed to allocate memory for requested buffer', CUBLAS_STATUS_ALLOC_FAILED, or bad_alloc, not just 'out of memory'. The background-removal and transparency-fixer fallback-to-lighter-model paths only matched the literal 'out of memory', so the fallback was dead code and transparency-fixer (default birefnet-hr-matting) always failed with an allocation error. Add isMemoryAllocError() and use it in both checks. * fix(ai): use bundled PaddleOCR models so OCR runs offline ocr.py passed no model dirs to PaddleOCR, so PaddleX resolved models from ~/.paddlex and downloaded them from HuggingFace at runtime (slow first use, broken air-gapped), ignoring the models the OCR bundle ships in MODELS_PATH; it also pulled doc-orientation/unwarping models that are not bundled. Pin detection, recognition and textline models to the bundled dirs in MODELS_PATH (per language) and disable use_doc_orientation_classify / use_doc_unwarping, with per-component fallback when a model is absent. Verified: OCR runs with zero HuggingFace requests. * fix(docker): add CAP_KILL so container shutdown is graceful cap_drop: ALL without re-adding KILL meant tini (PID 1, root) could not forward SIGTERM to the gosu-dropped snapotter process (root minus CAP_KILL cannot signal a different UID). docker stop logged '[FATAL tini] forwarding signal: Operation not permitted', never delivered the signal, and fell back to SIGKILL after the 10s timeout. Add KILL to cap_add in both compose files. Verified: docker stop completes in 0s with SIGTERM delivered (exit 143) and no FATAL tini. * fix(ai): serialize bundle installs against AI jobs to prevent sidecar segfault A feature bundle install rewrites the shared Python venv (pip + copytree of site-packages/*.so) as a background subprocess, with no coordination against AI tool jobs that dlopen native libs (torch / onnxruntime CUDA) from the same venv; a job loading a shared object while it is overwritten segfaults the sidecar. Add a process-wide async mutex (venv-lock.ts): bridge.run() acquires it before every AI script and the install route holds it across the installer subprocess. Both run in the same Node process so a module-level lock suffices. Verified: concurrent install + AI job produces zero segfaults and the job serializes behind the install. * fix(ai): make the venv lock read/write so concurrent AI jobs are not serialized The first cut used an exclusive mutex, which (a) deferred the dispatcher spawn by a microtask and broke unit tests that synchronously drive the mocked spawn, and (b) serialized AI jobs against each other, removing the dispatcher's by-id request multiplexing. Make it a writer-preferring read/write lock: AI jobs are shared readers (with a synchronous fast path so spawn still happens in-tick) and a bundle install is the exclusive writer. Verified: all 764 AI unit tests pass. * fix(ai): degrade OCR to Tesseract on CPU-only hosts instead of segfaulting The amd64 AI bundle ships paddlepaddle-gpu, whose native libs dlopen libcuda.so.1 at import and segfault on a host without a GPU (libcuda is the driver lib, injected only by nvidia-container-toolkit on GPU hosts). The segfault crashed the shared long-lived AI dispatcher and, after a few attempts, tripped the bridge crash-recovery permanent-disable, wedging all AI until a container restart. The standalone ocr tool defaults to quality=balanced (PaddleOCR), so it hit this on every CPU-only deployment; ocr-pdf already hardcoded Tesseract and was unaffected. ocr.py now gates the PaddleOCR tiers on gpu_available(): balanced/best transparently fall back to fast (Tesseract, CPU-capable) when no usable GPU is present, and run_paddleocr_v5/run_paddleocr_vl refuse before importing paddle so the GPU build is never dlopen'd on CPU. GPU hosts are unchanged. Verified on a CPU-only Windows/WSL2 box: ocr returns Tesseract text across repeated runs with the dispatcher staying healthy (no wedge). |
||
|
|
ffbf4f9e10 |
feat(i18n): raise translation coverage across all 20 locales (#332)
Close genuinely-untranslated UI strings and fix orthography/diacritic errors across every non-English locale, while keeping legitimate cognates, loanwords, and technical tokens (OIDC, SAML, PDF, YAML, brand names) as-is. - Untranslated strings closed: 225 (976 -> 751 remaining; the remainder are legit cognates / brand / format tokens that natives keep in English) - Orthography: Italian 52 accent fixes (verb forms, a/e/i finals), Vietnamese 41 strings with restored tone marks, Turkish 35, Polish 42, Dutch 17 (trema), plus smaller passes in es/fr/sv/pt-BR - Biggest gap wins: Indonesian 102 -> 72; every CJK/Cyrillic/Arabic/Indic locale roughly halved its English leftovers Cross-locale key parity preserved exactly; all placeholders intact. Verified: pnpm typecheck, i18n parity/locale/tool-i18n tests (43/43), placeholder-integrity check (0 mismatches), Biome. |
||
|
|
7a70affac5 |
fix(ai): enforce the feature gate on the per-request fallback path (#331)
The persistent Python dispatcher rejects scripts whose feature bundle is not installed, but the per-request fallback (used when the dispatcher is down, e.g. restarting right after a model repair) spawned scripts directly and bypassed that gate. Behavior was therefore inconsistent: a gated script would fail under the dispatcher but run under the fallback -- the "works once after a repair" symptom from the original report. - add packages/ai/src/feature-gate.ts: SCRIPT_BUNDLE_MAP + missingBundleForScript, mirroring TOOL_BUNDLE_MAP in dispatcher.py, reading the same installed.json and failing closed exactly like dispatcher._get_installed_bundles() - runPerRequest now rejects with "feature_not_installed" (the same message the dispatcher path surfaces) when a gated script's bundle is not installed - unit tests for the gate, plus a drift test pinning the TS map to dispatcher.py Closes #327 |
||
|
|
32c1192d63 |
fix(passport-photo): require the face-detection bundle, not just background-removal (#329)
* fix(passport-photo): require the face-detection bundle, not just background-removal Passport Photo runs face-landmark detection (face_landmarks.py, gated to the face-detection bundle) before background removal (background-removal bundle), but it was only declared under and guarded against background-removal. A user who installed only Background Removal passed every JS-side check, then hit a late "feature_not_installed" from the Python dispatcher gate when the analyze step ran face landmarks, and the UI never told them Face Detection was needed. - shared: add TOOL_EXTRA_BUNDLES + getRequiredBundlesForTool so a tool can declare more than one required bundle (passport-photo needs background-removal and face-detection). enablesTools is untouched, so the one-tool-per-bundle invariant still holds. - api: isToolInstalled() now checks every required bundle; add getFirstMissingBundleForTool() so the analyze and base routes, pipeline (both guards) and batch report the bundle the user actually still needs. - web: the proactive install prompt (tool-page) and features-store treat a tool as installed only when all required bundles are present, and point the prompt at the first missing one (sequential install, no new UI). Refs #327 * test(passport-photo): deterministic integration coverage for the two-bundle guard Boots the real API with an isolated DATA_DIR and controls installed.json to prove the HTTP route behavior end-to-end: - nothing installed -> 501 naming background-removal - only background-removal installed -> 501 naming face-detection (issue #327) - both installed -> guard passes (not 501) - base route reports face-detection too Refs #327 |
||
|
|
717de2577a |
chore(deps): bump the production-deps group across 1 directory with 18 updates (#326)
Bumps the production-deps group with 18 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@scalar/fastify-api-reference](https://github.com/scalar/scalar/tree/HEAD/integrations/fastify) | `1.59.3` | `1.60.0` | | [@sentry/node](https://github.com/getsentry/sentry-javascript) | `10.57.0` | `10.59.0` | | [bullmq](https://github.com/taskforcesh/bullmq) | `5.78.1` | `5.79.1` | | [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) | `5.9.0` | `5.9.3` | | [ipaddr.js](https://github.com/whitequark/ipaddr.js) | `2.3.0` | `2.4.0` | | [papaparse](https://github.com/mholt/PapaParse) | `5.5.3` | `5.5.4` | | [pg](https://github.com/brianc/node-postgres/tree/HEAD/packages/pg) | `8.21.0` | `8.22.0` | | [playwright](https://github.com/microsoft/playwright) | `1.60.0` | `1.61.0` | | [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) | `5.37.0` | `5.38.2` | | [sharp](https://github.com/lovell/sharp) | `0.35.1` | `0.35.2` | | [astro](https://github.com/withastro/astro/tree/HEAD/packages/astro) | `6.4.7` | `6.4.8` | | [lucide](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide) | `1.18.0` | `1.21.0` | | [@sentry/react](https://github.com/getsentry/sentry-javascript) | `10.57.0` | `10.59.0` | | [posthog-js](https://github.com/PostHog/posthog-js) | `1.386.6` | `1.391.9` | | [react-image-crop](https://github.com/dominictobias/react-image-crop) | `11.0.10` | `11.1.2` | | [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) | `7.17.0` | `7.18.0` | | [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) | `3.1068.0` | `3.1073.0` | | [@aws-sdk/lib-storage](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/lib/lib-storage) | `3.1068.0` | `3.1073.0` | Updates `@scalar/fastify-api-reference` from 1.59.3 to 1.60.0 - [Release notes](https://github.com/scalar/scalar/releases) - [Changelog](https://github.com/scalar/scalar/blob/main/integrations/fastify/CHANGELOG.md) - [Commits](https://github.com/scalar/scalar/commits/HEAD/integrations/fastify) Updates `@sentry/node` from 10.57.0 to 10.59.0 - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/10.57.0...10.59.0) Updates `bullmq` from 5.78.1 to 5.79.1 - [Release notes](https://github.com/taskforcesh/bullmq/releases) - [Commits](https://github.com/taskforcesh/bullmq/compare/v5.78.1...v5.79.1) Updates `fast-xml-parser` from 5.9.0 to 5.9.3 - [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases) - [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/CHANGELOG.md) - [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/compare/v5.9.0...v5.9.3) Updates `ipaddr.js` from 2.3.0 to 2.4.0 - [Changelog](https://github.com/whitequark/ipaddr.js/blob/main/Changes.md) - [Commits](https://github.com/whitequark/ipaddr.js/compare/v2.3.0...v2.4.0) Updates `papaparse` from 5.5.3 to 5.5.4 - [Release notes](https://github.com/mholt/PapaParse/releases) - [Changelog](https://github.com/mholt/PapaParse/blob/master/CHANGELOG.md) - [Commits](https://github.com/mholt/PapaParse/compare/5.5.3...5.5.4) Updates `pg` from 8.21.0 to 8.22.0 - [Changelog](https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md) - [Commits](https://github.com/brianc/node-postgres/commits/pg@8.22.0/packages/pg) Updates `playwright` from 1.60.0 to 1.61.0 - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](https://github.com/microsoft/playwright/compare/v1.60.0...v1.61.0) Updates `posthog-node` from 5.37.0 to 5.38.2 - [Release notes](https://github.com/PostHog/posthog-js/releases) - [Changelog](https://github.com/PostHog/posthog-js/blob/main/packages/node/CHANGELOG.md) - [Commits](https://github.com/PostHog/posthog-js/commits/posthog-node@5.38.2/packages/node) Updates `sharp` from 0.35.1 to 0.35.2 - [Release notes](https://github.com/lovell/sharp/releases) - [Commits](https://github.com/lovell/sharp/compare/v0.35.1...v0.35.2) Updates `astro` from 6.4.7 to 6.4.8 - [Release notes](https://github.com/withastro/astro/releases) - [Changelog](https://github.com/withastro/astro/blob/astro@6.4.8/packages/astro/CHANGELOG.md) - [Commits](https://github.com/withastro/astro/commits/astro@6.4.8/packages/astro) Updates `lucide` from 1.18.0 to 1.21.0 - [Release notes](https://github.com/lucide-icons/lucide/releases) - [Commits](https://github.com/lucide-icons/lucide/commits/1.21.0/packages/lucide) Updates `@sentry/react` from 10.57.0 to 10.59.0 - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/10.57.0...10.59.0) Updates `posthog-js` from 1.386.6 to 1.391.9 - [Release notes](https://github.com/PostHog/posthog-js/releases) - [Changelog](https://github.com/PostHog/posthog-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/PostHog/posthog-js/compare/posthog-js@1.386.6...posthog-js@1.391.9) Updates `react-image-crop` from 11.0.10 to 11.1.2 - [Release notes](https://github.com/dominictobias/react-image-crop/releases) - [Commits](https://github.com/dominictobias/react-image-crop/compare/11.0.10...11.1.2) Updates `react-router-dom` from 7.17.0 to 7.18.0 - [Release notes](https://github.com/remix-run/react-router/releases) - [Changelog](https://github.com/remix-run/react-router/blob/react-router-dom@7.18.0/packages/react-router-dom/CHANGELOG.md) - [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@7.18.0/packages/react-router-dom) Updates `@aws-sdk/client-s3` from 3.1068.0 to 3.1073.0 - [Release notes](https://github.com/aws/aws-sdk-js-v3/releases) - [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-s3/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1073.0/clients/client-s3) Updates `@aws-sdk/lib-storage` from 3.1068.0 to 3.1073.0 - [Release notes](https://github.com/aws/aws-sdk-js-v3/releases) - [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/lib/lib-storage/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1073.0/lib/lib-storage) --- updated-dependencies: - dependency-name: "@aws-sdk/client-s3" dependency-version: 3.1073.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: "@aws-sdk/lib-storage" dependency-version: 3.1073.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: "@scalar/fastify-api-reference" dependency-version: 1.60.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: "@sentry/node" dependency-version: 10.59.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: "@sentry/react" dependency-version: 10.59.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: astro dependency-version: 6.4.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-deps - dependency-name: bullmq dependency-version: 5.79.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: fast-xml-parser dependency-version: 5.9.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-deps - dependency-name: ipaddr.js dependency-version: 2.4.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: lucide dependency-version: 1.21.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: papaparse dependency-version: 5.5.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-deps - dependency-name: pg dependency-version: 8.22.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: playwright dependency-version: 1.61.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: posthog-js dependency-version: 1.391.2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: posthog-node dependency-version: 5.38.2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: react-image-crop dependency-version: 11.1.2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: react-router-dom dependency-version: 7.18.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: sharp dependency-version: 0.35.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-deps ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
c8371c3cd1 |
chore(deps-dev): bump the dev-deps group with 5 updates (#297)
Bumps the dev-deps group with 5 updates: | Package | From | To | | --- | --- | --- | | [@playwright/test](https://github.com/microsoft/playwright) | `1.60.0` | `1.61.0` | | [@testcontainers/postgresql](https://github.com/testcontainers/testcontainers-node) | `12.0.2` | `12.0.3` | | [@testcontainers/redis](https://github.com/testcontainers/testcontainers-node) | `12.0.2` | `12.0.3` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `22.19.21` | `22.20.0` | | [vitepress-plugin-llms](https://github.com/okineadev/vitepress-plugin-llms) | `1.13.1` | `1.13.2` | Updates `@playwright/test` from 1.60.0 to 1.61.0 - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](https://github.com/microsoft/playwright/compare/v1.60.0...v1.61.0) Updates `@testcontainers/postgresql` from 12.0.2 to 12.0.3 - [Release notes](https://github.com/testcontainers/testcontainers-node/releases) - [Commits](https://github.com/testcontainers/testcontainers-node/compare/v12.0.2...v12.0.3) Updates `@testcontainers/redis` from 12.0.2 to 12.0.3 - [Release notes](https://github.com/testcontainers/testcontainers-node/releases) - [Commits](https://github.com/testcontainers/testcontainers-node/compare/v12.0.2...v12.0.3) Updates `@types/node` from 22.19.21 to 22.20.0 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `vitepress-plugin-llms` from 1.13.1 to 1.13.2 - [Release notes](https://github.com/okineadev/vitepress-plugin-llms/releases) - [Commits](https://github.com/okineadev/vitepress-plugin-llms/compare/v1.13.1...v1.13.2) --- updated-dependencies: - dependency-name: "@playwright/test" dependency-version: 1.61.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-deps - dependency-name: "@testcontainers/postgresql" dependency-version: 12.0.3 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-deps - dependency-name: "@testcontainers/redis" dependency-version: 12.0.3 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-deps - dependency-name: "@types/node" dependency-version: 22.20.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-deps - dependency-name: vitepress-plugin-llms dependency-version: 1.13.2 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-deps ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
95d100c20b |
feat(web): in-canvas zoom & pan for the object eraser and split tools (#320)
* feat(web): add pure zoom/pan math module with unit tests * feat(i18n): add a11y.pan key across all locales (English, matching adjacent zoom labels) * feat(web): add useZoomPan hook (state + gestures over pure math) * feat(web): add ZoomToolbar component * feat(web): zoom & pan in the object eraser canvas * feat(web): zoom & pan in the split tool preview * fix(web): synchronous pan-mode refs so drag-pan is race-free under fast input * test(e2e): zoom & pan acceptance (split always-on, eraser bundle-gated) |
||
|
|
c4ce5f96df |
fix(i18n): translate Swedish (sv) UI strings (#310)
Translate the ~950 previously-untranslated Swedish UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
|
||
|
|
4de24b8c9e |
fix(i18n): translate German (de) UI strings (#308)
Translate the ~950 previously-untranslated German UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
|
||
|
|
3900350ad5 |
fix(i18n): translate Brazilian Portuguese (pt-BR) UI strings (#307)
Translate the ~950 previously-untranslated Brazilian Portuguese UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
|
||
|
|
fe5c7c8c86 |
fix(i18n): translate French (fr) UI strings (#306)
Translate the ~950 previously-untranslated French UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
|
||
|
|
b3ff8accec |
fix(i18n): translate Spanish (es) UI strings (#301)
Translate the ~950 previously-untranslated Spanish UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
|
||
|
|
29fcc87708 |
fix(i18n): restore accents in Italian loading-message arrays (#322)
The auth.rotatingPhrases and features.progressMessages arrays were skipped by the main Italian pass (#298) and still had a few missing accents (e.g. verb e -> è). Diacritic-only fix; wording, placeholders, and key parity unchanged. |
||
|
|
5a6368db89 |
fix(i18n): translate Thai (th) UI strings (#319)
Translate the ~950 previously-untranslated Thai UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
|
||
|
|
8a8a4f99a4 |
fix(i18n): translate Indonesian (id) UI strings (#318)
Translate the ~950 previously-untranslated Indonesian UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
|
||
|
|
470272169d |
fix(i18n): translate Vietnamese (vi) UI strings (#317)
Translate the ~950 previously-untranslated Vietnamese UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
|
||
|
|
00c89e4ae9 |
fix(i18n): translate Hindi (hi) UI strings (#316)
Translate the ~950 previously-untranslated Hindi UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
|
||
|
|
8c1c2c310e |
fix(i18n): translate Turkish (tr) UI strings (#315)
Translate the ~950 previously-untranslated Turkish UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
|
||
|
|
ab37505e68 |
fix(i18n): translate Arabic (ar) UI strings (#314)
Translate the ~950 previously-untranslated Arabic UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
|
||
|
|
6ee1b0f7a1 |
fix(i18n): translate Ukrainian (uk) UI strings (#313)
Translate the ~950 previously-untranslated Ukrainian UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
|
||
|
|
7ce9c64b50 |
fix(i18n): translate Polish (pl) UI strings (#312)
Translate the ~950 previously-untranslated Polish UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
|
||
|
|
eb9a14a226 |
fix(i18n): translate Russian (ru) UI strings (#311)
Translate the ~950 previously-untranslated Russian UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
|
||
|
|
d036ff6d44 |
fix(i18n): translate Dutch (nl) UI strings (#309)
Translate the ~950 previously-untranslated Dutch UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
|
||
|
|
e7b412fc09 |
fix(i18n): translate Korean (ko) UI strings (#305)
Translate the ~950 previously-untranslated Korean UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
|