aboutsummaryrefslogtreecommitdiff
path: root/src/app
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-04-16 01:09:25 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-04-16 01:09:25 +0300
commitef1c6b1497fd0ad26e9d32f301ffb801cb96aa40 (patch)
treef80cbb5fc67eb2796c932a9fdd5315ce3d21d18e /src/app
parentd76a5b8cec72b420b340a13ea186ea169b39b69e (diff)
fix: switch updates to manual preview flow
Diffstat (limited to 'src/app')
-rw-r--r--src/app/actions.rs22
-rw-r--r--src/app/input.rs33
-rw-r--r--src/app/mod.rs26
3 files changed, 65 insertions, 16 deletions
diff --git a/src/app/actions.rs b/src/app/actions.rs
index fcdeff92..ffb41ba1 100644
--- a/src/app/actions.rs
+++ b/src/app/actions.rs
@@ -481,8 +481,8 @@ impl AppState {
self.update_dismissed = true;
self.toast = Some(ToastNotification {
kind: ToastKind::UpdateInstalled,
- title: format!("updated to v{version}"),
- context: "restart to use it".to_string(),
+ title: format!("v{version} available"),
+ context: "run `herdr update`".to_string(),
});
Vec::new()
}
@@ -1009,6 +1009,24 @@ mod tests {
}
#[test]
+ fn update_ready_sets_manual_update_toast() {
+ let mut state = AppState::test_new();
+
+ let updates = state.handle_app_event(AppEvent::UpdateReady {
+ version: "0.5.0".into(),
+ });
+
+ assert!(updates.is_empty());
+ assert_eq!(state.update_available.as_deref(), Some("0.5.0"));
+ assert!(state.latest_release_notes_available);
+ assert!(state.update_dismissed);
+ let toast = state.toast.as_ref().expect("update toast");
+ assert_eq!(toast.kind, ToastKind::UpdateInstalled);
+ assert_eq!(toast.title, "v0.5.0 available");
+ assert_eq!(toast.context, "run `herdr update`");
+ }
+
+ #[test]
fn toggle_fullscreen_works() {
let mut state = app_with_workspaces(&["test"]);
state.workspaces[0].test_split(Direction::Horizontal);
diff --git a/src/app/input.rs b/src/app/input.rs
index 347d8e41..dbc2547d 100644
--- a/src/app/input.rs
+++ b/src/app/input.rs
@@ -1446,7 +1446,10 @@ impl AppState {
}
fn release_notes_modal_inner(&self) -> Option<ratatui::layout::Rect> {
- self.onboarding_modal_inner(76, 20)
+ self.onboarding_modal_inner(
+ crate::ui::RELEASE_NOTES_MODAL_SIZE.0,
+ crate::ui::RELEASE_NOTES_MODAL_SIZE.1,
+ )
}
fn release_notes_close_button_at(&self, col: u16, row: u16) -> bool {
@@ -1473,7 +1476,12 @@ impl AppState {
if inner.height < 8 || inner.width < 4 {
return None;
}
- Some(crate::ui::modal_stack_areas(inner, 2, 1, 0, 1).content)
+ let body = crate::ui::modal_stack_areas(inner, 2, 1, 0, 1).content;
+ let preview = self
+ .release_notes
+ .as_ref()
+ .is_some_and(|notes| notes.preview);
+ Some(crate::ui::release_notes_sections(body, preview).notes_body)
}
fn release_notes_scroll_metrics(&self) -> Option<crate::pane::ScrollMetrics> {
@@ -1774,14 +1782,12 @@ impl AppState {
pub(crate) fn global_menu_labels(&self) -> Vec<&'static str> {
let mut labels = vec!["settings", "keybinds", "reload keybinds"];
- if self.latest_release_notes_available || self.update_available.is_some() {
+ if self.update_available.is_some() {
+ labels.push("update available");
+ } else if self.latest_release_notes_available {
labels.push("what's new");
}
- labels.push(if self.update_available.is_some() {
- "quit to apply update"
- } else {
- "quit"
- });
+ labels.push("quit");
labels
}
@@ -1791,7 +1797,10 @@ impl AppState {
let labels = self.global_menu_labels();
let content_width = labels
.iter()
- .map(|label| label.chars().count() as u16)
+ .map(|label| {
+ let extra = if *label == "update available" { 2 } else { 0 };
+ label.chars().count() as u16 + extra
+ })
.max()
.unwrap_or(8)
.saturating_add(2);
@@ -4269,7 +4278,7 @@ mod tests {
}
#[test]
- fn update_pending_menu_surfaces_quit_to_apply_update_action() {
+ fn update_available_menu_surfaces_update_status_without_apply_action() {
let mut app = app_for_mouse_test();
app.state.update_available = Some("0.3.2".into());
app.state.latest_release_notes_available = true;
@@ -4287,8 +4296,8 @@ mod tests {
"settings",
"keybinds",
"reload keybinds",
- "what's new",
- "quit to apply update"
+ "update available",
+ "quit"
]
);
diff --git a/src/app/mod.rs b/src/app/mod.rs
index 165a6ff3..36b00033 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -205,7 +205,12 @@ impl App {
"using pane scrollback configuration"
);
- let latest_release_notes_available = crate::release_notes::load_latest().is_some();
+ let latest_release_notes = crate::release_notes::load_latest();
+ let update_available = latest_release_notes
+ .as_ref()
+ .filter(|notes| notes.preview)
+ .map(|notes| notes.version.clone());
+ let latest_release_notes_available = latest_release_notes.is_some();
let mode = if config.should_show_onboarding() {
state::Mode::Onboarding
@@ -261,7 +266,7 @@ impl App {
tab_press: None,
selection: None,
context_menu: None,
- update_available: None,
+ update_available,
latest_release_notes_available,
update_dismissed: false,
config_diagnostic,
@@ -1362,6 +1367,23 @@ mod tests {
}
#[test]
+ fn startup_restores_preview_update_available_from_saved_notes() {
+ let _guard = config_env_lock().lock().unwrap();
+ let path = temp_config_path("startup-preview-update-available");
+ std::env::set_var(crate::config::CONFIG_PATH_ENV_VAR, &path);
+
+ crate::release_notes::save_pending("0.5.0", "### Changed\n- One").unwrap();
+
+ let app = test_app();
+
+ assert_eq!(app.state.update_available.as_deref(), Some("0.5.0"));
+ assert!(app.state.latest_release_notes_available);
+
+ std::env::remove_var(crate::config::CONFIG_PATH_ENV_VAR);
+ let _ = std::fs::remove_dir_all(path.parent().unwrap());
+ }
+
+ #[test]
fn reload_keybinds_updates_live_state() {
let _guard = config_env_lock().lock().unwrap();
let path = temp_config_path("reload-keybinds-success");