From 2d72e7dfca47d727b9b83625d1e2fe9bd58f93bf Mon Sep 17 00:00:00 2001 From: captainsafia Date: Tue, 19 May 2026 00:12:44 +0000 Subject: [PATCH 1/3] fix: surface detailed GitHub auth errors Render the server-provided GitHub authentication error message in cloud-mode auth surfaces instead of always falling back to a generic missing-auth copy. Co-Authored-By: Oz --- app/src/ai/blocklist/block/status_bar.rs | 5 ++++- app/src/terminal/view/ambient_agent/loading_screen.rs | 7 +++++-- app/src/terminal/view/ambient_agent/model_tests.rs | 1 + app/src/terminal/view/ambient_agent/view_impl.rs | 1 + 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app/src/ai/blocklist/block/status_bar.rs b/app/src/ai/blocklist/block/status_bar.rs index af753be235..bd1c5369cd 100644 --- a/app/src/ai/blocklist/block/status_bar.rs +++ b/app/src/ai/blocklist/block/status_bar.rs @@ -929,13 +929,16 @@ impl BlocklistAIStatusBar { let error_color = theme.ansi_fg_red(); if let Some(auth_url) = ambient_agent_model.github_auth_url() { + let error_message = ambient_agent_model + .github_auth_error_message() + .unwrap_or("Missing GitHub authentication."); return Some(Message::new(vec![ MessageItem::Icon { icon: CoreIcon::Triangle, color: Some(error_color), }, MessageItem::Text { - content: "Missing GitHub authentication. ".into(), + content: format!("{error_message} ").into(), color: Some(error_color), }, MessageItem::hyperlink( diff --git a/app/src/terminal/view/ambient_agent/loading_screen.rs b/app/src/terminal/view/ambient_agent/loading_screen.rs index f4bb9c847d..fc198e62b4 100644 --- a/app/src/terminal/view/ambient_agent/loading_screen.rs +++ b/app/src/terminal/view/ambient_agent/loading_screen.rs @@ -301,6 +301,7 @@ pub fn render_cloud_mode_error_screen( /// Renders the cloud mode GitHub authentication required screen. pub fn render_cloud_mode_github_auth_required_screen( auth_url: &str, + error_message: Option<&str>, appearance: &Appearance, auth_button_mouse_state: &MouseStateHandle, _app: &AppContext, @@ -332,9 +333,11 @@ pub fn render_cloud_mode_github_auth_required_screen( .with_color(title_color) .finish(); - // Message text - "Please authenticate with GitHub to continue" + let error_message = error_message.unwrap_or("Please authenticate with GitHub to continue"); + + // Message text let message_text = Text::new( - "Please authenticate with GitHub to continue", + error_message.to_string(), appearance.ui_font_family(), appearance.monospace_font_size(), ) diff --git a/app/src/terminal/view/ambient_agent/model_tests.rs b/app/src/terminal/view/ambient_agent/model_tests.rs index 780abbaee9..4ae7babb6a 100644 --- a/app/src/terminal/view/ambient_agent/model_tests.rs +++ b/app/src/terminal/view/ambient_agent/model_tests.rs @@ -103,6 +103,7 @@ fn github_auth_url_for_initial_run_includes_focus_cloud_mode_next() { model.read(&app, |model, _| { let auth_url = model.github_auth_url().expect("auth url should be present"); + assert_eq!(model.github_auth_error_message(), Some("auth required")); let parsed = Url::parse(auth_url).expect("auth url should parse"); let next = parsed .query_pairs() diff --git a/app/src/terminal/view/ambient_agent/view_impl.rs b/app/src/terminal/view/ambient_agent/view_impl.rs index edd527e51f..467730bcf4 100644 --- a/app/src/terminal/view/ambient_agent/view_impl.rs +++ b/app/src/terminal/view/ambient_agent/view_impl.rs @@ -873,6 +873,7 @@ impl TerminalView { // Show GitHub auth required screen render_cloud_mode_github_auth_required_screen( auth_url, + ambient_agent_model.github_auth_error_message(), appearance, &ui_state.auth_button_mouse_state, app, From 4ad168abdbfe0bd1b48022e510861d96446a3e64 Mon Sep 17 00:00:00 2001 From: captainsafia Date: Mon, 18 May 2026 20:25:49 -0700 Subject: [PATCH 2/3] fix: constrain cloud mode auth error text Constrain Cloud Mode GitHub auth and setup error messages so long server-provided copy stays within the pane/card layout without changing the displayed text. Co-Authored-By: Oz --- app/src/terminal/input/message_bar/common.rs | 4 ++- .../view/ambient_agent/loading_screen.rs | 28 +++++++++++++------ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/app/src/terminal/input/message_bar/common.rs b/app/src/terminal/input/message_bar/common.rs index 9d510786a4..890d7f094f 100644 --- a/app/src/terminal/input/message_bar/common.rs +++ b/app/src/terminal/input/message_bar/common.rs @@ -92,7 +92,9 @@ fn render_message_bar_items(items: &[MessageItem], app: &AppContext) -> Box = match item { diff --git a/app/src/terminal/view/ambient_agent/loading_screen.rs b/app/src/terminal/view/ambient_agent/loading_screen.rs index fc198e62b4..75b51a9ca0 100644 --- a/app/src/terminal/view/ambient_agent/loading_screen.rs +++ b/app/src/terminal/view/ambient_agent/loading_screen.rs @@ -26,6 +26,9 @@ use crate::workspaces::user_workspaces::UserWorkspaces; /// Icon size for the error icon const ERROR_ICON_SIZE: f32 = 24.; +const MESSAGE_CARD_MAX_WIDTH: f32 = 400.; +const MESSAGE_CARD_HORIZONTAL_PADDING: f32 = 24.; +const MESSAGE_TEXT_MAX_WIDTH: f32 = MESSAGE_CARD_MAX_WIDTH - MESSAGE_CARD_HORIZONTAL_PADDING * 2.; /// Renders the cloud mode loading screen with shimmering warp logo and tips. pub fn render_cloud_mode_loading_screen( @@ -270,7 +273,11 @@ pub fn render_cloud_mode_error_screen( .with_spacing(12.) .with_child(error_icon) .with_child(title_text) - .with_child(selectable_error_text) + .with_child( + ConstrainedBox::new(selectable_error_text) + .with_max_width(MESSAGE_TEXT_MAX_WIDTH) + .finish(), + ) .finish(); // Red bordered container with 10% opacity background @@ -280,13 +287,13 @@ pub fn render_cloud_mode_error_screen( .with_background(error_background) .with_border(Border::all(1.).with_border_color(error_color.into())) .with_corner_radius(CornerRadius::with_all(Radius::Pixels(8.))) - .with_horizontal_padding(24.) + .with_horizontal_padding(MESSAGE_CARD_HORIZONTAL_PADDING) .with_vertical_padding(16.) .finish(); // Constrain error container to max 400px width let constrained_error = ConstrainedBox::new(error_container) - .with_max_width(400.) + .with_max_width(MESSAGE_CARD_MAX_WIDTH) .finish(); // Center the error container in the view @@ -342,6 +349,7 @@ pub fn render_cloud_mode_github_auth_required_screen( appearance.monospace_font_size(), ) .with_color(message_color) + .soft_wrap(true) .finish(); // Create the authenticate button @@ -362,7 +370,11 @@ pub fn render_cloud_mode_github_auth_required_screen( .with_spacing(12.) .with_child(auth_icon) .with_child(title_text) - .with_child(message_text) + .with_child( + ConstrainedBox::new(message_text) + .with_max_width(MESSAGE_TEXT_MAX_WIDTH) + .finish(), + ) .with_child(Container::new(auth_button).with_margin_top(8.).finish()) .finish(); @@ -373,13 +385,13 @@ pub fn render_cloud_mode_github_auth_required_screen( .with_background(auth_background) .with_border(Border::all(1.).with_border_color(border_color)) .with_corner_radius(CornerRadius::with_all(Radius::Pixels(8.))) - .with_horizontal_padding(24.) + .with_horizontal_padding(MESSAGE_CARD_HORIZONTAL_PADDING) .with_vertical_padding(16.) .finish(); // Constrain container to max 400px width let constrained_auth = ConstrainedBox::new(auth_container) - .with_max_width(400.) + .with_max_width(MESSAGE_CARD_MAX_WIDTH) .finish(); // Center the container in the view @@ -447,13 +459,13 @@ pub fn render_cloud_mode_cancelled_screen(appearance: &Appearance) -> Box Date: Tue, 19 May 2026 09:33:17 -0700 Subject: [PATCH 3/3] fix: wrap cloud mode setup footer errors Co-Authored-By: Oz --- app/src/ai/blocklist/block/status_bar.rs | 77 ++++++++----------- app/src/terminal/input/message_bar/common.rs | 54 ++++++++++++- .../view/ambient_agent/loading_screen.rs | 35 +++------ .../terminal/view/ambient_agent/view_impl.rs | 1 - 4 files changed, 94 insertions(+), 73 deletions(-) diff --git a/app/src/ai/blocklist/block/status_bar.rs b/app/src/ai/blocklist/block/status_bar.rs index bd1c5369cd..68c6ca79f7 100644 --- a/app/src/ai/blocklist/block/status_bar.rs +++ b/app/src/ai/blocklist/block/status_bar.rs @@ -1,3 +1,4 @@ +use markdown_parser::FormattedTextFragment; use std::{collections::HashSet, sync::Arc, time::Duration}; use super::{ @@ -22,11 +23,8 @@ use crate::{ AgentViewController, EphemeralMessageModel, }, terminal::input::{ - buffer_model::InputBufferModel, - message_bar::common::render_standard_message_bar, - message_bar::{Message, MessageItem}, - slash_command_model::SlashCommandModel, - suggestions_mode_model::InputSuggestionsModeModel, + buffer_model::InputBufferModel, message_bar::common::render_wrapping_standard_message_bar, + slash_command_model::SlashCommandModel, suggestions_mode_model::InputSuggestionsModeModel, HandoffComposeState, }, }; @@ -97,7 +95,6 @@ struct StateHandles { stop_button: MouseStateHandle, take_over_button: MouseStateHandle, hide_cli_responses_button: MouseStateHandle, - github_auth_link: MouseStateHandle, /// Tracks hover/press state for the inline `Check now` affordance rendered next to /// `Last seen by agent ...` while the agent is polling a long-running command. force_refresh_button: MouseStateHandle, @@ -916,7 +913,10 @@ impl BlocklistAIStatusBar { )) } - fn render_cloud_mode_setup_terminal_message(&self, app: &AppContext) -> Option { + fn render_cloud_mode_setup_terminal_message( + &self, + app: &AppContext, + ) -> Option> { if !FeatureFlag::CloudModeSetupV2.is_enabled() { return None; } @@ -932,48 +932,39 @@ impl BlocklistAIStatusBar { let error_message = ambient_agent_model .github_auth_error_message() .unwrap_or("Missing GitHub authentication."); - return Some(Message::new(vec![ - MessageItem::Icon { - icon: CoreIcon::Triangle, - color: Some(error_color), - }, - MessageItem::Text { - content: format!("{error_message} ").into(), - color: Some(error_color), - }, - MessageItem::hyperlink( - "Authenticate GitHub", - auth_url.to_owned(), - self.state_handles.github_auth_link.clone(), - ), - ])); + return Some(render_wrapping_standard_message_bar( + CoreIcon::Triangle, + error_color, + error_color, + vec![ + FormattedTextFragment::plain_text(format!("{error_message} ")), + FormattedTextFragment::hyperlink("Authenticate GitHub", auth_url.to_owned()), + ], + app, + )); } if ambient_agent_model.is_cancelled() { let color = theme.disabled_text_color(theme.background()).into_solid(); - return Some(Message::new(vec![ - MessageItem::Icon { - icon: CoreIcon::StopFilled, - color: Some(color), - }, - MessageItem::Text { - content: "Cloud agent run cancelled".into(), - color: Some(color), - }, - ])); + return Some(render_wrapping_standard_message_bar( + CoreIcon::StopFilled, + color, + color, + vec![FormattedTextFragment::plain_text( + "Cloud agent run cancelled", + )], + app, + )); } if let Some(error_message) = ambient_agent_model.error_message() { - return Some(Message::new(vec![ - MessageItem::Icon { - icon: CoreIcon::Triangle, - color: Some(error_color), - }, - MessageItem::Text { - content: error_message.to_owned().into(), - color: Some(error_color), - }, - ])); + return Some(render_wrapping_standard_message_bar( + CoreIcon::Triangle, + error_color, + error_color, + vec![FormattedTextFragment::plain_text(error_message.to_owned())], + app, + )); } None @@ -1152,7 +1143,7 @@ impl View for BlocklistAIStatusBar { if let Some(cloud_mode_setup_terminal_message) = self.render_cloud_mode_setup_terminal_message(app) { - return render_standard_message_bar(cloud_mode_setup_terminal_message, None, app); + return cloud_mode_setup_terminal_message; } let status_element = if let Some(cloud_mode_setup_status) = self.render_cloud_mode_setup_status(app) { diff --git a/app/src/terminal/input/message_bar/common.rs b/app/src/terminal/input/message_bar/common.rs index 890d7f094f..b44d1f317e 100644 --- a/app/src/terminal/input/message_bar/common.rs +++ b/app/src/terminal/input/message_bar/common.rs @@ -1,14 +1,15 @@ use crate::ai::blocklist::agent_view::agent_view_bg_color; +use markdown_parser::{FormattedText, FormattedTextFragment, FormattedTextLine}; use pathfinder_color::ColorU; use warp_core::ui::appearance::Appearance; use warp_core::ui::theme::Fill; use warp_core::ui::Icon; use warpui::elements::{ - Border, CacheOption, Clipped, Container, CornerRadius, Element, Hoverable, Image, - ParentElement, Radius, + Border, CacheOption, Clipped, Container, CornerRadius, Element, FormattedTextElement, + Hoverable, Image, ParentElement, Radius, Wrap, WrapFill, DEFAULT_UI_LINE_HEIGHT_RATIO, }; use warpui::platform::Cursor; -use warpui::prelude::{Align, ConstrainedBox, CrossAxisAlignment, Flex, Text}; +use warpui::prelude::{Align, ConstrainedBox, CrossAxisAlignment, Flex, MainAxisSize, Text}; use warpui::ui_components::keyboard_shortcut::keystroke_to_keys; use warpui::{AppContext, SingletonEntity}; @@ -29,7 +30,7 @@ pub fn render_standard_message_bar( right_element: Option>, app: &AppContext, ) -> Box { - use warpui::prelude::{MainAxisAlignment, MainAxisSize}; + use warpui::prelude::MainAxisAlignment; let (left_items, right_chips): (Vec<_>, Vec<_>) = message.items.into_iter().partition(|item| { !matches!( @@ -81,6 +82,51 @@ pub fn render_standard_message_bar( .with_height(standard_message_bar_height(app)) .finish() } +/// Renders a standard message bar variant for inline text and hyperlinks that need to soft-wrap. +/// `render_standard_message_bar` intentionally remains fixed-height and single-line for existing +/// hint/status bars. +pub fn render_wrapping_standard_message_bar( + icon: Icon, + icon_color: ColorU, + text_color: ColorU, + fragments: Vec, + app: &AppContext, +) -> Box { + let appearance = Appearance::as_ref(app); + let theme = appearance.theme(); + let font_size = styles::font_size(app); + let icon = ConstrainedBox::new(icon.to_warpui_icon(Fill::Solid(icon_color)).finish()) + .with_height(font_size) + .with_width(font_size) + .finish(); + let text = FormattedTextElement::new( + FormattedText::new([FormattedTextLine::Line(fragments)]), + font_size, + appearance.ui_font_family(), + appearance.monospace_font_family(), + text_color, + Default::default(), + ) + .with_line_height_ratio(DEFAULT_UI_LINE_HEIGHT_RATIO) + .with_hyperlink_font_color(theme.accent().into()) + .register_default_click_handlers(|url, _ctx, app| { + app.open_url(&url.url); + }) + .finish(); + + Container::new( + Wrap::row() + .with_main_axis_size(MainAxisSize::Max) + .with_cross_axis_alignment(CrossAxisAlignment::Start) + .with_spacing(4.) + .with_child(icon) + .with_child(WrapFill::new(0., text).finish()) + .finish(), + ) + .with_horizontal_padding(*terminal::view::PADDING_LEFT) + .with_vertical_padding(styles::VERTICAL_PADDING) + .finish() +} pub fn render_standard_message(message: Message, app: &AppContext) -> Box { render_message_bar_items(&message.items, app) diff --git a/app/src/terminal/view/ambient_agent/loading_screen.rs b/app/src/terminal/view/ambient_agent/loading_screen.rs index 75b51a9ca0..f4bb9c847d 100644 --- a/app/src/terminal/view/ambient_agent/loading_screen.rs +++ b/app/src/terminal/view/ambient_agent/loading_screen.rs @@ -26,9 +26,6 @@ use crate::workspaces::user_workspaces::UserWorkspaces; /// Icon size for the error icon const ERROR_ICON_SIZE: f32 = 24.; -const MESSAGE_CARD_MAX_WIDTH: f32 = 400.; -const MESSAGE_CARD_HORIZONTAL_PADDING: f32 = 24.; -const MESSAGE_TEXT_MAX_WIDTH: f32 = MESSAGE_CARD_MAX_WIDTH - MESSAGE_CARD_HORIZONTAL_PADDING * 2.; /// Renders the cloud mode loading screen with shimmering warp logo and tips. pub fn render_cloud_mode_loading_screen( @@ -273,11 +270,7 @@ pub fn render_cloud_mode_error_screen( .with_spacing(12.) .with_child(error_icon) .with_child(title_text) - .with_child( - ConstrainedBox::new(selectable_error_text) - .with_max_width(MESSAGE_TEXT_MAX_WIDTH) - .finish(), - ) + .with_child(selectable_error_text) .finish(); // Red bordered container with 10% opacity background @@ -287,13 +280,13 @@ pub fn render_cloud_mode_error_screen( .with_background(error_background) .with_border(Border::all(1.).with_border_color(error_color.into())) .with_corner_radius(CornerRadius::with_all(Radius::Pixels(8.))) - .with_horizontal_padding(MESSAGE_CARD_HORIZONTAL_PADDING) + .with_horizontal_padding(24.) .with_vertical_padding(16.) .finish(); // Constrain error container to max 400px width let constrained_error = ConstrainedBox::new(error_container) - .with_max_width(MESSAGE_CARD_MAX_WIDTH) + .with_max_width(400.) .finish(); // Center the error container in the view @@ -308,7 +301,6 @@ pub fn render_cloud_mode_error_screen( /// Renders the cloud mode GitHub authentication required screen. pub fn render_cloud_mode_github_auth_required_screen( auth_url: &str, - error_message: Option<&str>, appearance: &Appearance, auth_button_mouse_state: &MouseStateHandle, _app: &AppContext, @@ -340,16 +332,13 @@ pub fn render_cloud_mode_github_auth_required_screen( .with_color(title_color) .finish(); - let error_message = error_message.unwrap_or("Please authenticate with GitHub to continue"); - - // Message text + // Message text - "Please authenticate with GitHub to continue" let message_text = Text::new( - error_message.to_string(), + "Please authenticate with GitHub to continue", appearance.ui_font_family(), appearance.monospace_font_size(), ) .with_color(message_color) - .soft_wrap(true) .finish(); // Create the authenticate button @@ -370,11 +359,7 @@ pub fn render_cloud_mode_github_auth_required_screen( .with_spacing(12.) .with_child(auth_icon) .with_child(title_text) - .with_child( - ConstrainedBox::new(message_text) - .with_max_width(MESSAGE_TEXT_MAX_WIDTH) - .finish(), - ) + .with_child(message_text) .with_child(Container::new(auth_button).with_margin_top(8.).finish()) .finish(); @@ -385,13 +370,13 @@ pub fn render_cloud_mode_github_auth_required_screen( .with_background(auth_background) .with_border(Border::all(1.).with_border_color(border_color)) .with_corner_radius(CornerRadius::with_all(Radius::Pixels(8.))) - .with_horizontal_padding(MESSAGE_CARD_HORIZONTAL_PADDING) + .with_horizontal_padding(24.) .with_vertical_padding(16.) .finish(); // Constrain container to max 400px width let constrained_auth = ConstrainedBox::new(auth_container) - .with_max_width(MESSAGE_CARD_MAX_WIDTH) + .with_max_width(400.) .finish(); // Center the container in the view @@ -459,13 +444,13 @@ pub fn render_cloud_mode_cancelled_screen(appearance: &Appearance) -> Box