ilia 4aa0014d70
CI / Unit tests (push) Successful in 39s
CI / Gitleaks (push) Successful in 29s
Merge pull request 'docs: public mirror hygiene' (#6) from chore/public-docs-hygiene into main
2026-08-05 19:50:34 -05:00

SwipeAnything

Triage any collection one card at a time: right = keep, left = reject. Point it at a folder, a photo library, or (via adapters) an inbox, a database table, or another queue source.

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 keyboard shortcuts
  • Organize with keys 09: map destination folders in Settings, then press a number or tap the chip to move the current file (undo brings it back)
  • Inspect before deciding: press i for path, size, dates, and (on macOS) image dimensions and camera metadata; Reveal in Finder and Copy path
  • Non-destructive reject: moves files to .swipeanything-trash/ (or Immich library trash); permanent delete only via confirm-guarded Empty trash
  • Undo any number of steps back
  • Session resume after tab close, reload, or server restart (while underlying items are unchanged)
  • Thumbnails on macOS via Quick Look (HEIC/HEIF, video poster frames); falls back to original file elsewhere
  • Native Browse folder picker on macOS; type a path on other platforms
  • Live progress and per-action counts
  • Generic settings UI: each adapter declares config fields and gets a form without custom frontend code
  • Accessibility: live region announcements, labeled buttons, focus-trapped shortcuts dialog, visible focus rings, role="progressbar"
  • Zero build step: Node.js, Express, vanilla HTML/CSS/JS
  • npm test covers folder adapter, Immich adapter (mocked API), and HTTP API end-to-end
  • 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 (leave in place) 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 ↑
Move to organize folder 09 (if configured) chips under the main buttons
File details (path, size, dimensions, …) i Details in header
Shortcuts help Shift+? ? in header

Keep/Reject (button or key) flash the KEEP/REJECT stamp and fling the card, same as a drag. Numbered organize actions flash the destination label and lift the card away. Details open as a bottom sheet over the card so the layout doesnt jump.

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.

In Settings, fill any of the Organize folders (keys 09) rows with a destination path (and optional label). Only configured keys appear as actions — leave a row blank to disable it. Destination folders are created if missing, and if a destination sits inside the source tree it is skipped when listing so sorted files don't reappear in the queue.

Press i (or Details in the header) to inspect the current file before deciding: full path, size, dates, and on macOS image dimensions / camera / capture date when Spotlight knows them. Details open as a bottom sheet over the card. Reveal in Finder and Copy path are there too. Turn on Show file details on each card by default in Settings if you always want the sheet open.

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.

S
Description
A local-first, pluggable swipe-to-triage UI. Point it at a folder today; write an adapter and point it at anything tomorrow.
Readme MIT
202 KiB
Languages
JavaScript 79.6%
CSS 13.4%
HTML 3.6%
Shell 3.4%