diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-02 01:23:10 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-02 01:23:10 +0300 |
| commit | 45005f1f733a96e3d9e6748577c01ca2c594304b (patch) | |
| tree | 58710dd980f8fa42d8df6f0c04290dc5e23c948d /src | |
| parent | 9ae93b125249dead6169bdc505c11e0f43309e40 (diff) | |
fix: mark visible done panes seen on terminal focus
Diffstat (limited to 'src')
| -rw-r--r-- | src/app/actions.rs | 51 | ||||
| -rw-r--r-- | src/app/mod.rs | 41 | ||||
| -rw-r--r-- | src/app/runtime.rs | 2 | ||||
| -rw-r--r-- | src/server/headless.rs | 3 |
4 files changed, 92 insertions, 5 deletions
diff --git a/src/app/actions.rs b/src/app/actions.rs index 83e9bec6..0b187fed 100644 --- a/src/app/actions.rs +++ b/src/app/actions.rs @@ -179,6 +179,28 @@ impl AppState { } } + pub(crate) fn mark_active_tab_seen(&mut self) -> bool { + let Some(ws_idx) = self.active else { + return false; + }; + let Some(tab) = self + .workspaces + .get_mut(ws_idx) + .and_then(crate::workspace::Workspace::active_tab_mut) + else { + return false; + }; + + let mut changed = false; + for pane in tab.panes.values_mut() { + if !pane.seen { + pane.seen = true; + changed = true; + } + } + changed + } + pub fn next_workspace(&mut self) { if !self.workspaces.is_empty() { let current = self.active.unwrap_or(self.selected); @@ -591,10 +613,10 @@ impl AppState { return; }; - if is_background_completion_transition(change.previous_state, change.state) - && !suppress_active_tab_notifications - { - pane.seen = false; + if change.state != AgentState::Idle { + pane.seen = true; + } else if is_background_completion_transition(change.previous_state, change.state) { + pane.seen = suppress_active_tab_notifications; } if self.local_sound_playback && self.sound.allows(change.known_agent) { @@ -914,6 +936,27 @@ mod tests { } #[test] + fn active_tab_completion_marks_pane_seen() { + let mut state = app_with_workspaces(&["active"]); + state.active = Some(0); + state.outer_terminal_focus = Some(true); + let pane_id = *state.workspaces[0].panes.keys().next().unwrap(); + let pane = state.workspaces[0].panes.get_mut(&pane_id).unwrap(); + pane.state = AgentState::Working; + pane.seen = false; + + state.handle_app_event(AppEvent::StateChanged { + pane_id, + agent: Some(Agent::Pi), + state: AgentState::Idle, + }); + + let pane = state.workspaces[0].panes.get(&pane_id).unwrap(); + assert_eq!(pane.state, AgentState::Idle); + assert!(pane.seen); + } + + #[test] fn initial_idle_in_background_stays_seen() { let mut state = app_with_workspaces(&["active", "background"]); state.active = Some(0); diff --git a/src/app/mod.rs b/src/app/mod.rs index 9e437e62..1a287a38 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1153,6 +1153,47 @@ mod tests { } #[tokio::test] + async fn outer_focus_gained_marks_visible_done_panes_seen() { + let mut app = test_app(); + let mut workspace = Workspace::test_new("test"); + let root_pane = workspace.tabs[0].root_pane; + let split_pane = workspace.test_split(ratatui::layout::Direction::Horizontal); + let background_tab = workspace.test_add_tab(Some("background")); + let background_pane = workspace.tabs[background_tab].root_pane; + + workspace.tabs[0].panes.get_mut(&root_pane).unwrap().state = AgentState::Idle; + workspace.tabs[0].panes.get_mut(&root_pane).unwrap().seen = false; + workspace.tabs[0].panes.get_mut(&split_pane).unwrap().state = AgentState::Idle; + workspace.tabs[0].panes.get_mut(&split_pane).unwrap().seen = false; + workspace.tabs[background_tab] + .panes + .get_mut(&background_pane) + .unwrap() + .state = AgentState::Idle; + workspace.tabs[background_tab] + .panes + .get_mut(&background_pane) + .unwrap() + .seen = false; + + app.state.workspaces = vec![workspace]; + app.state.active = Some(0); + app.state.selected = 0; + app.state.mode = Mode::Terminal; + app.state.outer_terminal_focus = Some(false); + + let handled = app + .handle_raw_input_event(crate::raw_input::RawInputEvent::OuterFocusGained) + .await; + + assert!(handled); + assert_eq!(app.state.outer_terminal_focus, Some(true)); + assert!(app.state.workspaces[0].tabs[0].panes[&root_pane].seen); + assert!(app.state.workspaces[0].tabs[0].panes[&split_pane].seen); + assert!(!app.state.workspaces[0].tabs[background_tab].panes[&background_pane].seen); + } + + #[tokio::test] async fn repeat_key_events_are_ignored_outside_terminal_mode() { let mut app = test_app(); app.state.mode = Mode::ReleaseNotes; diff --git a/src/app/runtime.rs b/src/app/runtime.rs index 094b26f1..27c061bd 100644 --- a/src/app/runtime.rs +++ b/src/app/runtime.rs @@ -91,7 +91,7 @@ impl App { } crate::raw_input::RawInputEvent::OuterFocusGained => { self.state.outer_terminal_focus = Some(true); - false + self.state.mark_active_tab_seen() } crate::raw_input::RawInputEvent::OuterFocusLost => { self.state.outer_terminal_focus = Some(false); diff --git a/src/server/headless.rs b/src/server/headless.rs index b4e21320..d3e81a10 100644 --- a/src/server/headless.rs +++ b/src/server/headless.rs @@ -636,6 +636,9 @@ impl HeadlessServer { self.effective_size = client.terminal_size; self.app.state.outer_terminal_focus = client.outer_terminal_focus; + if client.outer_terminal_focus == Some(true) { + self.app.state.mark_active_tab_seen(); + } if !client.host_terminal_theme.is_empty() { self.app.set_host_terminal_theme(client.host_terminal_theme); } |
