- Add Cloudflare Pages deployment for landing page (snapotter.com) and
docs (docs.snapotter.com)
- Create deploy-landing.yml and update deploy-docs.yml workflows
- Update CI to ignore apps/landing/** paths
- Fix logo transparency (remove white background) across all apps
- Recreate social-preview.png with SnapOtter branding
- Update all docs URLs from GitHub Pages to docs.snapotter.com
- Update VitePress config: light theme default, fix llms.txt paths
- Add .vitepress/cache/ and .env.* to gitignore
- Add 8 new E2E specs for AI tools (upscale, enhance-faces, colorize,
restore-photo, erase-object, smart-crop, passport-photo, red-eye-removal)
closing all HIGH/MEDIUM coverage gaps from the test matrix audit
- Fix ensureAiDirs() crash on non-Docker environments by gating on
isDockerEnvironment() — prevents ENOENT when /data doesn't exist
- Bump torch 2.6.0→2.7.0 and torchvision 0.21.0→0.22.0 in feature
manifest for broader Python version compatibility
- Add Python 3.14 version guard warning in install_feature.py
- Remove duplicate torchvision shims from upscale.py and enhance_faces.py
(dispatcher.py already handles this at startup)
- Remove orphaned tools.batch i18n key and dead pipeline-builder filter
- Regenerate 4 visual regression baselines for current UI state
- Add data-testid to passport-photo generate button for E2E testability
The lock file stored process.pid (Node server PID) which is always
alive. When releaseInstallLock wasn't called (e.g., error path),
the lock persisted forever, making the bundle stuck in "installing"
and blocking all future installs.
Fix: remove PID from lock entirely. Use simple file existence as
mutex. On server startup, unconditionally delete any lock file.
Tracks which AI feature bundles are installed via /data/ai/installed.json
with atomic writes, in-memory caching, file-based install locks, and
startup recovery for interrupted installs.