From 20c43d205614d675bf6d4512031abed8e0197833 Mon Sep 17 00:00:00 2001 From: LyAhn Date: Sun, 7 Jun 2026 22:30:02 +0100 Subject: [PATCH] fix: address P1 and P2 review issues from PR #8 round 7 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 --- src-tauri/src/db.rs | 14 ++++++++++++++ src-tauri/src/indexer.rs | 5 +++++ src-tauri/src/vector.rs | 18 +++++++++++------- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src-tauri/src/db.rs b/src-tauri/src/db.rs index 83d9ac2..572212d 100644 --- a/src-tauri/src/db.rs +++ b/src-tauri/src/db.rs @@ -263,6 +263,13 @@ pub fn migrate(conn: &Connection) -> Result<()> { groups_json TEXT NOT NULL ); + CREATE TABLE IF NOT EXISTS app_kv ( + key TEXT PRIMARY KEY, + value INTEGER NOT NULL DEFAULT 0 + ); + + INSERT OR IGNORE INTO app_kv (key, value) VALUES ('embedding_revision', 0); + CREATE INDEX IF NOT EXISTS idx_images_folder_id ON images(folder_id); CREATE INDEX IF NOT EXISTS idx_images_modified_at ON images(modified_at); CREATE INDEX IF NOT EXISTS idx_embedding_jobs_status ON embedding_jobs(status); @@ -915,6 +922,13 @@ pub fn mark_embedding_ready(conn: &Connection, image_id: i64, model: &str) -> Re params![image_id, model], )?; conn.execute("DELETE FROM embedding_jobs WHERE image_id = ?1", [image_id])?; + // Advance the monotonic revision so the HNSW cache is always rebuilt after + // any embedding change, regardless of clock resolution. + conn.execute( + "INSERT INTO app_kv (key, value) VALUES ('embedding_revision', 1) + ON CONFLICT(key) DO UPDATE SET value = value + 1", + [], + )?; Ok(()) } diff --git a/src-tauri/src/indexer.rs b/src-tauri/src/indexer.rs index a5d062d..25b4c94 100644 --- a/src-tauri/src/indexer.rs +++ b/src-tauri/src/indexer.rs @@ -373,6 +373,11 @@ fn do_index(app: AppHandle, pool: &DbPool, folder_id: i64, folder_path: PathBuf) let _ = db::backfill_embedding_jobs(&conn)?; db::update_folder_count(&conn, folder_id)?; let _ = db::clear_folder_scan_error(&conn, folder_id); + // Invalidate duplicate scan cache — any reindex can change file contents + // or the set of files, making cached duplicate groups stale. + let folder_scope = format!("folder:{}", folder_id); + let _ = db::clear_duplicate_scan_cache(&conn, &folder_scope); + let _ = db::clear_duplicate_scan_cache(&conn, "all"); } emit_progress( diff --git a/src-tauri/src/vector.rs b/src-tauri/src/vector.rs index 644ece8..25a3220 100644 --- a/src-tauri/src/vector.rs +++ b/src-tauri/src/vector.rs @@ -215,13 +215,17 @@ pub fn get_image_embedding(conn: &Connection, image_id: i64) -> Result Result { - let count: i64 = conn.query_row("SELECT COUNT(*) FROM image_vec", [], |row| row.get(0))?; - let max_updated_at: Option = conn.query_row( - "SELECT MAX(embedding_updated_at) FROM images WHERE embedding_status = 'ready'", - [], - |row| row.get(0), - )?; - Ok(format!("{}:{}", count, max_updated_at.unwrap_or_default())) + // Use the monotonically incremented app_kv counter so that two embeddings + // saved within the same clock second still advance the revision, preventing + // the HNSW cache from serving stale vectors. + let revision: i64 = conn + .query_row( + "SELECT COALESCE((SELECT value FROM app_kv WHERE key = 'embedding_revision'), 0)", + [], + |row| row.get(0), + ) + .unwrap_or(0); + Ok(revision.to_string()) } // fn image_ids_for_folder(