Settings: highlight the selected adapter, restyle radios/password/select for the dark theme, and keep saved settings scoped to the chosen adapter. CI: run npm test + gitleaks on push/PR so the suite is an actual gate.
SwipeAnything
Unleash the swipe on anything. Point it at a folder or a photo library, and (via adapters) at your inbox, a database table, or whatever else you need to triage — one card at a time, right = keep, left = reject, like a dating app for your backlog.
Why
Tinder-style triage tools already exist, but each one is wired to a single source: an image folder (image-tinder, photo-tinder-desktop), an inbox (SwipeMail), or an AI approval queue (decision-desk).
SwipeAnything separates the swipe UI from the source. An adapter turns any collection of things into a queue of cards; the UI, keyboard shortcuts, drag gestures, undo stack, and settings form all work the same regardless of what's behind them. Write a new adapter and you get the whole UI for free.
Status
Two adapters ship today — local folder and Immich (self-hosted photo library) — proving the framework works for both a filesystem and a remote API. Email, database-row, and other adapters are welcome as contributions; see CONTRIBUTING.md.
Features
- Swipe (touch/mouse drag) or use the keyboard
- Non-destructive by default: "reject" moves files to a
.swipeanything-trash/folder (or, for Immich, the library's own trash) — never a hard delete. A separate, confirm-guarded "Empty trash" action is the only place that permanently deletes anything. - Undo, any number of steps back
- Session resume: closing the tab, reloading, or restarting the server picks up right where you left off (as long as the underlying items haven't changed)
- Real thumbnails on macOS (via Quick Look), including HEIC/HEIF and video poster frames, with automatic fallback to the original file everywhere else
- Native "Browse…" folder picker on macOS; type a path anywhere else
- Live progress + per-action counts
- Generic settings UI: every adapter declares its own config fields and gets a form for free — no adapter-specific frontend code required
- Accessible by default: live region announcements per card, labeled action
buttons, a focus-trapped shortcuts dialog, visible focus rings, and a real
role="progressbar" - Zero build step: Node.js + Express + vanilla HTML/CSS/JS
npm testruns a real test suite (folder adapter, Immich adapter with a mocked API, and the HTTP API end-to-end) — no separate test server to stand up- CI on every push/PR (Gitea Actions):
npm test+ gitleaks
Quickstart
git clone https://git.levkin.ca/ilia/SwipeAnything.git
cd SwipeAnything
npm install
npm start
Open http://localhost:5757. On first run you'll land on Settings —
pick an adapter (the selected one is highlighted), fill in its settings
(Browse… opens a Finder dialog on macOS for folder paths), and start swiping.
Alternatively, copy swipeanything.config.example.json to
swipeanything.config.json and edit it directly:
cp swipeanything.config.example.json swipeanything.config.json
Running the tests
npm test
Uses Node's built-in test runner (node --test) — no extra dev dependencies.
The Immich adapter is tested with a mocked fetch, so nothing here needs a
live server. The same suite is the PR gate in .gitea/workflows/ci.yml.
Controls
| Action | Gesture | Key | Button |
|---|---|---|---|
| Keep | Drag right | → |
Keep → |
| Reject (moves to trash) | Drag left | ← |
Reject ← |
| Skip / next (no decision) | — | ↓ or Space |
Skip ↓ |
| Undo / go back | — | ↑ or Ctrl/Cmd+Z |
Undo ↑ |
| Shortcuts help | — | Shift+? |
? in header |
Keep/Reject (button or key) flash the KEEP/REJECT stamp and fling the card, same as a drag.
Adapters
Local folder
Point at any local folder. Optionally recurse into subfolders and filter by
extension. "Reject" moves the file into .swipeanything-trash/ next to the
source; "Empty trash" (a separate, confirm-guarded action shown in the header
once there's anything to empty) permanently deletes what's in there.
Immich
Point at a self-hosted Immich server + API key and swipe through your photo library newest-first (or randomly). "Reject" moves the asset to Immich's own trash via the API; "Undo" restores it from there. This is an early adapter — tested against the documented API shape, not every Immich version, so please open an issue/PR if something doesn't match your server.
How adapters work
Every adapter implements a small contract (adapters/base.js): declare a
settings schema, list the items to review, apply/undo actions on them, and
stream a preview/thumbnail. adapters/folder.js (local filesystem) and
adapters/immich.js (remote API) are two different reference
implementations — read whichever is closer to what you're building, then see
CONTRIBUTING.md for a full walkthrough of writing your own
(email, database rows, RSS, anything).
UI (public/) --> Express API (server.js) --> Adapter Registry --> your adapter
Project layout
server.js Express app: API + static file serving
adapters/base.js Adapter contract every adapter implements
adapters/folder.js Reference adapter: local files/folders
adapters/immich.js Reference adapter: remote API (Immich)
adapters/registry.js Adapter registration
lib/thumbnails.js macOS Quick Look-based thumbnail cache
public/ Vanilla HTML/CSS/JS swipe UI + settings UI
test/ node:test suite (adapters + HTTP API)
swipeanything.config.example.json Copy to swipeanything.config.json to configure
Notes on platform-specific features
A few conveniences use macOS system tools instead of adding npm dependencies, and quietly no-op elsewhere:
- Thumbnails use
qlmanage(Quick Look). Falls back to serving the original file if generation fails or times out, or on non-macOS platforms. - Folder picker uses
osascript/Finder. On other platforms, or if automation permission is denied, just type the path into the field instead.
Roadmap ideas
- Email adapter (IMAP): swipe archive/delete/label
- Generic JSON/CSV/database-row adapter: swipe to tag or update a status column
- Multi-select "later" bucket as a first-class third action everywhere
- Mobile-friendly PWA wrapper
Changelog
See CHANGELOG.md.
License
MIT, see LICENSE.