feat(theme-sync): persistent switch notification so slow switches read as progress

The "Applying theme — rebuilding…" toast used the default timeout, so it
vanished seconds into a multi-minute home-manager switch — leaving the
user with no signal and the impression the selection failed. Now it's
persistent (timeout 0) and all theme notifications share a synchronous
tag, so swaync REPLACES it in place with "Theme applied ✓" (or a critical
failure toast that persists) when the rebuild finishes.

notify() grew persistent/urgency/summary params. Honest indeterminate
feedback — HM exposes no percentage, so no fake progress bar.

Verified: py_compile clean; the full apply→switch→notify path runs end to
end (dummy NOMARCHY_REBUILD). The in-place replacement is visible only in
a real swaync session.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Bernardo Magri
2026-06-13 15:52:41 +01:00
parent 20df0d8a60
commit 084b2a0500
3 changed files with 31 additions and 26 deletions

View File

@@ -59,10 +59,27 @@ def die(msg: str) -> "None":
sys.exit(1)
def notify(body: str) -> None:
if shutil.which("notify-send"):
subprocess.run(["notify-send", "-a", "Nomarchy", "Nomarchy", body],
capture_output=True)
# All Nomarchy theme notifications share this synchronous tag, so the
# notification daemon (swaync) REPLACES the previous one in place instead
# of stacking — the "rebuilding…" toast becomes the "applied ✓" toast.
NOTIFY_SYNC_TAG = "nomarchy-theme-sync"
def notify(body: str, summary: str = "Nomarchy", *,
persistent: bool = False, urgency: str = "normal") -> None:
"""Desktop notification. persistent (timeout 0) keeps it up for the
whole rebuild so a slow switch never reads as a failed selection;
the success/failure call replaces it via the synchronous tag."""
if shutil.which("notify-send") is None:
return
subprocess.run(
["notify-send", "-a", "Nomarchy",
"-u", urgency,
"-t", "0" if persistent else "5000",
"-h", f"string:x-canonical-private-synchronous:{NOTIFY_SYNC_TAG}",
summary, body],
capture_output=True,
)
# ─── State management ─────────────────────────────────────────────────────
@@ -145,10 +162,13 @@ def run_switch() -> None:
return
log(f"rebuilding: {' '.join(argv)}")
notify("Applying theme — rebuilding the desktop…")
# Persistent (timeout 0): stays up for the whole rebuild — replaced
# in place by the success/failure notification below — so a multi-
# minute switch never looks like it silently failed.
notify("Applying theme — rebuilding the desktop…", persistent=True)
result = subprocess.run(argv) # stream output to the caller's terminal
if result.returncode != 0:
notify("Theme rebuild FAILED — see terminal / journal")
notify("Theme rebuild FAILED — see terminal / journal", urgency="critical")
die("rebuild failed (state file already updated; fix and re-run)")
notify("Theme applied ✓")