From 991670d863578510fa3bc10781ef44129fef54c0 Mon Sep 17 00:00:00 2001 From: 2ro <17595647+2ro@users.noreply.github.com> Date: Tue, 16 Jun 2026 22:58:07 -0400 Subject: [PATCH] nostr: pause the @name re-verify sweep while the app is backgrounded MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 78s sweep fired regardless of whether anyone was looking, spending mixnet round-trips in the background. Gate it on a frame heartbeat: the GUI stamps crate::mark_frame() each draw (with a light ~2s repaint cadence so it stays fresh while visible); when the app is backgrounded eframe stops drawing and the stamp goes stale, so crate::app_foreground() reads false and the sweep skips. The skip deliberately does NOT advance last_name_sweep, so the first tick after the app returns to the foreground runs the sweep immediately — catching up on resume rather than waiting out another full interval. Heartbeat lives at the crate root so nostr reads it without depending on the gui module. --- src/gui/app.rs | 7 +++++++ src/lib.rs | 32 ++++++++++++++++++++++++++++++++ src/nostr/client.rs | 6 +++++- 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/gui/app.rs b/src/gui/app.rs index 9bde766..45c7495 100755 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -77,6 +77,13 @@ impl App { self.first_draw = false; } + // Heartbeat for "is the app on-screen": stamp this frame, and keep a light + // ~2s repaint cadence so the stamp stays fresh while visible. When the app + // is backgrounded eframe stops calling this, the stamp goes stale, and + // background workers (the @name re-verify sweep) pause until we're back. + crate::mark_frame(); + ctx.request_repaint_after(std::time::Duration::from_secs(2)); + // Keep the Android status-bar icons readable against the in-app theme // (the app draws edge-to-edge over the status bar). Only on change. let white_icons = crate::gui::theme::status_bar_white_icons(); diff --git a/src/lib.rs b/src/lib.rs index 3a34530..e4073a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -378,6 +378,38 @@ pub fn on_data(data: String) { *w_data = Some(data); } +/// Unix-seconds timestamp of the most recent GUI frame. Background workers read +/// it to tell whether the app is actually on-screen: while the app is +/// backgrounded, eframe stops calling the per-frame draw and this stops +/// advancing. Crate-root so both `gui` and `nostr` can reach it without coupling. +static LAST_FRAME_AT: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(0); + +/// A frame older than this many seconds means the app isn't drawing — i.e. it's +/// backgrounded/occluded. The GUI keeps a ~2s repaint heartbeat while visible, so +/// this leaves a couple of frames of margin before declaring "not foreground". +const FOREGROUND_STALE_SECS: i64 = 5; + +fn now_unix_secs() -> i64 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_secs() as i64) + .unwrap_or(0) +} + +/// Stamp that the GUI just drew a frame. Called once per frame from the app loop. +pub fn mark_frame() { + LAST_FRAME_AT.store(now_unix_secs(), std::sync::atomic::Ordering::Relaxed); +} + +/// True when the GUI drew a frame within the last few seconds — i.e. the app is +/// foreground and visible. While backgrounded (no frames), returns false, so +/// periodic background work (the @name re-verify sweep) can pause and catch up +/// on resume instead of burning mixnet round-trips while nobody's looking. +pub fn app_foreground() -> bool { + let last = LAST_FRAME_AT.load(std::sync::atomic::Ordering::Relaxed); + last != 0 && now_unix_secs() - last <= FOREGROUND_STALE_SECS +} + lazy_static! { /// Data provided from deeplink or opened file. pub static ref INCOMING_DATA: Arc>> = Arc::new(RwLock::new(None)); diff --git a/src/nostr/client.rs b/src/nostr/client.rs index 50e03c3..b689755 100644 --- a/src/nostr/client.rs +++ b/src/nostr/client.rs @@ -894,7 +894,11 @@ async fn run_service(svc: Arc, wallet: Wallet) { // Re-validate cached @usernames so a released/reassigned name // stops showing. Only the stalest few per sweep (capped) to bound // mixnet lookups; each worker re-checks against the identity server. - if now - last_name_sweep >= NAME_REVERIFY_INTERVAL_SECS { + // Skipped while the app is backgrounded — no point spending mixnet + // round-trips when nobody's looking. We DON'T advance last_name_sweep + // in that case, so the very next foreground tick runs the sweep + // immediately to catch up on resume. + if now - last_name_sweep >= NAME_REVERIFY_INTERVAL_SECS && crate::app_foreground() { last_name_sweep = now; svc.store.set_last_name_sweep_at(now); let mut due: Vec<_> = svc