aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-06-18 20:06:43 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-06-18 20:06:43 +0300
commit9f4f1630893f74697a2097ef11bf5bf512a37850 (patch)
tree5648332c37526b2868db205b661f89ba6c125f0d /src
parentd74ba8ca9e49d9d94a4e59a0ee0bdbd1128af76e (diff)
feat: add background update check toggles
refs #677
Diffstat (limited to 'src')
-rw-r--r--src/app/mod.rs65
-rw-r--r--src/app/runtime.rs6
-rw-r--r--src/config/model.rs13
-rw-r--r--src/main.rs8
4 files changed, 80 insertions, 12 deletions
diff --git a/src/app/mod.rs b/src/app/mod.rs
index dbc2d416..a24267e5 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -115,6 +115,8 @@ pub struct App {
pub(crate) next_animation_tick: Option<Instant>,
pub(crate) next_auto_update_check: Option<Instant>,
pub(crate) next_agent_manifest_update_check: Option<Instant>,
+ pub(crate) update_version_check_enabled: bool,
+ pub(crate) update_manifest_check_enabled: bool,
pub(crate) agent_metadata_deadline: Option<Instant>,
pub(crate) pending_agent_resume_deadline: Option<Instant>,
pub(crate) selection_autoscroll_deadline: Option<Instant>,
@@ -190,6 +192,10 @@ fn auto_updates_enabled(no_session: bool) -> bool {
!no_session && !cfg!(debug_assertions)
}
+fn background_update_check_enabled(no_session: bool, check_enabled: bool) -> bool {
+ auto_updates_enabled(no_session) && check_enabled
+}
+
fn load_plugin_registry(no_session: bool) -> crate::app::state::InstalledPluginRegistry {
if no_session {
return std::collections::HashMap::new();
@@ -646,9 +652,15 @@ impl App {
// Background auto-update is disabled in monolithic no-session mode
// and in debug/test builds so local development never mutates the
// running binary out from under spawned test processes.
- if auto_updates_enabled(no_session) {
+ let version_check_enabled =
+ background_update_check_enabled(no_session, config.update.version_check);
+ let manifest_check_enabled =
+ background_update_check_enabled(no_session, config.update.manifest_check);
+ if version_check_enabled {
let update_tx = event_tx.clone();
std::thread::spawn(move || crate::update::auto_update(update_tx));
+ }
+ if manifest_check_enabled {
let manifest_update_tx = event_tx.clone();
std::thread::spawn(move || {
crate::detect::manifest_update::auto_update(manifest_update_tx)
@@ -679,10 +691,12 @@ impl App {
last_pane_click: None,
next_resize_poll: Instant::now() + RESIZE_POLL_INTERVAL,
next_animation_tick: None,
- next_auto_update_check: auto_updates_enabled(no_session)
+ next_auto_update_check: version_check_enabled
.then_some(Instant::now() + AUTO_UPDATE_CHECK_INTERVAL),
- next_agent_manifest_update_check: auto_updates_enabled(no_session)
+ next_agent_manifest_update_check: manifest_check_enabled
.then_some(Instant::now() + AUTO_UPDATE_CHECK_INTERVAL),
+ update_version_check_enabled: config.update.version_check,
+ update_manifest_check_enabled: config.update.manifest_check,
agent_metadata_deadline: None,
pending_agent_resume_deadline: None,
session_save_deadline: None,
@@ -733,13 +747,15 @@ impl App {
let pane_id_aliases = crate::persist::handoff_pane_aliases(snapshot, &workspaces);
app.no_session = false;
- if auto_updates_enabled(app.no_session) {
- let now = Instant::now();
+ let now = Instant::now();
+ if background_update_check_enabled(app.no_session, app.update_version_check_enabled) {
app.next_auto_update_check = app
.state
.update_available
.is_none()
.then_some(now + AUTO_UPDATE_CHECK_INTERVAL);
+ }
+ if background_update_check_enabled(app.no_session, app.update_manifest_check_enabled) {
app.next_agent_manifest_update_check = Some(now + AUTO_UPDATE_CHECK_INTERVAL);
}
app.state.detach_exits = false;
@@ -1344,6 +1360,37 @@ impl App {
self.state.pane_scrollback_limit_bytes = config.advanced.scrollback_limit_bytes;
}
+ if !invalid_section("update") {
+ let now = Instant::now();
+ let previous_version_check_enabled = self.update_version_check_enabled;
+ let previous_manifest_check_enabled = self.update_manifest_check_enabled;
+ self.update_version_check_enabled = config.update.version_check;
+ self.update_manifest_check_enabled = config.update.manifest_check;
+
+ if !self.update_version_check_enabled {
+ self.next_auto_update_check = None;
+ } else if !previous_version_check_enabled
+ && background_update_check_enabled(
+ self.no_session,
+ self.update_version_check_enabled,
+ )
+ && self.state.update_available.is_none()
+ {
+ self.next_auto_update_check = Some(now);
+ }
+
+ if !self.update_manifest_check_enabled {
+ self.next_agent_manifest_update_check = None;
+ } else if !previous_manifest_check_enabled
+ && background_update_check_enabled(
+ self.no_session,
+ self.update_manifest_check_enabled,
+ )
+ {
+ self.next_agent_manifest_update_check = Some(now);
+ }
+ }
+
if !invalid_section("terminal") {
self.state.default_shell = config.terminal.default_shell.clone();
self.state.shell_mode = config.terminal.shell_mode;
@@ -2332,12 +2379,14 @@ mod tests {
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(
&path,
- "[terminal]\ndefault_shell = \"nu\"\nshell_mode = \"non_login\"\nnew_cwd = \"home\"\n[keys]\nnew_workspace = \"prefix+m\"\nprefix = \"ctrl+a\"\n[ui]\nagent_panel_scope = \"current\"\nagent_panel_sort = \"priority\"\nredraw_on_focus_gained = false\nright_click_passthrough_modifier = \"ctrl\"\n[ui.toast]\ndelivery = \"herdr\"\n[experimental]\nswitch_ascii_input_source_in_prefix = true\n",
+ "[terminal]\ndefault_shell = \"nu\"\nshell_mode = \"non_login\"\nnew_cwd = \"home\"\n[keys]\nnew_workspace = \"prefix+m\"\nprefix = \"ctrl+a\"\n[update]\nversion_check = false\nmanifest_check = false\n[ui]\nagent_panel_scope = \"current\"\nagent_panel_sort = \"priority\"\nredraw_on_focus_gained = false\nright_click_passthrough_modifier = \"ctrl\"\n[ui.toast]\ndelivery = \"herdr\"\n[experimental]\nswitch_ascii_input_source_in_prefix = true\n",
)
.unwrap();
std::env::set_var(crate::config::CONFIG_PATH_ENV_VAR, &path);
let mut app = test_app();
+ app.next_auto_update_check = Some(Instant::now());
+ app.next_agent_manifest_update_check = Some(Instant::now());
let report = app.reload_config();
assert_eq!(report.status, crate::config::ConfigReloadStatus::Applied);
@@ -2368,6 +2417,10 @@ mod tests {
app.state.new_terminal_cwd,
crate::config::NewTerminalCwdConfig::Home
);
+ assert!(!app.update_version_check_enabled);
+ assert!(!app.update_manifest_check_enabled);
+ assert!(app.next_auto_update_check.is_none());
+ assert!(app.next_agent_manifest_update_check.is_none());
assert!(app.state.switch_ascii_input_source_in_prefix);
assert!(app.state.config_diagnostic.is_none());
let toast = app.state.toast.as_ref().unwrap();
diff --git a/src/app/runtime.rs b/src/app/runtime.rs
index 097f1ace..6ef9a786 100644
--- a/src/app/runtime.rs
+++ b/src/app/runtime.rs
@@ -3,7 +3,7 @@ use std::time::{Duration, Instant};
use crossterm::terminal;
use super::{
- auto_updates_enabled, repeat_key_identity, App, Mode, ANIMATION_INTERVAL,
+ background_update_check_enabled, repeat_key_identity, App, Mode, ANIMATION_INTERVAL,
AUTO_UPDATE_CHECK_INTERVAL, GIT_REMOTE_STATUS_REFRESH_INTERVAL, MIN_RENDER_INTERVAL,
RESIZE_POLL_INTERVAL, SELECTION_AUTOSCROLL_INTERVAL,
};
@@ -432,7 +432,7 @@ impl App {
}
pub(crate) fn run_auto_update_check(&mut self) {
- if !auto_updates_enabled(self.no_session) {
+ if !background_update_check_enabled(self.no_session, self.update_version_check_enabled) {
self.next_auto_update_check = None;
return;
}
@@ -452,7 +452,7 @@ impl App {
}
pub(crate) fn run_agent_manifest_update_check(&mut self) {
- if !auto_updates_enabled(self.no_session) {
+ if !background_update_check_enabled(self.no_session, self.update_manifest_check_enabled) {
self.next_agent_manifest_update_check = None;
return;
}
diff --git a/src/config/model.rs b/src/config/model.rs
index 7b1e7bb7..d07a425f 100644
--- a/src/config/model.rs
+++ b/src/config/model.rs
@@ -30,14 +30,17 @@ impl UpdateChannelConfig {
#[derive(Debug, Clone, Copy, Deserialize)]
#[serde(default)]
pub struct UpdateConfig {
- #[serde(default = "default_update_channel")]
pub channel: UpdateChannelConfig,
+ pub version_check: bool,
+ pub manifest_check: bool,
}
impl Default for UpdateConfig {
fn default() -> Self {
Self {
channel: default_update_channel(),
+ version_check: true,
+ manifest_check: true,
}
}
}
@@ -728,17 +731,23 @@ mod tests {
use super::*;
#[test]
- fn update_channel_defaults_for_platform_and_parses() {
+ fn update_config_defaults_and_parses() {
let default_config = Config::default();
assert_eq!(default_config.update.channel, default_update_channel());
+ assert!(default_config.update.version_check);
+ assert!(default_config.update.manifest_check);
let toml = r#"
[update]
channel = "preview"
+version_check = false
+manifest_check = false
"#;
let config: Config = toml::from_str(toml).unwrap();
assert_eq!(config.update.channel, UpdateChannelConfig::Preview);
assert_eq!(config.update.channel.as_str(), "preview");
+ assert!(!config.update.version_check);
+ assert!(!config.update.manifest_check);
}
#[test]
diff --git a/src/main.rs b/src/main.rs
index 3c66ff1d..193e24d8 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -143,10 +143,16 @@ const DEFAULT_CONFIG: &str = r##"# herdr configuration
# new_cwd = "follow"
[update]
-# Update channel used by background checks and `herdr update`.
+# Update channel used by background version checks and `herdr update`.
# Use "stable" for normal releases or "preview" for opt-in preview builds.
# channel = "stable"
+# Check herdr.dev for new Herdr versions in the background.
+# version_check = true
+
+# Check herdr.dev for remote agent-detection manifest updates in the background.
+# manifest_check = true
+
[keys]
# Prefix key to enter prefix mode (default: "ctrl+b")
# Examples: "ctrl+b", "f12", "esc", "-"