991670d863
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.