auto_update_ui: Add error fallback for viewing release notes locally (#45900)

CodingDoll and Smit Barmase created

Closes https://github.com/zed-industries/zed/issues/42140

Changes:

- Add show_view_release_notes_locally_error to prompt users when the
operation fails.

<img width="1536" height="864" alt="image"
src="https://github.com/user-attachments/assets/265bbf7e-6931-4559-99b2-7c1d8fbcd6cc"
/>

Release Notes:

- Added a notification with a link to view release notes online when Zed
can’t load them locally.

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>

Change summary

Cargo.lock                                  |  1 
crates/auto_update/src/auto_update.rs       | 26 ++++++-----
crates/auto_update_ui/Cargo.toml            |  1 
crates/auto_update_ui/src/auto_update_ui.rs | 48 ++++++++++++++++++----
4 files changed, 52 insertions(+), 24 deletions(-)

Detailed changes

Cargo.lock πŸ”—

@@ -1400,7 +1400,6 @@ dependencies = [
  "client",
  "editor",
  "gpui",
- "http_client",
  "markdown_preview",
  "release_channel",
  "semver",

crates/auto_update/src/auto_update.rs πŸ”—

@@ -250,26 +250,28 @@ pub fn check(_: &Check, window: &mut Window, cx: &mut App) {
     }
 }
 
-pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut App) -> Option<()> {
-    let auto_updater = AutoUpdater::get(cx)?;
+pub fn release_notes_url(cx: &mut App) -> Option<String> {
     let release_channel = ReleaseChannel::try_global(cx)?;
-
-    match release_channel {
+    let url = match release_channel {
         ReleaseChannel::Stable | ReleaseChannel::Preview => {
+            let auto_updater = AutoUpdater::get(cx)?;
             let auto_updater = auto_updater.read(cx);
-            let current_version = auto_updater.current_version.clone();
+            let current_version = &auto_updater.current_version;
             let release_channel = release_channel.dev_name();
             let path = format!("/releases/{release_channel}/{current_version}");
-            let url = &auto_updater.client.http_client().build_url(&path);
-            cx.open_url(url);
+            auto_updater.client.http_client().build_url(&path)
         }
         ReleaseChannel::Nightly => {
-            cx.open_url("https://github.com/zed-industries/zed/commits/nightly/");
+            "https://github.com/zed-industries/zed/commits/nightly/".to_string()
         }
-        ReleaseChannel::Dev => {
-            cx.open_url("https://github.com/zed-industries/zed/commits/main/");
-        }
-    }
+        ReleaseChannel::Dev => "https://github.com/zed-industries/zed/commits/main/".to_string(),
+    };
+    Some(url)
+}
+
+pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut App) -> Option<()> {
+    let url = release_notes_url(cx)?;
+    cx.open_url(&url);
     None
 }
 

crates/auto_update_ui/Cargo.toml πŸ”—

@@ -17,7 +17,6 @@ auto_update.workspace = true
 client.workspace = true
 editor.workspace = true
 gpui.workspace = true
-http_client.workspace = true
 markdown_preview.workspace = true
 release_channel.workspace = true
 semver.workspace = true

crates/auto_update_ui/src/auto_update_ui.rs πŸ”—

@@ -1,13 +1,13 @@
-use auto_update::AutoUpdater;
+use auto_update::{AutoUpdater, release_notes_url};
 use editor::{Editor, MultiBuffer};
 use gpui::{App, Context, DismissEvent, Entity, Window, actions, prelude::*};
-use http_client::HttpClient;
 use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
 use release_channel::{AppVersion, ReleaseChannel};
 use serde::Deserialize;
 use smol::io::AsyncReadExt;
 use util::ResultExt as _;
 use workspace::Workspace;
+use workspace::notifications::ErrorMessagePrompt;
 use workspace::notifications::simple_message_notification::MessageNotification;
 use workspace::notifications::{NotificationId, show_app_notification};
 
@@ -39,6 +39,28 @@ struct ReleaseNotesBody {
     release_notes: String,
 }
 
+fn notify_release_notes_failed_to_show_locally(
+    workspace: &mut Workspace,
+    _window: &mut Window,
+    cx: &mut Context<Workspace>,
+) {
+    struct ViewReleaseNotesLocallyError;
+    workspace.show_notification(
+        NotificationId::unique::<ViewReleaseNotesLocallyError>(),
+        cx,
+        |cx| {
+            cx.new(move |cx| {
+                let url = release_notes_url(cx);
+                let mut prompt = ErrorMessagePrompt::new("Couldn't load release notes", cx);
+                if let Some(url) = url {
+                    prompt = prompt.with_link_button("View in Browser".to_string(), url);
+                }
+                prompt
+            })
+        },
+    );
+}
+
 fn view_release_notes_locally(
     workspace: &mut Workspace,
     window: &mut Window,
@@ -46,14 +68,13 @@ fn view_release_notes_locally(
 ) {
     let release_channel = ReleaseChannel::global(cx);
 
-    let url = match release_channel {
-        ReleaseChannel::Nightly => Some("https://github.com/zed-industries/zed/commits/nightly/"),
-        ReleaseChannel::Dev => Some("https://github.com/zed-industries/zed/commits/main/"),
-        _ => None,
-    };
-
-    if let Some(url) = url {
-        cx.open_url(url);
+    if matches!(
+        release_channel,
+        ReleaseChannel::Nightly | ReleaseChannel::Dev
+    ) {
+        if let Some(url) = release_notes_url(cx) {
+            cx.open_url(&url);
+        }
         return;
     }
 
@@ -77,6 +98,9 @@ fn view_release_notes_locally(
                 let markdown = markdown.await.log_err();
                 let response = client.get(&url, Default::default(), true).await;
                 let Some(mut response) = response.log_err() else {
+                    workspace
+                        .update_in(cx, notify_release_notes_failed_to_show_locally)
+                        .log_err();
                     return;
                 };
 
@@ -123,6 +147,10 @@ fn view_release_notes_locally(
                             cx.notify();
                         })
                         .log_err();
+                } else {
+                    workspace
+                        .update_in(cx, notify_release_notes_failed_to_show_locally)
+                        .log_err();
                 }
             })
             .detach();