- store: removeFolder now also resets exploreTagsFolderId so the Explore
tag list is invalidated when a folder is removed, not just tagCloud
- db: delete_folder moves vector::delete_embedding calls outside the
transaction — sqlite-vec virtual-table DML is unreliable inside a
transaction and caused remove_folder to fail for folders with embeddings
- store: loadMoreImages now passes similarSourceFolderId as the fourth
arg to loadSimilarImages; omitting it caused each paginated load to
overwrite the state with similarFolderId, corrupting scope-switches
that use similarSourceFolderId afterward
- commands: semantic search doubling loop now returns when batch reaches
the 8192 cap rather than re-fetching the same IDs forever when filters
yield fewer than limit matches (P1 infinite loop)
- db/indexer: tagging worker checks is_tagging_job_processing instead
of is_tagging_job_cancelled so paused jobs reset to 'pending' are also
discarded; cancelled rows are deleted, paused rows kept for retry
- Lightbox: capture image id at form-submit time and compare against
currentImageIdRef in the addUserTag callback, matching the guard
already applied to the getImageTags effect
- indexer: WalkDir errors are no longer silently swallowed; paths under
unreadable subtrees are excluded from the missing-file deletion pass
using Path::starts_with (component-aware) so a transient permission
error cannot cascade into data loss or affect sibling directories
- hnsw_index: fetch folder embedding IDs before acquiring the read lock
in find_similar_image_matches; holding the lock across a SQLite query
was delaying concurrent ensure_index write-lock requests
- store/DuplicateFinder: add duplicateScanError state; scanDuplicates
now catches errors and stores them rather than silently leaving the
user with empty results and no feedback
- commands: delete_images_from_disk now returns Vec<i64> of IDs that
were actually removed from disk rather than a bare count, so callers
can act precisely on what succeeded vs. what failed
- store: deleteSelectedDuplicates uses the returned ID set to filter
duplicate groups (files that failed to delete stay visible for retry)
and invalidates both the global "all" cache and each affected folder
cache, not just the currently-viewed scope
- Lightbox: guard the getImageTags effect with a cancellation flag so
a response that arrives after the user has navigated to another image
cannot overwrite the new image's tag list
- vector: increment embedding_revision whenever delete_embedding is
called so the HNSW cache is invalidated after image/folder deletions,
not just after inserts
- hnsw_index: fix race in build_index — read the revision before
fetching embeddings and again after the parallel_insert; retry the
whole build if the revision advanced during construction, ensuring
the cached index always reflects a consistent snapshot
- commands: replace the fixed 4× over-fetch in semantic_search_images
with a progressive doubling loop; start at exactly `limit` candidates
and double the batch (capped at 8192) until the page is filled or the
vector table is exhausted, preventing both under- and over-fetching
indexer: clear duplicate scan cache (folder + global) at the end of
every do_index so stale groups are never presented after a reindex;
previously a file modified or replaced on disk would still appear as a
duplicate candidate from the cached result until a manual rescan
db/vector: replace timestamp-based HNSW revision with a monotonically
incremented integer counter (app_kv.embedding_revision); mark_embedding_ready
atomically increments the counter so two embeddings saved within the same
second correctly advance the revision; get_embedding_revision now reads
this counter instead of MAX(embedding_updated_at), preventing the HNSW
cache from serving stale vectors after same-second batch updates
db/commands: paginate tag search — add offset + COUNT query to
search_images_by_tag returning (images, total); TagSearchParams gains
offset field and command returns TagSearchPage; store handles both the
initial load and load-more path so results beyond PAGE_SIZE are reachable
commands: semantic search over-fetches by 4× when any post-query filter
(folder, media kind, favorites, rating) is active, then truncates to the
requested limit; previously well-rated matches ranked just beyond the bare
vector limit were silently omitted
db: wrap update_folder_path in a transaction so a path-rewrite failure
(e.g. uniqueness collision) rolls back the already-updated folder row
instead of leaving the DB in an inconsistent half-migrated state
store: invalidate exploreTagsFolderId when a tagging run completes so
Explore reflects the updated tag distribution without requiring a manual
folder switch; previously the cache was never busted by background tagging
captioner: set_caption_detail now marks CAPTION_SESSION_DIRTY so the
cached FlorenceCaptioner session is torn down and rebuilt with the new
prompt and token limit; previously only the file was updated on disk
db: reset_generated_captions now cancels in-flight caption jobs instead
of deleting them; the worker checks for a cancelled row before writing
back, so deleting the row allowed results to be written immediately after
a reset — now mirrors the clear_caption_jobs cancellation protocol
BackgroundTasks: caption_pending/ready/failed counts are now included in
pendingMediaWork, task stages, hasFailed checks, and the dismiss snapshot;
previously a folder with only caption work caused the panel to disappear
entirely and exposed no caption progress or failure status
- BackgroundTasks: per-task Retry now dispatches to the correct backend —
retryFailedEmbeddings for embedding failures, queueTaggingJobs for tagging
failures, or both; previously always called embedding retry only
- commands: tag-cloud cache key now hashes both image IDs and embedding bytes
(xxh3) so re-embedding a file after reindex correctly invalidates the cache;
removed now-dead fnv_hash_ids helper
- db: update_folder_path uses SUBSTR instead of replace() to rewrite only the
leading path prefix, preventing corruption when the old folder name also
appears in a subdirectory name
- db: add_user_tag promotes an existing AI-owned tag to user-owned on conflict
instead of DO NOTHING, preventing the tagger from deleting a manually added
tag that coincides with an AI tag
- db/commands: add clear_duplicate_scan_cache / invalidate_duplicate_scan_cache
so deletion removes the stale persisted cache entry; without this a restart
would reload groups containing deleted images
- store: setSimilarScope re-runs loadSimilarByRegion (with saved crop) when the
active collection is Region Search Results, instead of silently switching to
whole-image similarity and discarding the crop
- Backend: new `rename_folder` command (updates display name only, not path)
and `update_folder_path` command (rewrites image paths in DB before reindexing
so thumbnails/embeddings are not regenerated unnecessarily)
- DB: `rename_folder` and `update_folder_path` helpers; `scan_error` column on
folders table to surface indexing failures without silently dropping images
- Store: `renameFolder` and `updateFolderPath` actions
- Sidebar: right-click context menu on each folder row (Reindex, Rename, Locate
Folder, Remove from app); inline rename input on Enter/Escape/blur; missing-
folder recovery banner with Locate/Remove; hover icon buttons (reindex + remove
with Confirm/Cancel) preserved alongside the context menu
BackgroundTasks: dismiss no longer calls clearTaggingJobs; dismissal only
updates local dismissed state
DuplicateFinder: entering the view clears stale groups when the stored scan
scope differs from selectedFolderId; duplicateScanFolderId is recorded on
each scan and cache load so scope is always tracked
Tagger threshold: set_tagger_threshold now sets TAGGER_SESSION_DIRTY so the
worker rebuilds its WdTagger instance and applies the new threshold on the
next batch
Region search global scope: fetch offset+limit+2 candidates before removing
the source image so has_more is accurate across pages
Region search pagination: loadMoreImages now sets loadingImages and checks
galleryRequestToken before appending results, preventing duplicate pages and
stale responses corrupting a new collection
Folder recovery: when index_folder detects a missing path it records the
error in a new folders.scan_error column (ensure_column migration) and emits
done without deleting images, preserving them for recovery. A new
update_folder_path command rewrites image paths in the DB before reindexing
so thumbnails and embeddings are not needlessly regenerated for unchanged
files. The sidebar shows an amber warning icon on affected folders and a
recovery banner with Locate Folder and Remove actions
- upsert_image now resets ai_tagged_at, ai_rating, ai_tagger_model, and
ai_tagger_error to NULL and deletes AI-sourced image_tags when a file
is reindexed after content changes, preventing stale tags/ratings from
the previous version being shown and blocking re-tagging
- get_pending_tagging_jobs_excluding now applies the paused-folder
exclusion in SQL before LIMIT using folder_exclusion_clause, matching
the pattern used by caption and embedding job claimers; previously a
large paused-folder backlog could starve all unpaused folders
- Merged similarRequestToken into galleryRequestToken so all
gallery-producing requests share one generation counter; a late
similarity response can no longer overwrite a subsequently loaded
folder, and vice versa
- Duplicate scanner now emits one DuplicateGroup per distinct full hash
within a sample-hash bucket, preventing disjoint sets (A,A vs B,B)
from being merged and presented as a single deletable group
- Tagger model download now installs the shared ONNX runtime DLLs via
ensure_onnx_runtime so the tagger is ready on a clean install without
requiring the caption model to have been downloaded first
- Caption queue clear now follows the tagging worker pattern: marks
in-flight rows as cancelled and deletes only non-processing rows;
the caption worker skips writing results for cancelled jobs; startup
cleanup removes any leftover cancelled caption rows
- Region search pagination now stores the crop rect in state and uses
find_similar_by_region for subsequent pages instead of falling through
to loadImages which appended unrelated folder results
- Duplicate scanner now runs a full-file hash pass (Phase 3) for large
files (> 64 KB) after the sample-hash shortlist, preventing false
positives from reaching the destructive deletion workflow
- delete_images_from_disk now attempts filesystem removal before touching
the DB; only successfully deleted files have their rows removed,
keeping locked/permission-denied files visible in the library
- AI tag upsert guards user-sourced rows with WHERE source != 'user' so
a tagger re-run cannot silently overwrite manually added tags
- clear_tagging_jobs excludes 'cancelled' rows from the immediate DELETE
so the worker can observe cancellation via is_tagging_job_cancelled
before the rows are cleaned up at next startup
Register the Tauri notification plugin and request notification permission during startup. Send completion notifications for folder scans, embeddings, AI tagging, and duplicate scans, including failure counts where available.
Include tags used by a single image in Explore and invalidate the tag cache after edits so changes appear immediately. Add an accessible, time-limited confirmation step before removing indexed folders from the sidebar.
Move sqlite-vec embedding deletions outside the image transaction to avoid transactional virtual-table failures. Emit a terminal indexing progress event on errors so the frontend reloads partial state and clears its active scan state.
Adds a "Search within image" button to the lightbox that lets the user
draw a crop region on an image and find visually similar results using
that crop's embedding. The crop is embedded in-memory without a temp
file via a new embed_image_crop method on ClipImageEmbedder.
Introduces a folder-scoped cosine search (search_image_ids_by_embedding_in_folder)
to support the current-folder scope option, and wires up the new
find_similar_by_region Tauri command end-to-end from Rust through to
the Zustand store and Lightbox UI.
Introduces tagger.rs for WD-based image tagging with CSV label support.
Adds csv, ureq, and zip dependencies; updates gitignore to exclude Python/JSON files.