Phase 4 of the 0.1.0 release prep:
- tauri-plugin-log: rotating file log (5 MB, Info) in the app log dir plus
stdout; all 54 backend println!/eprintln! sites migrated to log macros so
release builds finally produce diagnostics
- tauri-plugin-single-instance (registered first): second launches focus the
existing window instead of racing the same SQLite DB with a second worker
fleet; tauri-plugin-window-state persists window geometry
- real CSP replacing null (scripts self-only, media/images via the asset
protocol, IPC endpoints whitelisted, object-src none) with a devCsp
variant for Vite HMR
- asset protocol scope narrowed from '**' to thumbnails statically plus
per-folder runtime grants (setup, add_folder, update_folder_path)
Panic audit (plan item) concluded with no changes: worker loops already
catch and log all Result errors; remaining unwraps are startup fail-fast,
poisoned-lock convention, infallible-by-construction, or test code.
Phase 3 of the 0.1.0 release prep:
- tauri-plugin-updater + tauri-plugin-process wired into the builder, with
updater:default / process:allow-restart capabilities
- bundle.createUpdaterArtifacts: true; endpoint points at the GitHub
releases latest.json, pubkey baked into tauri.conf.json
- store: update status state machine (check / download progress / install)
with a quiet launch check (prod only) that stays silent on network errors
- UI: dismissible update toast with download progress, plus an Updates group
in Settings > General showing current version and manual check/install
Applies the stashed 'deps and cpu mode' work: cuda was hardcoded on the
candle dependency lines, making the candle-cuda feature flag dead and any
non-CUDA build impossible. Now default = [candle-cuda] for the main dev
machine, with pnpm dev:app:cpu / build:app:cpu (--no-default-features) for
CPU-only machines and CI. Also trims unused deps (log, uuid, tokio-full,
chrono-serde). Verified: cargo check --locked --no-default-features passes.
Phase 1 of the 0.1.0 release prep:
- LICENSE (MIT) plus license fields in Cargo.toml, package.json, and bundle config
- tauri.conf.json: publisher/copyright/descriptions/homepage so the NSIS
installer no longer derives 'jezz' from the identifier as manufacturer
- version: null in tauri.conf.json — Cargo.toml is now the version source
- bundle.targets narrowed from 'all' to nsis (updater-friendly, faster builds)
- track pnpm-workspace.yaml (esbuild build-script approval, needed by CI)
Major speedups for large-folder thumbnail generation:
- JPEGs decode through mozjpeg at the smallest DCT scale covering
320px instead of full resolution, with catch_unwind and a fallback
to the image crate for anything mozjpeg rejects. EXIF orientation
preserved in both paths.
- RGB8 pipeline end to end (no RGBA round-trip) and CatmullRom resize
in place of Lanczos3.
- Video posters grab the seeked frame directly instead of running the
ffmpeg thumbnail filter (which decoded ~100 frames), with decode
threads capped at 2 per process.
- Workers only sleep when the queue is empty rather than 250ms after
every batch. Image batches commit before videos start, and videos
run sequentially off the rayon pool (blocking ffmpeg waits were
starving image decode) with per-item commits so progress keeps
moving through slow stretches.
Monitors all registered folders using OS-native events (ReadDirectoryChangesW
on Windows) so new, modified, and deleted files are reflected in the gallery
automatically without manual reindexing.
Key design points:
- Adaptive blocking: recv() when no events pending (zero CPU), switches to
recv_timeout(earliest_deadline) only when debounce timers are running — wakes
exactly when the soonest event is ready, no busy-polling
- 500 ms per-path debounce coalesces rapid OS event bursts into one action
- Change detection preserved: build_record skips upsert if file_size + mtime
unchanged, preventing spurious thumbnail/embedding re-queues and avoiding
clobbering of existing metadata like thumbnail_path
- Access events (reads) filtered out; only Create/Modify/Remove trigger work
- Deletion path: emits watcher-deleted event with image IDs; frontend removes
those images from state and clears selectedImage if it was deleted
WatcherHandle stored in app state; add_folder / remove_folder / update_folder_path
commands keep the watched path set in sync with the DB.
Adds EXIF date extraction during indexing so photos can be sorted by when
they were actually taken, not just when the file was modified on disk.
- Add `taken_at` (nullable TEXT, ISO 8601) column to the images table via
ensure_column migration and a new idx_images_taken_at index
- Populate taken_at in build_record via extract_exif_date, which tries
DateTimeOriginal → DateTimeDigitized → DateTime tags and rejects all-zero
sentinel dates written by uninitialised cameras
- upsert_image uses taken_at = excluded.taken_at so a re-indexed file gets
fresh EXIF; stale dates from replaced files are not preserved
- Add taken_asc / taken_desc sort using COALESCE(taken_at, created_at) so
images without EXIF gracefully fall back to their creation date
- Surface as "Taken: newest / oldest" in the Toolbar sort dropdown
- Shift map_image_row column indices after the new taken_at (index 10)
Adds a full discovery and organisation layer to the gallery.
## AI Tagger
- WD tagger (ONNX via ort) with DirectML/CPU acceleration
- Per-image and per-folder tag queuing; configurable batch size and
confidence threshold; model downloaded on first use via ureq/zip
- Tags stored with source ('ai' | 'user'); user tags are never
overwritten by AI; AI tags protected from accidental promotion
- Tagger pause/resume per folder; in-flight batches discarded cleanly
on pause or cancellation without leaving jobs stuck in processing
## Semantic & Tag Search
- CLIP text-query embedding via candle (HuggingFace hub model)
- Progressive candidate doubling with filter post-processing so
folder/rating/media-kind filters do not silently under-return results
- Tag search with DB-backed pagination (offset + COUNT total)
- Filename search unchanged; prefix syntax: s:, t:, f: in search bar
## Similarity Search
- HNSW in-memory index (hnsw_rs) with monotonic embedding_revision
counter for cache invalidation; build_index retries if a concurrent
write advances the revision during construction
- Folder-scoped similar search uses brute-force cosine scan (no k limit)
- Region-based similarity: crop an arbitrary rectangle in the lightbox,
embed it with CLIP, find the nearest images globally or within folder
- Pagination for both similar and region results; scope toggle
(all media / current folder) re-runs the query without reopening image
## Duplicate Finder
- Three-phase detection: stat (size) → sample hash (4×16 KB windows)
→ full-file hash (xxh3, large files only) to eliminate false positives
- Filesystem-first deletion: only removes DB rows for files successfully
deleted from disk; failed files remain visible for retry
- Persisted scan cache (SQLite) with invalidation on reindex and deletion;
both global and per-folder scopes invalidated after any deletion
## Tag Cloud & Explore
- Visual cluster explore: k-means over CLIP embeddings, configurable k,
representative thumbnail per cluster; cache keyed on xxh3 of embedding set
- Tag explore: ranked tag list with image counts and representative
thumbnail per tag; invalidated when AI tagging completes or folder removed
- Tag autocomplete for search bar
## Folder Management
- Inline rename (display name only, not OS rename)
- Relocate: file-picker to update folder path; all image paths rewritten
using SUBSTR prefix replacement to avoid corrupting paths that contain
the folder name as a substring
- Missing-folder recovery banner: Locate or Remove, never silent deletion
- Right-click context menu on sidebar items: Reindex, Rename, Locate,
Remove; hover buttons preserved alongside context menu
## Background Tasks
- Per-folder progress panel with embedding, tagging, caption, and
thumbnail stage breakdown
- Retry button per task for failed embeddings and tagging jobs
- Completed-task notifications (system tray via tauri-plugin-notification)
- Scan errors shown inline; WalkDir permission errors protect existing
records from deletion rather than cascading data loss
## Backend correctness
- sqlite-vec DML performed outside transactions throughout (virtual-table
limitation); embedding revision incremented on delete as well as insert
- Tagging job discard checks status = 'processing' so paused jobs reset
to 'pending' are also skipped, not just cancelled ones
- Stale async responses in Lightbox guarded with cancellation flags and
currentImageIdRef for both tag-load and tag-add callbacks
- Duplicate cache cleared on reindex; folder-removal clears vector rows
outside transaction before deleting the folder row
Introduces an Explore view with a tag cloud that clusters image embeddings
using cosine k-means and labels clusters via vocabulary-nearest-neighbour
CLIP matching. Vocabulary embeddings are disk-cached (FNV hash-keyed) to
avoid redundant inference. Enables CUDA for candle dependencies and adds a
build.rs check that surfaces a clear error when the toolkit is missing.
- make Candle CUDA support opt-in so default builds work on CPU-only machines
- improve semantic search loading and empty states in the gallery
- keep semantic search UX clearer when no matches are found
Refs: #4
- add semantic text search with toolbar mode switching and sqlite-vec query support
- improve embedding progress visibility, failure recovery, and similar-image affordances
- add search clearing and keyboard controls for filename vs semantic search modes
- refine background task interactions and gallery/lightbox embedding states
Refs: #4
- add Candle + HF Hub CLIP image embedding pipeline with background embedding worker
- write image embeddings into sqlite-vec and expose similar-image lookup through a new backend command
- surface embedding progress and recovery in the UI, including retries for failed embeddings
- improve gallery/lightbox embedding UX and make similar-image actions directly accessible
Refs: #3, #4
Sets up Tauri v2 + React frontend with SQLite metadata store,
sqlite-vec for CLIP embedding infrastructure, and r2d2 connection
pool replacing the single Arc<Mutex<Connection>>. Indexer now uses
rayon for parallel file metadata collection.