From f9760fe8a8e7968773120fb6cba94bb426808d71 Mon Sep 17 00:00:00 2001 From: Psychotoxical <171614930+Psychotoxical@users.noreply.github.com> Date: Fri, 17 Jul 2026 02:19:40 +0200 Subject: [PATCH] fix(dev): push manifest-only changes in theme-watch (#1318) --- src-tauri/src/lib.rs | 93 ++++++++++++++++++++++++++++---------------- 1 file changed, 59 insertions(+), 34 deletions(-) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 28612ae0..293c3725 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -444,12 +444,14 @@ pub fn run() { } } - // Per-file state: mtime (gates the read while a - // file is unchanged) and last pushed payload - // (change detection + ready re-send). Entries only - // update after a successful emit, so a failed push - // retries next tick instead of being marked seen. - type Seen = HashMap, serde_json::Value)>; + // Per-file state: css + manifest mtimes (gate the + // reads while both files are unchanged) and last + // pushed payload (change detection + ready + // re-send). Entries only update after a successful + // emit, so a failed push retries next tick instead + // of being marked seen. + type Stamps = (Option, Option); + type Seen = HashMap; let seen: Arc> = Arc::new(Mutex::new(HashMap::new())); // The frontend announces its listeners (on mount @@ -495,46 +497,46 @@ pub fn run() { WatchTarget::File(f) => vec![f.clone()], }; for f in files { - let mtime = + let css_mtime = std::fs::metadata(&f).and_then(|m| m.modified()).ok(); + // The sibling manifest is part of the + // watched payload — a version bump alone + // must reach the UI (tester report: it + // didn't), so its stamp gates too. + let manifest_path = + f.parent().map(|d| d.join("manifest.json")); + let (manifest_exists, manifest_mtime) = match manifest_path + .as_ref() + .map(std::fs::metadata) + { + Some(Ok(md)) => (true, md.modified().ok()), + _ => (false, None), + }; + let stamps = (css_mtime, manifest_mtime); let Ok(mut m) = seen.lock() else { continue }; - // mtime gate: skip the full read while the - // stamp is unchanged (a None stamp never - // matches, so filesystems without mtime - // fall back to read + compare). - if let Some((prev_mtime, _)) = m.get(&f) { - if prev_mtime.is_some() && *prev_mtime == mtime { + // mtime gate: skip the full reads while + // both stamps are unchanged (a None stamp + // never matches an existing file, so + // filesystems without mtime fall back to + // read + compare). + if let Some(((prev_css, prev_manifest), _)) = m.get(&f) { + let css_fresh = + prev_css.is_some() && *prev_css == css_mtime; + let manifest_fresh = *prev_manifest == manifest_mtime + && (manifest_mtime.is_some() || !manifest_exists); + if css_fresh && manifest_fresh { continue; } } let Ok(css) = std::fs::read_to_string(&f) else { continue; }; - let event = match m.get_mut(&f) { - // mtime-only touch — restamp quietly. - Some(entry) - if entry.1.get("css").and_then(|c| c.as_str()) - == Some(css.as_str()) => - { - entry.0 = mtime; - continue; - } - // First sight in directory mode - // installs without stealing the - // active theme; a save applies. - None if matches!(target, WatchTarget::Dir(_)) => { - "theme-watch:css-seed" - } - _ => "theme-watch:css", - }; // Ship the sibling manifest's metadata so // the frontend can show the theme's real // name/author/version instead of dev // placeholders (and the update badge // stays quiet). - let manifest = f - .parent() - .map(|d| d.join("manifest.json")) + let manifest = manifest_path .and_then(|p| std::fs::read_to_string(p).ok()) .and_then(|s| { serde_json::from_str::(&s).ok() @@ -554,8 +556,31 @@ pub fn run() { "description": meta("description"), "mode": meta("mode"), }); + let event = match m.get_mut(&f) { + // mtime-only touch — restamp quietly. + Some(entry) if entry.1 == payload => { + entry.0 = stamps; + continue; + } + // Metadata-only change (version bump + // etc.) refreshes the install without + // stealing the active theme. + Some(entry) + if entry.1.get("css").and_then(|c| c.as_str()) + == Some(css.as_str()) => + { + "theme-watch:css-seed" + } + // First sight in directory mode + // installs without stealing the + // active theme; a save applies. + None if matches!(target, WatchTarget::Dir(_)) => { + "theme-watch:css-seed" + } + _ => "theme-watch:css", + }; if handle.emit(event, &payload).is_ok() { - m.insert(f, (mtime, payload)); + m.insert(f, (stamps, payload)); } } std::thread::sleep(std::time::Duration::from_millis(300));