From 4af6a4f625b1965b783c9efb857f8ac665adb8eb Mon Sep 17 00:00:00 2001 From: Igor Soarez Date: Tue, 20 Oct 2026 12:43:07 +0300 Subject: rebuild the status model as herdr's state machine, event-driven and tested the seen logic was inverted: first observations were marked done and real completions idle, so the menu showed teal for agents that did nothing and green for agents that finished, and attending changed nothing visible. - watcher::advance is herdr's apply_pane_state_change + mark_active_tab_seen as a pure function, unit-tested against herdr's own cases; attendance is window-level like herdr's active tab - hook reports are events applied in order, so a turn shorter than a poll is still a completion - event-driven: hook reports, dingger focus, and tmux focus hooks wake the watcher; notifications are delivered off-thread (the macos call blocked ticks for 2+ seconds) after the pane options are written - all per-pane state persists in tmux pane options, so restarts and deploys lose nothing, including the authoritative hook state - every click goes through dingger focus: the menu bar shells out to it and the status-line binding runs it; it picks the right client, skips the window already shown, and activates the terminal app hosting the client - the ready queue is ordered like the menu (blocked first, then oldest done) - menu bar and tmux segment render herdr's symbols in herdr's palette - tests/e2e.sh: 60 assertions against an isolated tmux server with a real client, a 30s poll interval, and a slow fake notifier --- crates/dinggerd/src/notify.rs | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) (limited to 'crates/dinggerd/src/notify.rs') diff --git a/crates/dinggerd/src/notify.rs b/crates/dinggerd/src/notify.rs index 9fcfcde..e619d6d 100644 --- a/crates/dinggerd/src/notify.rs +++ b/crates/dinggerd/src/notify.rs @@ -3,7 +3,7 @@ use std::process::{Command, Stdio}; use std::sync::atomic::{AtomicU64, Ordering}; -use tracing::{debug, warn}; +use tracing::{info, warn}; pub enum NotificationKind { NeedsAttention, @@ -63,14 +63,27 @@ pub fn play_sound(kind: &NotificationKind) { } } +/// Deliver a macOS notification without blocking the caller: the delivery +/// call waits on Notification Center for seconds, and the watcher must keep +/// updating the UI meanwhile. pub fn send_notification(agent_label: &str, kind: &NotificationKind, context: &str) { - debug!(agent = agent_label, kind = kind.title(), context, "notifying"); + info!(agent = agent_label, kind = kind.title(), context, "notifying"); let title = format!("{agent_label} {}", kind.title()); - let notification = mac_notification_sys::Notification::new() - .title(title.as_str()) - .message(context) - .send(); - if let Err(err) = notification { - warn!(%err, "failed to deliver macOS notification"); - } + let context = context.to_string(); + std::thread::spawn(move || { + // Test seam: deliver through a command instead (tests/e2e.sh). + if let Ok(command) = std::env::var("DINGGER_NOTIFIER") { + let _ = std::process::Command::new("sh") + .args(["-c", &command, &title, &context]) + .status(); + return; + } + let notification = mac_notification_sys::Notification::new() + .title(title.as_str()) + .message(context.as_str()) + .send(); + if let Err(err) = notification { + warn!(%err, "failed to deliver macOS notification"); + } + }); } -- cgit v1.2.3