aboutsummaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-28 15:22:12 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-28 15:23:04 +0300
commit28222b20890a1f6cd36fd477de8e9bc6e5537ff5 (patch)
tree75d54cf3a1d43f22b85adcfc6712b08cf31482a6 /src/server
parenta90c90aa4aa92142e4684a81dcb7c2cab9aaac6b (diff)
fix: use live workspace labels in notifications
refs #332
Diffstat (limited to 'src/server')
-rw-r--r--src/server/headless.rs10
-rw-r--r--src/server/notifications.rs90
2 files changed, 99 insertions, 1 deletions
diff --git a/src/server/headless.rs b/src/server/headless.rs
index 18717782..e6576df6 100644
--- a/src/server/headless.rs
+++ b/src/server/headless.rs
@@ -1218,6 +1218,7 @@ impl HeadlessServer {
} else {
toast_message_from_state_change(
&self.app.state,
+ &self.app.terminal_runtimes,
pane_id_val,
suppress_active_tab_notifications,
prev_state,
@@ -1302,6 +1303,7 @@ impl HeadlessServer {
} else {
toast_message_from_state_change(
&self.app.state,
+ &self.app.terminal_runtimes,
pane_id_val,
suppress_active_tab_notifications,
prev_state,
@@ -2115,12 +2117,17 @@ impl HeadlessServer {
crate::app::state::ToastKind::Finished => "finished",
crate::app::state::ToastKind::UpdateInstalled => "updated",
};
+ let workspace_label = self.app.state.workspaces[*ws_idx].display_name_from(
+ &self.app.state.terminals,
+ &self.app.terminal_runtimes,
+ );
let msg_text = format!(
"{} {}: {}",
agent_label,
event_text,
crate::app::actions::notification_context(
&self.app.state.workspaces[*ws_idx],
+ &workspace_label,
*ws_idx,
*pane_id,
)
@@ -2491,7 +2498,10 @@ impl HeadlessServer {
.agent_metadata_deadline
.filter(|deadline| now >= *deadline)
{
+ let previous_toast = self.app.state.toast.clone();
for update in self.app.state.expire_agent_metadata_at(deadline, now) {
+ self.app
+ .refresh_new_herdr_toast_context_for_update(&update, &previous_toast);
self.app.emit_pane_state_update(&update);
}
self.app.sync_agent_metadata_deadline();
diff --git a/src/server/notifications.rs b/src/server/notifications.rs
index 1729c698..0ed681ac 100644
--- a/src/server/notifications.rs
+++ b/src/server/notifications.rs
@@ -4,6 +4,7 @@ use crate::config;
use crate::detect::AgentState;
use crate::layout::PaneId;
use crate::protocol;
+use crate::terminal::TerminalRuntimeRegistry;
pub(crate) fn should_forward_toast_to_clients(delivery: config::ToastDelivery) -> bool {
toast_notify_kind(delivery).is_some()
@@ -19,6 +20,7 @@ pub(crate) fn toast_notify_kind(delivery: config::ToastDelivery) -> Option<proto
pub(crate) fn toast_message_from_state_change(
state: &AppState,
+ terminal_runtimes: &TerminalRuntimeRegistry,
pane_id: PaneId,
suppress_active_tab_notifications: bool,
prev_state: AgentState,
@@ -41,11 +43,12 @@ pub(crate) fn toast_message_from_state_change(
.terminals
.get(&pane.attached_terminal_id)
.and_then(|terminal| terminal.effective_agent_label())?;
+ let workspace_label = ws.display_name_from(&state.terminals, terminal_runtimes);
Some(format!(
"{} {}: {}",
agent_label,
toast_event_text(kind),
- app::actions::notification_context(ws, ws_idx, pane_id)
+ app::actions::notification_context(ws, &workspace_label, ws_idx, pane_id)
))
})
})
@@ -58,3 +61,88 @@ fn toast_event_text(kind: app::state::ToastKind) -> &'static str {
app::state::ToastKind::UpdateInstalled => "updated",
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::detect::Agent;
+ use crate::terminal::TerminalState;
+
+ fn init_repo(path: &std::path::Path) {
+ let status = std::process::Command::new("git")
+ .args(["init", "-q"])
+ .current_dir(path)
+ .status()
+ .unwrap();
+ assert!(status.success(), "git init failed for {}", path.display());
+ }
+
+ #[tokio::test]
+ async fn toast_message_uses_live_root_runtime_cwd_label() {
+ let mut state = AppState::test_new();
+ state
+ .workspaces
+ .push(crate::workspace::Workspace::test_new("stale"));
+ state.ensure_test_terminals();
+ let root = state.workspaces[0].tabs[0].root_pane;
+ let terminal_id = state.workspaces[0].terminal_id(root).cloned().unwrap();
+ let temp_root = std::env::temp_dir().join(format!(
+ "herdr-forwarded-toast-context-{}-{}",
+ std::process::id(),
+ std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .unwrap()
+ .as_nanos()
+ ));
+ let stale_cwd = temp_root.join("__herdr_original__");
+ let live_cwd = temp_root.join("__herdr_projects__");
+ std::fs::create_dir_all(&stale_cwd).unwrap();
+ std::fs::create_dir_all(&live_cwd).unwrap();
+ init_repo(&stale_cwd);
+ init_repo(&live_cwd);
+ state.workspaces[0].custom_name = None;
+ state.workspaces[0].identity_cwd = stale_cwd.clone();
+ let mut terminal = TerminalState::new(terminal_id.clone(), stale_cwd);
+ terminal.set_detected_state(Some(Agent::Codex), AgentState::Idle);
+ state.terminals.insert(terminal_id.clone(), terminal);
+ let (events, _) = tokio::sync::mpsc::channel(4);
+ let runtime = crate::terminal::TerminalRuntime::spawn(
+ root,
+ 24,
+ 80,
+ live_cwd.clone(),
+ 0,
+ crate::terminal_theme::TerminalTheme::default(),
+ "/bin/sh",
+ events,
+ std::sync::Arc::new(tokio::sync::Notify::new()),
+ std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
+ )
+ .unwrap();
+ let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2);
+ while runtime.cwd() != Some(live_cwd.clone()) && std::time::Instant::now() < deadline {
+ tokio::time::sleep(std::time::Duration::from_millis(10)).await;
+ }
+ let mut terminal_runtimes = TerminalRuntimeRegistry::new();
+ terminal_runtimes.insert(terminal_id, runtime);
+
+ let message = toast_message_from_state_change(
+ &state,
+ &terminal_runtimes,
+ root,
+ false,
+ AgentState::Working,
+ AgentState::Idle,
+ );
+
+ assert_eq!(
+ message.as_deref(),
+ Some("codex finished: __herdr_projects__ ยท 1")
+ );
+
+ for (_, runtime) in terminal_runtimes.drain() {
+ runtime.shutdown();
+ }
+ let _ = std::fs::remove_dir_all(temp_root);
+ }
+}