aboutsummaryrefslogtreecommitdiff
path: root/src/app
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-20 22:08:04 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-20 22:08:04 +0300
commitc584037c77c63f6a08f8837f357c89432b8043e7 (patch)
tree16647d6628a11dbbf5e576fb86f2a87b507e202e /src/app
parentf7b26a911772dab16a49d982002d2bbe58e0956b (diff)
feat: badge outdated integration updates
Diffstat (limited to 'src/app')
-rw-r--r--src/app/input/settings.rs73
-rw-r--r--src/app/input/sidebar.rs10
-rw-r--r--src/app/state.rs19
3 files changed, 98 insertions, 4 deletions
diff --git a/src/app/input/settings.rs b/src/app/input/settings.rs
index 400b2796..6caccb0e 100644
--- a/src/app/input/settings.rs
+++ b/src/app/input/settings.rs
@@ -283,7 +283,12 @@ impl AppState {
}
let mut x = inner.x;
for section in SettingsSection::ALL {
- let width = section.label().len() as u16 + 2;
+ let badge_width = if self.settings_section_has_badge(*section) {
+ 2
+ } else {
+ 0
+ };
+ let width = section.label().len() as u16 + 2 + badge_width;
if col >= x && col < x + width {
return Some(*section);
}
@@ -493,4 +498,70 @@ mod tests {
assert_eq!(app.state.settings.list.selected, 0);
}
+
+ #[test]
+ fn integration_update_badge_only_tracks_outdated_recommendations() {
+ let mut state = state_with_workspaces(&["test"]);
+ state.integration_recommendations = vec![integration_recommendation(
+ crate::integration::IntegrationStatusKind::NotInstalled,
+ true,
+ )];
+ assert!(!state.integration_updates_available());
+
+ state.integration_recommendations = vec![integration_recommendation(
+ crate::integration::IntegrationStatusKind::NotInstalled,
+ false,
+ )];
+ assert!(!state.integration_updates_available());
+
+ state.integration_recommendations = vec![integration_recommendation(
+ crate::integration::IntegrationStatusKind::Current,
+ true,
+ )];
+ assert!(!state.integration_updates_available());
+
+ state.integration_recommendations = vec![integration_recommendation(
+ crate::integration::IntegrationStatusKind::Outdated,
+ true,
+ )];
+ assert!(state.integration_updates_available());
+ }
+
+ #[test]
+ fn settings_tab_hit_area_includes_integration_update_badge() {
+ let mut state = state_with_workspaces(&["test"]);
+ state.integration_recommendations = vec![integration_recommendation(
+ crate::integration::IntegrationStatusKind::Outdated,
+ true,
+ )];
+ open_settings(&mut state);
+
+ let inner = state.settings_inner_rect();
+ let tab_y = inner.y + 1;
+ let integrations_x = inner.x
+ + SettingsSection::ALL[..SettingsSection::ALL.len() - 1]
+ .iter()
+ .map(|section| section.label().len() as u16 + 3)
+ .sum::<u16>();
+ let dotted_width = SettingsSection::Integrations.label().len() as u16 + 4;
+
+ assert_eq!(
+ state.settings_tab_at(integrations_x + dotted_width - 1, tab_y),
+ Some(SettingsSection::Integrations)
+ );
+ }
+
+ fn integration_recommendation(
+ state: crate::integration::IntegrationStatusKind,
+ available: bool,
+ ) -> crate::integration::IntegrationRecommendation {
+ crate::integration::IntegrationRecommendation {
+ target: crate::api::schema::IntegrationTarget::Claude,
+ label: "claude",
+ command: "claude",
+ available,
+ path: std::path::PathBuf::from("/tmp/herdr-test-integration"),
+ state,
+ }
+ }
}
diff --git a/src/app/input/sidebar.rs b/src/app/input/sidebar.rs
index 94feaf93..7491c9c1 100644
--- a/src/app/input/sidebar.rs
+++ b/src/app/input/sidebar.rs
@@ -175,7 +175,7 @@ impl AppState {
}
let footer = self.sidebar_footer_rect();
- let width = if self.update_available.is_some() {
+ let width = if self.global_menu_attention_badge_visible() {
8
} else {
6
@@ -203,8 +203,12 @@ impl AppState {
let content_width = labels
.iter()
.map(|label| {
- let extra = if *label == "update ready" { 2 } else { 0 };
- label.chars().count() as u16 + extra
+ let badge_width = if self.global_menu_item_has_badge(label) {
+ 2
+ } else {
+ 0
+ };
+ label.chars().count() as u16 + badge_width
})
.max()
.unwrap_or(8)
diff --git a/src/app/state.rs b/src/app/state.rs
index 2b078caf..921a82c9 100644
--- a/src/app/state.rs
+++ b/src/app/state.rs
@@ -1011,6 +1011,25 @@ impl AppState {
self.show_agent_labels_on_pane_borders
}
+ pub(crate) fn integration_updates_available(&self) -> bool {
+ self.integration_recommendations
+ .iter()
+ .any(|item| item.state == crate::integration::IntegrationStatusKind::Outdated)
+ }
+
+ pub(crate) fn global_menu_attention_badge_visible(&self) -> bool {
+ self.update_available.is_some() || self.integration_updates_available()
+ }
+
+ pub(crate) fn global_menu_item_has_badge(&self, item: &str) -> bool {
+ (item == "update ready" && self.update_available.is_some())
+ || (item == "settings" && self.integration_updates_available())
+ }
+
+ pub(crate) fn settings_section_has_badge(&self, section: SettingsSection) -> bool {
+ section == SettingsSection::Integrations && self.integration_updates_available()
+ }
+
pub fn focused_pane_requests_mouse_capture(&self) -> bool {
self.mode == Mode::Terminal
&& self