From e57126dcd4c59cd33a77a521ba7614625bae246f Mon Sep 17 00:00:00 2001 From: Taha Date: Wed, 16 Sep 2026 21:41:45 +0000 Subject: [PATCH] auto_update: Add title bar feedback when Zed is already up to date (#64106) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective A manual "check for updates" that finds no newer version left the updater back at `Idle`, so the "Checking for Zed Updates…" indicator in the title bar simply vanished with no confirmation that the check had succeeded. ## Solution Track that transition in `UpdateVersion` and show an "Up to Date" button in its place for a few seconds. The timer is stored as a task on the view, so it is cancelled when the view is dropped or a new update state arrives. `restart_after_wake` passed through `Idle` before re-polling, which would read as a completed check; it now stays in `Checking`. ## Testing - Did you test these changes? If so, how? **Tested by running Zed in preview channel.** - Are there any parts that need more testing? **No** - How can other people (reviewers) test your changes? Is there anything specific they need to know? **Use `ZED_RELEASE_CHANNEL=preview cargo run` to open Zed. Then manually trigger a check within menu `Zed -> Check for Updates`.** - If relevant, what platforms did you test these changes on, and are there any important ones you can't test? **Tested only on MacOS Tahoe** ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [ ] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable ## Showcase A visual demo of what happens in title_bar when a manual check for updates is triggered. https://github.com/user-attachments/assets/23b72bf2-6188-4eae-82b9-1c5b70782bb4 --- Release Notes: - Added confirmation in the title bar when a manual update check finds Zed is already up to date --- crates/auto_update/src/auto_update.rs | 21 ++++++--- crates/title_bar/src/update_version.rs | 43 ++++++++++++++++++- .../ui/src/components/collab/update_button.rs | 5 +++ 3 files changed, 62 insertions(+), 7 deletions(-) diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 0d86651c00..38324d5348 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -3,8 +3,8 @@ use client::Client; use db::kvp::KeyValueStore; use futures_lite::StreamExt; use gpui::{ - App, AppContext as _, AsyncApp, BackgroundExecutor, Context, Entity, Global, Task, TaskExt, - Window, actions, + App, AppContext as _, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, Global, + Task, TaskExt, Window, actions, }; use http_client::{HttpClient, HttpClientWithUrl}; use paths::remote_servers_dir; @@ -173,6 +173,11 @@ impl AutoUpdateStatus { } } +pub enum AutoUpdateEvent { + /// A manual check received a release response and found no newer version. + UpToDate, +} + pub struct AutoUpdater { status: AutoUpdateStatus, current_version: Version, @@ -419,6 +424,8 @@ impl UpdateCheckType { } } +impl EventEmitter for AutoUpdater {} + impl AutoUpdater { pub fn get(cx: &mut App) -> Option> { cx.default_global::().0.clone() @@ -763,11 +770,15 @@ impl AutoUpdater { let Some(newer_version) = newer_version else { this.update(cx, |this, cx| { - let status = match previous_status { + this.status = match previous_status { AutoUpdateStatus::Updated { .. } => previous_status, - _ => AutoUpdateStatus::Idle, + _ => { + if this.update_check_type.is_manual() { + cx.emit(AutoUpdateEvent::UpToDate); + } + AutoUpdateStatus::Idle + } }; - this.status = status; cx.notify(); }); return Ok(()); diff --git a/crates/title_bar/src/update_version.rs b/crates/title_bar/src/update_version.rs index e90764ab81..b5164b23ea 100644 --- a/crates/title_bar/src/update_version.rs +++ b/crates/title_bar/src/update_version.rs @@ -1,15 +1,20 @@ use std::sync::Arc; +use std::time::Duration; use anyhow::anyhow; -use auto_update::{AutoUpdateStatus, AutoUpdater, UpdateCheckType}; -use gpui::{Empty, Render}; +use auto_update::{AutoUpdateEvent, AutoUpdateStatus, AutoUpdater, UpdateCheckType}; +use gpui::{Empty, Render, Task}; use semver::Version; use ui::{Tooltip, UpdateButton, prelude::*}; +const UP_TO_DATE_DISPLAY_DURATION: Duration = Duration::from_secs(4); + pub struct UpdateVersion { status: AutoUpdateStatus, update_check_type: UpdateCheckType, dismissed_status: Option, + showing_up_to_date: bool, + _up_to_date_timer: Option>, } impl UpdateVersion { @@ -20,23 +25,53 @@ impl UpdateVersion { this.status = auto_update.status(); this.update_check_type = auto_update.update_check_type(); this.dismissed_status = auto_update.dismissed_status(); + if !matches!(this.status, AutoUpdateStatus::Idle) { + this.hide_up_to_date(); + } cx.notify(); }) .detach(); + cx.subscribe(&auto_updater, |this, _, event, cx| match event { + AutoUpdateEvent::UpToDate => this.show_up_to_date(cx), + }) + .detach(); Self { status: auto_updater.read(cx).status(), update_check_type: UpdateCheckType::Automatic, dismissed_status: auto_updater.read(cx).dismissed_status(), + showing_up_to_date: false, + _up_to_date_timer: None, } } else { Self { status: AutoUpdateStatus::Idle, update_check_type: UpdateCheckType::Automatic, dismissed_status: None, + showing_up_to_date: false, + _up_to_date_timer: None, } } } + fn show_up_to_date(&mut self, cx: &mut Context) { + self.showing_up_to_date = true; + self._up_to_date_timer = Some(cx.spawn(async move |this, cx| { + cx.background_executor() + .timer(UP_TO_DATE_DISPLAY_DURATION) + .await; + this.update(cx, |this, cx| { + this.hide_up_to_date(); + cx.notify(); + }) + .ok(); + })); + } + + fn hide_up_to_date(&mut self) { + self.showing_up_to_date = false; + self._up_to_date_timer = None; + } + pub fn update_simulation(&mut self, cx: &mut Context) { let next_state = match self.status { AutoUpdateStatus::Idle => AutoUpdateStatus::Checking, @@ -59,6 +94,7 @@ impl UpdateVersion { self.status = next_state; self.update_check_type = UpdateCheckType::Manual; self.dismissed_status = None; + self.hide_up_to_date(); cx.notify() } @@ -91,6 +127,9 @@ impl Render for UpdateVersion { if self.is_dismissed() { return Empty.into_any_element(); } + if self.showing_up_to_date { + return UpdateButton::up_to_date().into_any_element(); + } match &self.status { AutoUpdateStatus::Checking if self.update_check_type.is_manual() => { UpdateButton::checking().into_any_element() diff --git a/crates/ui/src/components/collab/update_button.rs b/crates/ui/src/components/collab/update_button.rs index 0b94072e90..9c17f498f8 100644 --- a/crates/ui/src/components/collab/update_button.rs +++ b/crates/ui/src/components/collab/update_button.rs @@ -118,6 +118,10 @@ impl UpdateButton { .disabled(true) } + pub fn up_to_date() -> Self { + Self::new(IconName::Check, "Up to Date").disabled(true) + } + pub fn updated(version: impl Into) -> Self { Self::new(IconName::Download, "Restart to Update") .tooltip(version) @@ -254,6 +258,7 @@ impl Component for UpdateButton { "Installing", UpdateButton::installing(version).into_any_element(), ), + single_example("Up to Date", UpdateButton::up_to_date().into_any_element()), ], ), example_group_with_title(