aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-04-02 23:28:49 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-04-02 23:28:49 +0300
commit8c037355c192cbe1b5e8dfed68bbb1739aa52e7d (patch)
tree38ea34451a93cf56a1b3a6bb6be4547e159ce98d /src
parent290a57e3331bb25dcd16284099bf1dc6afa68ee5 (diff)
fix: merge hook authority with tab state updates
Diffstat (limited to 'src')
-rw-r--r--src/app/actions.rs187
1 files changed, 129 insertions, 58 deletions
diff --git a/src/app/actions.rs b/src/app/actions.rs
index 279cbabf..84d5e592 100644
--- a/src/app/actions.rs
+++ b/src/app/actions.rs
@@ -3,9 +3,10 @@
use tracing::{info, warn};
-use crate::detect::AgentState;
+use crate::detect::{Agent, AgentState};
use crate::events::AppEvent;
use crate::layout::{find_in_direction, NavDirection, PaneId};
+use crate::pane::EffectiveStateChange;
use super::state::{AppState, Mode, ToastKind, ToastNotification};
@@ -43,7 +44,7 @@ fn notification_toast_for_state_change(
}
}
-fn agent_label(agent: crate::detect::Agent) -> &'static str {
+fn agent_label(agent: Agent) -> &'static str {
match agent {
crate::detect::Agent::Pi => "pi",
crate::detect::Agent::Claude => "claude",
@@ -59,6 +60,16 @@ fn agent_label(agent: crate::detect::Agent) -> &'static str {
}
}
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct PaneStateUpdate {
+ pub pane_id: PaneId,
+ pub ws_idx: usize,
+ pub previous_agent: Option<Agent>,
+ pub previous_state: AgentState,
+ pub agent: Option<Agent>,
+ pub state: AgentState,
+}
+
// ---------------------------------------------------------------------------
// Workspace operations
// ---------------------------------------------------------------------------
@@ -275,9 +286,12 @@ impl AppState {
// ---------------------------------------------------------------------------
impl AppState {
- pub fn handle_app_event(&mut self, event: AppEvent) {
+ pub fn handle_app_event(&mut self, event: AppEvent) -> Vec<PaneStateUpdate> {
match event {
- AppEvent::PaneDied { pane_id } => self.handle_pane_died(pane_id),
+ AppEvent::PaneDied { pane_id } => {
+ self.handle_pane_died(pane_id);
+ Vec::new()
+ }
AppEvent::UpdateReady { version } => {
self.update_available = Some(version.clone());
self.update_dismissed = true;
@@ -286,65 +300,122 @@ impl AppState {
title: format!("updated to v{version}"),
context: "restart to use it".to_string(),
});
+ Vec::new()
}
AppEvent::StateChanged {
pane_id,
agent,
state,
- } => {
- for (ws_idx, ws) in self.workspaces.iter_mut().enumerate() {
- let workspace_name = ws.display_name();
- let Some(tab_idx) = ws.find_tab_index_for_pane(pane_id) else {
- continue;
- };
- if let Some(pane) = ws.tabs[tab_idx].panes.get_mut(&pane_id) {
- let is_active_ws = self.active == Some(ws_idx) && ws.active_tab == tab_idx;
- let prev_state = pane.state;
-
- // Mark unseen when transitioning to Idle in background
- if state == AgentState::Idle
- && prev_state != AgentState::Idle
- && !is_active_ws
- {
- pane.seen = false;
- }
-
- // Blocked prompts should always make noise; done sounds stay background-only.
- if self.sound.allows(agent) {
- if let Some(sound) =
- notification_sound_for_state_change(is_active_ws, prev_state, state)
- {
- crate::sound::play(sound);
- }
- }
-
- if self.toast_config.enabled {
- if let (Some(agent), Some(kind)) = (
- agent,
- notification_toast_for_state_change(
- is_active_ws,
- prev_state,
- state,
- ),
- ) {
- let event_text = match kind {
- ToastKind::NeedsAttention => "needs attention",
- ToastKind::Finished => "finished",
- ToastKind::UpdateInstalled => "updated",
- };
- self.toast = Some(ToastNotification {
- kind,
- title: format!("{} {}", agent_label(agent), event_text),
- context: format!("{} · {}", workspace_name, ws_idx + 1),
- });
- }
- }
-
- pane.detected_agent = agent;
- pane.state = state;
- break;
- }
- }
+ } => self
+ .update_pane_state(pane_id, |pane| pane.set_detected_state(agent, state))
+ .into_iter()
+ .collect(),
+ AppEvent::HookStateReported {
+ pane_id,
+ source,
+ agent,
+ state,
+ message,
+ } => self
+ .update_pane_state(pane_id, |pane| {
+ pane.set_hook_authority(source, agent, state, message)
+ })
+ .into_iter()
+ .collect(),
+ AppEvent::HookAuthorityCleared { pane_id, source } => self
+ .update_pane_state(pane_id, |pane| pane.clear_hook_authority(source.as_deref()))
+ .into_iter()
+ .collect(),
+ AppEvent::HookAgentReleased {
+ pane_id,
+ source,
+ agent,
+ } => self
+ .update_pane_state(pane_id, |pane| pane.release_agent(&source, agent))
+ .into_iter()
+ .collect(),
+ }
+ }
+
+ fn update_pane_state<F>(&mut self, pane_id: PaneId, update: F) -> Option<PaneStateUpdate>
+ where
+ F: FnOnce(&mut crate::pane::PaneState) -> Option<EffectiveStateChange>,
+ {
+ let ws_idx = self
+ .workspaces
+ .iter()
+ .position(|ws| ws.pane_state(pane_id).is_some())?;
+ let workspace_name = self.workspaces[ws_idx].display_name();
+ let change = {
+ let pane = self.workspaces[ws_idx]
+ .tabs
+ .iter_mut()
+ .find_map(|tab| tab.panes.get_mut(&pane_id))?;
+ update(pane)?
+ };
+ self.apply_pane_state_change(ws_idx, pane_id, &workspace_name, change);
+ Some(PaneStateUpdate {
+ pane_id,
+ ws_idx,
+ previous_agent: change.previous_agent,
+ previous_state: change.previous_state,
+ agent: change.agent,
+ state: change.state,
+ })
+ }
+
+ fn apply_pane_state_change(
+ &mut self,
+ ws_idx: usize,
+ pane_id: PaneId,
+ workspace_name: &str,
+ change: EffectiveStateChange,
+ ) {
+ let is_active_ws = self.active == Some(ws_idx);
+ let Some(pane) = self.workspaces[ws_idx]
+ .tabs
+ .iter_mut()
+ .find_map(|tab| tab.panes.get_mut(&pane_id))
+ else {
+ return;
+ };
+
+ if change.state == AgentState::Idle
+ && change.previous_state != AgentState::Idle
+ && !is_active_ws
+ {
+ pane.seen = false;
+ }
+
+ if self.sound.allows(change.agent) {
+ if let Some(sound) = notification_sound_for_state_change(
+ is_active_ws,
+ change.previous_state,
+ change.state,
+ ) {
+ crate::sound::play(sound);
+ }
+ }
+
+ if self.toast_config.enabled {
+ if let (Some(agent), Some(kind)) = (
+ change.agent,
+ notification_toast_for_state_change(
+ is_active_ws,
+ change.previous_state,
+ change.state,
+ ),
+ ) {
+ let event_text = match kind {
+ ToastKind::NeedsAttention => "needs attention",
+ ToastKind::Finished => "finished",
+ ToastKind::UpdateInstalled => "updated",
+ };
+ self.toast = Some(ToastNotification {
+ kind,
+ title: format!("{} {}", agent_label(agent), event_text),
+ context: format!("{} · {}", workspace_name, ws_idx + 1),
+ });
}
}
}