ai onboarding: Add overall fixes to the whole flow (#34996)

Danilo Leal , Agus Zubiaga , and Ben Kunkle created

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

Release Notes:

- N/A

---------

Co-authored-by: Agus Zubiaga <hi@aguz.me>
Co-authored-by: Ben Kunkle <Ben.kunkle@gmail.com>

Change summary

crates/agent/src/thread_store.rs                           |   9 
crates/agent_ui/src/agent_configuration.rs                 |  11 
crates/agent_ui/src/agent_panel.rs                         |  56 +-
crates/agent_ui/src/message_editor.rs                      |  52 +-
crates/agent_ui/src/ui.rs                                  |   1 
crates/agent_ui/src/ui/end_trial_upsell.rs                 |  55 +-
crates/agent_ui/src/ui/upsell.rs                           | 163 --------
crates/ai_onboarding/src/agent_panel_onboarding_content.rs |   7 
crates/ai_onboarding/src/ai_onboarding.rs                  |  30 +
crates/assistant_context/src/context_store.rs              |   5 
crates/client/src/user.rs                                  |   6 
crates/language_models/src/provider/cloud.rs               |   5 
12 files changed, 150 insertions(+), 250 deletions(-)

Detailed changes

crates/agent/src/thread_store.rs 🔗

@@ -41,6 +41,9 @@ use std::{
 };
 use util::ResultExt as _;
 
+pub static ZED_STATELESS: std::sync::LazyLock<bool> =
+    std::sync::LazyLock::new(|| std::env::var("ZED_STATELESS").map_or(false, |v| !v.is_empty()));
+
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 pub enum DataType {
     #[serde(rename = "json")]
@@ -874,7 +877,11 @@ impl ThreadsDatabase {
 
         let needs_migration_from_heed = mdb_path.exists();
 
-        let connection = Connection::open_file(&sqlite_path.to_string_lossy());
+        let connection = if *ZED_STATELESS {
+            Connection::open_memory(Some("THREAD_FALLBACK_DB"))
+        } else {
+            Connection::open_file(&sqlite_path.to_string_lossy())
+        };
 
         connection.exec(indoc! {"
                 CREATE TABLE IF NOT EXISTS threads (

crates/agent_ui/src/agent_configuration.rs 🔗

@@ -185,6 +185,13 @@ impl AgentConfiguration {
             None
         };
 
+        let is_signed_in = self
+            .workspace
+            .read_with(cx, |workspace, _| {
+                workspace.client().status().borrow().is_connected()
+            })
+            .unwrap_or(false);
+
         v_flex()
             .w_full()
             .when(is_expanded, |this| this.mb_2())
@@ -233,8 +240,8 @@ impl AgentConfiguration {
                                                     .size(LabelSize::Large),
                                             )
                                             .map(|this| {
-                                                if is_zed_provider {
-                                                    this.gap_2().child(
+                                                if is_zed_provider && is_signed_in {
+                                                    this.child(
                                                         self.render_zed_plan_info(current_plan, cx),
                                                     )
                                                 } else {

crates/agent_ui/src/agent_panel.rs 🔗

@@ -564,6 +564,17 @@ impl AgentPanel {
         let inline_assist_context_store =
             cx.new(|_cx| ContextStore::new(project.downgrade(), Some(thread_store.downgrade())));
 
+        let thread_id = thread.read(cx).id().clone();
+
+        let history_store = cx.new(|cx| {
+            HistoryStore::new(
+                thread_store.clone(),
+                context_store.clone(),
+                [HistoryEntryId::Thread(thread_id)],
+                cx,
+            )
+        });
+
         let message_editor = cx.new(|cx| {
             MessageEditor::new(
                 fs.clone(),
@@ -573,22 +584,13 @@ impl AgentPanel {
                 prompt_store.clone(),
                 thread_store.downgrade(),
                 context_store.downgrade(),
+                Some(history_store.downgrade()),
                 thread.clone(),
                 window,
                 cx,
             )
         });
 
-        let thread_id = thread.read(cx).id().clone();
-        let history_store = cx.new(|cx| {
-            HistoryStore::new(
-                thread_store.clone(),
-                context_store.clone(),
-                [HistoryEntryId::Thread(thread_id)],
-                cx,
-            )
-        });
-
         cx.observe(&history_store, |_, _, cx| cx.notify()).detach();
 
         let active_thread = cx.new(|cx| {
@@ -851,6 +853,7 @@ impl AgentPanel {
                 self.prompt_store.clone(),
                 self.thread_store.downgrade(),
                 self.context_store.downgrade(),
+                Some(self.history_store.downgrade()),
                 thread.clone(),
                 window,
                 cx,
@@ -1124,6 +1127,7 @@ impl AgentPanel {
                 self.prompt_store.clone(),
                 self.thread_store.downgrade(),
                 self.context_store.downgrade(),
+                Some(self.history_store.downgrade()),
                 thread.clone(),
                 window,
                 cx,
@@ -2283,20 +2287,21 @@ impl AgentPanel {
         }
 
         match &self.active_view {
-            ActiveView::Thread { thread, .. } => thread
-                .read(cx)
-                .thread()
-                .read(cx)
-                .configured_model()
-                .map_or(true, |model| {
-                    model.provider.id() == language_model::ZED_CLOUD_PROVIDER_ID
-                }),
-            ActiveView::TextThread { .. } => LanguageModelRegistry::global(cx)
-                .read(cx)
-                .default_model()
-                .map_or(true, |model| {
-                    model.provider.id() == language_model::ZED_CLOUD_PROVIDER_ID
-                }),
+            ActiveView::Thread { .. } | ActiveView::TextThread { .. } => {
+                let history_is_empty = self
+                    .history_store
+                    .update(cx, |store, cx| store.recent_entries(1, cx).is_empty());
+
+                let has_configured_non_zed_providers = LanguageModelRegistry::read_global(cx)
+                    .providers()
+                    .iter()
+                    .any(|provider| {
+                        provider.is_authenticated(cx)
+                            && provider.id() != language_model::ZED_CLOUD_PROVIDER_ID
+                    });
+
+                history_is_empty || !has_configured_non_zed_providers
+            }
             ActiveView::ExternalAgentThread { .. }
             | ActiveView::History
             | ActiveView::Configuration => false,
@@ -2317,9 +2322,8 @@ impl AgentPanel {
 
         Some(
             div()
-                .size_full()
                 .when(thread_view, |this| {
-                    this.bg(cx.theme().colors().panel_background)
+                    this.size_full().bg(cx.theme().colors().panel_background)
                 })
                 .when(text_thread_view, |this| {
                     this.bg(cx.theme().colors().editor_background)

crates/agent_ui/src/message_editor.rs 🔗

@@ -9,6 +9,7 @@ use crate::ui::{
     MaxModeTooltip,
     preview::{AgentPreview, UsageCallout},
 };
+use agent::history_store::HistoryStore;
 use agent::{
     context::{AgentContextKey, ContextLoadResult, load_context},
     context_store::ContextStoreEvent,
@@ -29,8 +30,9 @@ use fs::Fs;
 use futures::future::Shared;
 use futures::{FutureExt as _, future};
 use gpui::{
-    Animation, AnimationExt, App, Entity, EventEmitter, Focusable, KeyContext, Subscription, Task,
-    TextStyle, WeakEntity, linear_color_stop, linear_gradient, point, pulsating_between,
+    Animation, AnimationExt, App, Entity, EventEmitter, Focusable, IntoElement, KeyContext,
+    Subscription, Task, TextStyle, WeakEntity, linear_color_stop, linear_gradient, point,
+    pulsating_between,
 };
 use language::{Buffer, Language, Point};
 use language_model::{
@@ -80,6 +82,7 @@ pub struct MessageEditor {
     user_store: Entity<UserStore>,
     context_store: Entity<ContextStore>,
     prompt_store: Option<Entity<PromptStore>>,
+    history_store: Option<WeakEntity<HistoryStore>>,
     context_strip: Entity<ContextStrip>,
     context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
     model_selector: Entity<AgentModelSelector>,
@@ -161,6 +164,7 @@ impl MessageEditor {
         prompt_store: Option<Entity<PromptStore>>,
         thread_store: WeakEntity<ThreadStore>,
         text_thread_store: WeakEntity<TextThreadStore>,
+        history_store: Option<WeakEntity<HistoryStore>>,
         thread: Entity<Thread>,
         window: &mut Window,
         cx: &mut Context<Self>,
@@ -233,6 +237,7 @@ impl MessageEditor {
             workspace,
             context_store,
             prompt_store,
+            history_store,
             context_strip,
             context_picker_menu_handle,
             load_context_task: None,
@@ -1661,32 +1666,36 @@ impl Render for MessageEditor {
 
         let line_height = TextSize::Small.rems(cx).to_pixels(window.rem_size()) * 1.5;
 
-        let in_pro_trial = matches!(
-            self.user_store.read(cx).current_plan(),
-            Some(proto::Plan::ZedProTrial)
-        );
+        let has_configured_providers = LanguageModelRegistry::read_global(cx)
+            .providers()
+            .iter()
+            .filter(|provider| {
+                provider.is_authenticated(cx) && provider.id() != ZED_CLOUD_PROVIDER_ID
+            })
+            .count()
+            > 0;
 
-        let pro_user = matches!(
-            self.user_store.read(cx).current_plan(),
-            Some(proto::Plan::ZedPro)
-        );
+        let is_signed_out = self
+            .workspace
+            .read_with(cx, |workspace, _| {
+                workspace.client().status().borrow().is_signed_out()
+            })
+            .unwrap_or(true);
 
-        let configured_providers: Vec<(IconName, SharedString)> =
-            LanguageModelRegistry::read_global(cx)
-                .providers()
-                .iter()
-                .filter(|provider| {
-                    provider.is_authenticated(cx) && provider.id() != ZED_CLOUD_PROVIDER_ID
-                })
-                .map(|provider| (provider.icon(), provider.name().0.clone()))
-                .collect();
-        let has_existing_providers = configured_providers.len() > 0;
+        let has_history = self
+            .history_store
+            .as_ref()
+            .and_then(|hs| hs.update(cx, |hs, cx| hs.entries(cx).len() > 0).ok())
+            .unwrap_or(false)
+            || self
+                .thread
+                .read_with(cx, |thread, _| thread.messages().len() > 0);
 
         v_flex()
             .size_full()
             .bg(cx.theme().colors().panel_background)
             .when(
-                has_existing_providers && !in_pro_trial && !pro_user,
+                !has_history && is_signed_out && has_configured_providers,
                 |this| this.child(cx.new(ApiKeysWithProviders::new)),
             )
             .when(changed_buffers.len() > 0, |parent| {
@@ -1778,6 +1787,7 @@ impl AgentPreview for MessageEditor {
                     None,
                     thread_store.downgrade(),
                     text_thread_store.downgrade(),
+                    None,
                     thread,
                     window,
                     cx,

crates/agent_ui/src/ui.rs 🔗

@@ -5,7 +5,6 @@ mod end_trial_upsell;
 mod new_thread_button;
 mod onboarding_modal;
 pub mod preview;
-mod upsell;
 
 pub use agent_notification::*;
 pub use burn_mode_tooltip::*;

crates/agent_ui/src/ui/end_trial_upsell.rs 🔗

@@ -3,7 +3,7 @@ use std::sync::Arc;
 use ai_onboarding::{AgentPanelOnboardingCard, BulletItem};
 use client::zed_urls;
 use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
-use ui::{Divider, List, prelude::*};
+use ui::{Divider, List, Tooltip, prelude::*};
 
 #[derive(IntoElement, RegisterComponent)]
 pub struct EndTrialUpsell {
@@ -33,14 +33,19 @@ impl RenderOnce for EndTrialUpsell {
             )
             .child(
                 List::new()
-                    .child(BulletItem::new("500 prompts per month with Claude models"))
-                    .child(BulletItem::new("Unlimited edit predictions")),
+                    .child(BulletItem::new("500 prompts with Claude models"))
+                    .child(BulletItem::new(
+                        "Unlimited edit predictions with Zeta, our open-source model",
+                    )),
             )
             .child(
                 Button::new("cta-button", "Upgrade to Zed Pro")
                     .full_width()
                     .style(ButtonStyle::Tinted(ui::TintColor::Accent))
-                    .on_click(|_, _, cx| cx.open_url(&zed_urls::upgrade_to_zed_pro_url(cx))),
+                    .on_click(move |_, _window, cx| {
+                        telemetry::event!("Upgrade To Pro Clicked", state = "end-of-trial");
+                        cx.open_url(&zed_urls::upgrade_to_zed_pro_url(cx))
+                    }),
             );
 
         let free_section = v_flex()
@@ -55,37 +60,43 @@ impl RenderOnce for EndTrialUpsell {
                             .color(Color::Muted)
                             .buffer_font(cx),
                     )
+                    .child(
+                        Label::new("(Current Plan)")
+                            .size(LabelSize::Small)
+                            .color(Color::Custom(cx.theme().colors().text_muted.opacity(0.6)))
+                            .buffer_font(cx),
+                    )
                     .child(Divider::horizontal()),
             )
             .child(
                 List::new()
-                    .child(BulletItem::new(
-                        "50 prompts per month with the Claude models",
-                    ))
-                    .child(BulletItem::new(
-                        "2000 accepted edit predictions using our open-source Zeta model",
-                    )),
-            )
-            .child(
-                Button::new("dismiss-button", "Stay on Free")
-                    .full_width()
-                    .style(ButtonStyle::Outlined)
-                    .on_click({
-                        let callback = self.dismiss_upsell.clone();
-                        move |_, window, cx| callback(window, cx)
-                    }),
+                    .child(BulletItem::new("50 prompts with the Claude models"))
+                    .child(BulletItem::new("2,000 accepted edit predictions")),
             );
 
         AgentPanelOnboardingCard::new()
-            .child(Headline::new("Your Zed Pro trial has expired."))
+            .child(Headline::new("Your Zed Pro Trial has expired"))
             .child(
                 Label::new("You've been automatically reset to the Free plan.")
-                    .size(LabelSize::Small)
                     .color(Color::Muted)
-                    .mb_1(),
+                    .mb_2(),
             )
             .child(pro_section)
             .child(free_section)
+            .child(
+                h_flex().absolute().top_4().right_4().child(
+                    IconButton::new("dismiss_onboarding", IconName::Close)
+                        .icon_size(IconSize::Small)
+                        .tooltip(Tooltip::text("Dismiss"))
+                        .on_click({
+                            let callback = self.dismiss_upsell.clone();
+                            move |_, window, cx| {
+                                telemetry::event!("Banner Dismissed", source = "AI Onboarding");
+                                callback(window, cx)
+                            }
+                        }),
+                ),
+            )
     }
 }
 

crates/agent_ui/src/ui/upsell.rs 🔗

@@ -1,163 +0,0 @@
-use component::{Component, ComponentScope, single_example};
-use gpui::{
-    AnyElement, App, ClickEvent, IntoElement, ParentElement, RenderOnce, SharedString, Styled,
-    Window,
-};
-use theme::ActiveTheme;
-use ui::{
-    Button, ButtonCommon, ButtonStyle, Checkbox, Clickable, Color, Label, LabelCommon,
-    RegisterComponent, ToggleState, h_flex, v_flex,
-};
-
-/// A component that displays an upsell message with a call-to-action button
-///
-/// # Example
-/// ```
-/// let upsell = Upsell::new(
-///     "Upgrade to Zed Pro",
-///     "Get access to advanced AI features and more",
-///     "Upgrade Now",
-///     Box::new(|_, _window, cx| {
-///         cx.open_url("https://zed.dev/pricing");
-///     }),
-///     Box::new(|_, _window, cx| {
-///         // Handle dismiss
-///     }),
-///     Box::new(|checked, window, cx| {
-///         // Handle don't show again
-///     }),
-/// );
-/// ```
-#[derive(IntoElement, RegisterComponent)]
-pub struct Upsell {
-    title: SharedString,
-    message: SharedString,
-    cta_text: SharedString,
-    on_click: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
-    on_dismiss: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
-    on_dont_show_again: Box<dyn Fn(bool, &mut Window, &mut App) + 'static>,
-}
-
-impl Upsell {
-    /// Create a new upsell component
-    pub fn new(
-        title: impl Into<SharedString>,
-        message: impl Into<SharedString>,
-        cta_text: impl Into<SharedString>,
-        on_click: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
-        on_dismiss: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
-        on_dont_show_again: Box<dyn Fn(bool, &mut Window, &mut App) + 'static>,
-    ) -> Self {
-        Self {
-            title: title.into(),
-            message: message.into(),
-            cta_text: cta_text.into(),
-            on_click,
-            on_dismiss,
-            on_dont_show_again,
-        }
-    }
-}
-
-impl RenderOnce for Upsell {
-    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
-        v_flex()
-            .w_full()
-            .p_4()
-            .gap_3()
-            .bg(cx.theme().colors().surface_background)
-            .rounded_md()
-            .border_1()
-            .border_color(cx.theme().colors().border)
-            .child(
-                v_flex()
-                    .gap_1()
-                    .child(
-                        Label::new(self.title)
-                            .size(ui::LabelSize::Large)
-                            .weight(gpui::FontWeight::BOLD),
-                    )
-                    .child(Label::new(self.message).color(Color::Muted)),
-            )
-            .child(
-                h_flex()
-                    .w_full()
-                    .justify_between()
-                    .items_center()
-                    .child(
-                        h_flex()
-                            .items_center()
-                            .gap_1()
-                            .child(
-                                Checkbox::new("dont-show-again", ToggleState::Unselected).on_click(
-                                    move |_, window, cx| {
-                                        (self.on_dont_show_again)(true, window, cx);
-                                    },
-                                ),
-                            )
-                            .child(
-                                Label::new("Don't show again")
-                                    .color(Color::Muted)
-                                    .size(ui::LabelSize::Small),
-                            ),
-                    )
-                    .child(
-                        h_flex()
-                            .gap_2()
-                            .child(
-                                Button::new("dismiss-button", "No Thanks")
-                                    .style(ButtonStyle::Subtle)
-                                    .on_click(self.on_dismiss),
-                            )
-                            .child(
-                                Button::new("cta-button", self.cta_text)
-                                    .style(ButtonStyle::Filled)
-                                    .on_click(self.on_click),
-                            ),
-                    ),
-            )
-    }
-}
-
-impl Component for Upsell {
-    fn scope() -> ComponentScope {
-        ComponentScope::Agent
-    }
-
-    fn name() -> &'static str {
-        "Upsell"
-    }
-
-    fn description() -> Option<&'static str> {
-        Some("A promotional component that displays a message with a call-to-action.")
-    }
-
-    fn preview(window: &mut Window, cx: &mut App) -> Option<AnyElement> {
-        let examples = vec![
-            single_example(
-                "Default",
-                Upsell::new(
-                    "Upgrade to Zed Pro",
-                    "Get unlimited access to AI features and more with Zed Pro. Unlock advanced AI capabilities and other premium features.",
-                    "Upgrade Now",
-                    Box::new(|_, _, _| {}),
-                    Box::new(|_, _, _| {}),
-                    Box::new(|_, _, _| {}),
-                ).render(window, cx).into_any_element(),
-            ),
-            single_example(
-                "Short Message",
-                Upsell::new(
-                    "Try Zed Pro for free",
-                    "Start your 7-day trial today.",
-                    "Start Trial",
-                    Box::new(|_, _, _| {}),
-                    Box::new(|_, _, _| {}),
-                    Box::new(|_, _, _| {}),
-                ).render(window, cx).into_any_element(),
-            ),
-        ];
-
-        Some(v_flex().gap_4().children(examples).into_any_element())
-    }
-}

crates/ai_onboarding/src/agent_panel_onboarding_content.rs 🔗

@@ -61,6 +61,11 @@ impl Render for AgentPanelOnboarding {
             Some(proto::Plan::ZedProTrial)
         );
 
+        let is_pro_user = matches!(
+            self.user_store.read(cx).current_plan(),
+            Some(proto::Plan::ZedPro)
+        );
+
         AgentPanelOnboardingCard::new()
             .child(
                 ZedAiOnboarding::new(
@@ -75,7 +80,7 @@ impl Render for AgentPanelOnboarding {
                 }),
             )
             .map(|this| {
-                if enrolled_in_trial || self.configured_providers.len() >= 1 {
+                if enrolled_in_trial || is_pro_user || self.configured_providers.len() >= 1 {
                     this
                 } else {
                     this.child(ApiKeysWithoutProviders::new())

crates/ai_onboarding/src/ai_onboarding.rs 🔗

@@ -16,6 +16,7 @@ use client::{Client, UserStore, zed_urls};
 use gpui::{AnyElement, Entity, IntoElement, ParentElement, SharedString};
 use ui::{Divider, List, ListItem, RegisterComponent, TintColor, Tooltip, prelude::*};
 
+#[derive(IntoElement)]
 pub struct BulletItem {
     label: SharedString,
 }
@@ -28,18 +29,27 @@ impl BulletItem {
     }
 }
 
-impl IntoElement for BulletItem {
-    type Element = AnyElement;
+impl RenderOnce for BulletItem {
+    fn render(self, window: &mut Window, _cx: &mut App) -> impl IntoElement {
+        let line_height = 0.85 * window.line_height();
 
-    fn into_element(self) -> Self::Element {
         ListItem::new("list-item")
             .selectable(false)
-            .start_slot(
-                Icon::new(IconName::Dash)
-                    .size(IconSize::XSmall)
-                    .color(Color::Hidden),
+            .child(
+                h_flex()
+                    .w_full()
+                    .min_w_0()
+                    .gap_1()
+                    .items_start()
+                    .child(
+                        h_flex().h(line_height).justify_center().child(
+                            Icon::new(IconName::Dash)
+                                .size(IconSize::XSmall)
+                                .color(Color::Hidden),
+                        ),
+                    )
+                    .child(div().w_full().min_w_0().child(Label::new(self.label))),
             )
-            .child(div().w_full().child(Label::new(self.label)))
             .into_any_element()
     }
 }
@@ -373,7 +383,9 @@ impl ZedAiOnboarding {
             .child(
                 List::new()
                     .child(BulletItem::new("500 prompts with Claude models"))
-                    .child(BulletItem::new("Unlimited edit predictions")),
+                    .child(BulletItem::new(
+                        "Unlimited edit predictions with Zeta, our open-source model",
+                    )),
             )
             .child(
                 Button::new("pro", "Continue with Zed Pro")

crates/assistant_context/src/context_store.rs 🔗

@@ -767,6 +767,11 @@ impl ContextStore {
     fn reload(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
         let fs = self.fs.clone();
         cx.spawn(async move |this, cx| {
+            pub static ZED_STATELESS: LazyLock<bool> =
+                LazyLock::new(|| std::env::var("ZED_STATELESS").map_or(false, |v| !v.is_empty()));
+            if *ZED_STATELESS {
+                return Ok(());
+            }
             fs.create_dir(contexts_dir()).await?;
 
             let mut paths = fs.read_dir(contexts_dir()).await?;

crates/client/src/user.rs 🔗

@@ -765,12 +765,14 @@ impl UserStore {
 
     pub fn current_plan(&self) -> Option<proto::Plan> {
         #[cfg(debug_assertions)]
-        if let Ok(plan) = std::env::var("ZED_SIMULATE_ZED_PRO_PLAN").as_ref() {
+        if let Ok(plan) = std::env::var("ZED_SIMULATE_PLAN").as_ref() {
             return match plan.as_str() {
                 "free" => Some(proto::Plan::Free),
                 "trial" => Some(proto::Plan::ZedProTrial),
                 "pro" => Some(proto::Plan::ZedPro),
-                _ => None,
+                _ => {
+                    panic!("ZED_SIMULATE_PLAN must be one of 'free', 'trial', or 'pro'");
+                }
             };
         }
 

crates/language_models/src/provider/cloud.rs 🔗

@@ -1159,19 +1159,20 @@ impl RenderOnce for ZedAiConfiguration {
 
         let manage_subscription_buttons = if is_pro {
             Button::new("manage_settings", "Manage Subscription")
+                .full_width()
                 .style(ButtonStyle::Tinted(TintColor::Accent))
                 .on_click(|_, _, cx| cx.open_url(&zed_urls::account_url(cx)))
                 .into_any_element()
         } else if self.plan.is_none() || self.eligible_for_trial {
             Button::new("start_trial", "Start 14-day Free Pro Trial")
-                .style(ui::ButtonStyle::Tinted(ui::TintColor::Accent))
                 .full_width()
+                .style(ui::ButtonStyle::Tinted(ui::TintColor::Accent))
                 .on_click(|_, _, cx| cx.open_url(&zed_urls::start_trial_url(cx)))
                 .into_any_element()
         } else {
             Button::new("upgrade", "Upgrade to Pro")
-                .style(ui::ButtonStyle::Tinted(ui::TintColor::Accent))
                 .full_width()
+                .style(ui::ButtonStyle::Tinted(ui::TintColor::Accent))
                 .on_click(|_, _, cx| cx.open_url(&zed_urls::upgrade_to_zed_pro_url(cx)))
                 .into_any_element()
         };