- Replace BigInt hammingDistance with 32-bit popcount — safer (no throw
on malformed hashes) and ~10× faster
- Make scan() async with setImmediate chunking so the event loop stays
responsive on large libraries
- Batch getGroups() photo lookup in slices of 900 to stay under SQLite's
999-variable limit
- Add POST /api/duplicates/keep-best endpoint: scores by resolution then
filesize, soft-deletes the losers, dismisses the pair
- DuplicateGroup: green "Keep" badge on the best photo, green "Keep best"
button in footer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Settings panel (⚙ in header):
- Watch Folders: add/remove paths, toggle enabled; worker polls every 30s
and calls watcher.add/unwatch without restart
- Ingestion: thumb/preview size and quality, applied per-file at ingest time
- Duplicate Detection: configurable Hamming distance threshold
- Danger Zone: clear all dismissed duplicate pairs
Multi-folder scanning:
- watch_folders table replaces single PHOTO_DIR env var
- PHOTO_DIR env var seeds the first entry on startup
- settings table with defaults for all tunable values
- Worker reads settings from DB on each drain cycle so changes are live
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>