Add end of service notifications (#30982)

Mikayla Maki , Max Brunsfeld , and Marshall Bowers created

Release Notes:

- N/A

---------

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Co-authored-by: Marshall Bowers <git@maxdeviant.com>

Change summary

crates/agent/src/agent.rs                                       |   1 
crates/agent/src/agent_panel.rs                                 | 339 +-
crates/agent/src/inline_prompt_editor.rs                        |  31 
crates/db/src/kvp.rs                                            |  27 
crates/gpui/src/util.rs                                         |  13 
crates/inline_completion/src/inline_completion.rs               |   7 
crates/inline_completion_button/src/inline_completion_button.rs |  35 
crates/ui/src/components/progress/progress_bar.rs               |  11 
crates/zeta/src/onboarding_modal.rs                             |  64 
9 files changed, 327 insertions(+), 201 deletions(-)

Detailed changes

crates/agent/src/agent_panel.rs 🔗

@@ -3,7 +3,7 @@ use std::path::Path;
 use std::sync::Arc;
 use std::time::Duration;
 
-use db::kvp::KEY_VALUE_STORE;
+use db::kvp::{Dismissable, KEY_VALUE_STORE};
 use markdown::Markdown;
 use serde::{Deserialize, Serialize};
 
@@ -66,8 +66,8 @@ use crate::ui::AgentOnboardingModal;
 use crate::{
     AddContextServer, AgentDiffPane, ContextStore, DeleteRecentlyOpenThread, ExpandMessageEditor,
     Follow, InlineAssistant, NewTextThread, NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff,
-    OpenHistory, ResetTrialUpsell, TextThreadStore, ThreadEvent, ToggleContextPicker,
-    ToggleNavigationMenu, ToggleOptionsMenu,
+    OpenHistory, ResetTrialEndUpsell, ResetTrialUpsell, TextThreadStore, ThreadEvent,
+    ToggleContextPicker, ToggleNavigationMenu, ToggleOptionsMenu,
 };
 
 const AGENT_PANEL_KEY: &str = "agent_panel";
@@ -157,7 +157,10 @@ pub fn init(cx: &mut App) {
                     window.refresh();
                 })
                 .register_action(|_workspace, _: &ResetTrialUpsell, _window, cx| {
-                    set_trial_upsell_dismissed(false, cx);
+                    TrialUpsell::set_dismissed(false, cx);
+                })
+                .register_action(|_workspace, _: &ResetTrialEndUpsell, _window, cx| {
+                    TrialEndUpsell::set_dismissed(false, cx);
                 });
         },
     )
@@ -1932,12 +1935,23 @@ impl AgentPanel {
         }
     }
 
+    fn should_render_trial_end_upsell(&self, cx: &mut Context<Self>) -> bool {
+        if TrialEndUpsell::dismissed() {
+            return false;
+        }
+
+        let plan = self.user_store.read(cx).current_plan();
+        let has_previous_trial = self.user_store.read(cx).trial_started_at().is_some();
+
+        matches!(plan, Some(Plan::Free)) && has_previous_trial
+    }
+
     fn should_render_upsell(&self, cx: &mut Context<Self>) -> bool {
         if !matches!(self.active_view, ActiveView::Thread { .. }) {
             return false;
         }
 
-        if self.hide_trial_upsell || dismissed_trial_upsell() {
+        if self.hide_trial_upsell || TrialUpsell::dismissed() {
             return false;
         }
 
@@ -1983,125 +1997,115 @@ impl AgentPanel {
             move |toggle_state, _window, cx| {
                 let toggle_state_bool = toggle_state.selected();
 
-                set_trial_upsell_dismissed(toggle_state_bool, cx);
+                TrialUpsell::set_dismissed(toggle_state_bool, cx);
             },
         );
 
-        Some(
-            div().p_2().child(
-                v_flex()
+        let contents = div()
+            .size_full()
+            .gap_2()
+            .flex()
+            .flex_col()
+            .child(Headline::new("Build better with Zed Pro").size(HeadlineSize::Small))
+            .child(
+                Label::new("Try Zed Pro for free for 14 days - no credit card required.")
+                    .size(LabelSize::Small),
+            )
+            .child(
+                Label::new(
+                    "Use your own API keys or enable usage-based billing once you hit the cap.",
+                )
+                .color(Color::Muted),
+            )
+            .child(
+                h_flex()
                     .w_full()
-                    .elevation_2(cx)
-                    .rounded(px(8.))
-                    .bg(cx.theme().colors().background.alpha(0.5))
-                    .p(px(3.))
-
+                    .px_neg_1()
+                    .justify_between()
+                    .items_center()
+                    .child(h_flex().items_center().gap_1().child(checkbox))
                     .child(
-                        div()
+                        h_flex()
                             .gap_2()
-                            .flex()
-                            .flex_col()
-                            .size_full()
-                            .border_1()
-                            .rounded(px(5.))
-                            .border_color(cx.theme().colors().text.alpha(0.1))
-                            .overflow_hidden()
-                            .relative()
-                            .bg(cx.theme().colors().panel_background)
-                            .px_4()
-                            .py_3()
-                            .child(
-                                div()
-                                    .absolute()
-                                    .top_0()
-                                    .right(px(-1.0))
-                                    .w(px(441.))
-                                    .h(px(167.))
-                                    .child(
-                                    Vector::new(VectorName::Grid, rems_from_px(441.), rems_from_px(167.)).color(ui::Color::Custom(cx.theme().colors().text.alpha(0.1)))
-                                )
-                            )
                             .child(
-                                div()
-                                    .absolute()
-                                    .top(px(-8.0))
-                                    .right_0()
-                                    .w(px(400.))
-                                    .h(px(92.))
-                                    .child(
-                                    Vector::new(VectorName::AiGrid, rems_from_px(400.), rems_from_px(92.)).color(ui::Color::Custom(cx.theme().colors().text.alpha(0.32)))
-                                )
+                                Button::new("dismiss-button", "Not Now")
+                                    .style(ButtonStyle::Transparent)
+                                    .color(Color::Muted)
+                                    .on_click({
+                                        let agent_panel = cx.entity();
+                                        move |_, _, cx| {
+                                            agent_panel.update(cx, |this, cx| {
+                                                this.hide_trial_upsell = true;
+                                                cx.notify();
+                                            });
+                                        }
+                                    }),
                             )
-                            // .child(
-                            //     div()
-                            //         .absolute()
-                            //         .top_0()
-                            //         .right(px(360.))
-                            //         .size(px(401.))
-                            //         .overflow_hidden()
-                            //         .bg(cx.theme().colors().panel_background)
-                            // )
                             .child(
-                                div()
-                                    .absolute()
-                                    .top_0()
-                                    .right_0()
-                                    .w(px(660.))
-                                    .h(px(401.))
-                                    .overflow_hidden()
-                                    .bg(linear_gradient(
-                                        75.,
-                                        linear_color_stop(cx.theme().colors().panel_background.alpha(0.01), 1.0),
-                                        linear_color_stop(cx.theme().colors().panel_background, 0.45),
-                                    ))
-                            )
-                            .child(Headline::new("Build better with Zed Pro").size(HeadlineSize::Small))
-                            .child(Label::new("Try Zed Pro for free for 14 days - no credit card required.").size(LabelSize::Small))
-                            .child(Label::new("Use your own API keys or enable usage-based billing once you hit the cap.").color(Color::Muted))
+                                Button::new("cta-button", "Start Trial")
+                                    .style(ButtonStyle::Transparent)
+                                    .on_click(|_, _, cx| cx.open_url(&zed_urls::account_url(cx))),
+                            ),
+                    ),
+            );
+
+        Some(self.render_upsell_container(cx, contents))
+    }
+
+    fn render_trial_end_upsell(
+        &self,
+        _window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> Option<impl IntoElement> {
+        if !self.should_render_trial_end_upsell(cx) {
+            return None;
+        }
+
+        Some(
+            self.render_upsell_container(
+                cx,
+                div()
+                    .size_full()
+                    .gap_2()
+                    .flex()
+                    .flex_col()
+                    .child(
+                        Headline::new("Your Zed Pro trial has expired.").size(HeadlineSize::Small),
+                    )
+                    .child(
+                        Label::new("You've been automatically reset to the free plan.")
+                            .size(LabelSize::Small),
+                    )
+                    .child(
+                        h_flex()
+                            .w_full()
+                            .px_neg_1()
+                            .justify_between()
+                            .items_center()
+                            .child(div())
                             .child(
                                 h_flex()
-                                    .w_full()
-                                    .px_neg_1()
-                                    .justify_between()
-                                    .items_center()
-                                    .child(h_flex().items_center().gap_1().child(checkbox))
+                                    .gap_2()
                                     .child(
-                                        h_flex()
-                                            .gap_2()
-                                            .child(
-                                                Button::new("dismiss-button", "Not Now")
-                                                    .style(ButtonStyle::Transparent)
-                                                    .color(Color::Muted)
-                                                    .on_click({
-                                                        let agent_panel = cx.entity();
-                                                        move |_, _, cx| {
-                                                            agent_panel.update(
-                                                                cx,
-                                                                |this, cx| {
-                                                                    let hidden =
-                                                                        this.hide_trial_upsell;
-                                                                    println!("hidden: {}", hidden);
-                                                                    this.hide_trial_upsell = true;
-                                                                    let new_hidden =
-                                                                        this.hide_trial_upsell;
-                                                                    println!(
-                                                                        "new_hidden: {}",
-                                                                        new_hidden
-                                                                    );
-
-                                                                    cx.notify();
-                                                                },
-                                                            );
-                                                        }
-                                                    }),
-                                            )
-                                            .child(
-                                                Button::new("cta-button", "Start Trial")
-                                                    .style(ButtonStyle::Transparent)
-                                                    .on_click(|_, _, cx| {
-                                                        cx.open_url(&zed_urls::account_url(cx))
-                                                    }),
-                                            ),
+                                        Button::new("dismiss-button", "Stay on Free")
+                                            .style(ButtonStyle::Transparent)
+                                            .color(Color::Muted)
+                                            .on_click({
+                                                let agent_panel = cx.entity();
+                                                move |_, _, cx| {
+                                                    agent_panel.update(cx, |_this, cx| {
+                                                        TrialEndUpsell::set_dismissed(true, cx);
+                                                        cx.notify();
+                                                    });
+                                                }
+                                            }),
+                                    )
+                                    .child(
+                                        Button::new("cta-button", "Upgrade to Zed Pro")
+                                            .style(ButtonStyle::Transparent)
+                                            .on_click(|_, _, cx| {
+                                                cx.open_url(&zed_urls::account_url(cx))
+                                            }),
                                     ),
                             ),
                     ),
@@ -2109,6 +2113,91 @@ impl AgentPanel {
         )
     }
 
+    fn render_upsell_container(&self, cx: &mut Context<Self>, content: Div) -> Div {
+        div().p_2().child(
+            v_flex()
+                .w_full()
+                .elevation_2(cx)
+                .rounded(px(8.))
+                .bg(cx.theme().colors().background.alpha(0.5))
+                .p(px(3.))
+                .child(
+                    div()
+                        .gap_2()
+                        .flex()
+                        .flex_col()
+                        .size_full()
+                        .border_1()
+                        .rounded(px(5.))
+                        .border_color(cx.theme().colors().text.alpha(0.1))
+                        .overflow_hidden()
+                        .relative()
+                        .bg(cx.theme().colors().panel_background)
+                        .px_4()
+                        .py_3()
+                        .child(
+                            div()
+                                .absolute()
+                                .top_0()
+                                .right(px(-1.0))
+                                .w(px(441.))
+                                .h(px(167.))
+                                .child(
+                                    Vector::new(
+                                        VectorName::Grid,
+                                        rems_from_px(441.),
+                                        rems_from_px(167.),
+                                    )
+                                    .color(ui::Color::Custom(cx.theme().colors().text.alpha(0.1))),
+                                ),
+                        )
+                        .child(
+                            div()
+                                .absolute()
+                                .top(px(-8.0))
+                                .right_0()
+                                .w(px(400.))
+                                .h(px(92.))
+                                .child(
+                                    Vector::new(
+                                        VectorName::AiGrid,
+                                        rems_from_px(400.),
+                                        rems_from_px(92.),
+                                    )
+                                    .color(ui::Color::Custom(cx.theme().colors().text.alpha(0.32))),
+                                ),
+                        )
+                        // .child(
+                        //     div()
+                        //         .absolute()
+                        //         .top_0()
+                        //         .right(px(360.))
+                        //         .size(px(401.))
+                        //         .overflow_hidden()
+                        //         .bg(cx.theme().colors().panel_background)
+                        // )
+                        .child(
+                            div()
+                                .absolute()
+                                .top_0()
+                                .right_0()
+                                .w(px(660.))
+                                .h(px(401.))
+                                .overflow_hidden()
+                                .bg(linear_gradient(
+                                    75.,
+                                    linear_color_stop(
+                                        cx.theme().colors().panel_background.alpha(0.01),
+                                        1.0,
+                                    ),
+                                    linear_color_stop(cx.theme().colors().panel_background, 0.45),
+                                )),
+                        )
+                        .child(content),
+                ),
+        )
+    }
+
     fn render_active_thread_or_empty_state(
         &self,
         window: &mut Window,
@@ -2827,6 +2916,7 @@ impl Render for AgentPanel {
             .on_action(cx.listener(Self::toggle_zoom))
             .child(self.render_toolbar(window, cx))
             .children(self.render_trial_upsell(window, cx))
+            .children(self.render_trial_end_upsell(window, cx))
             .map(|parent| match &self.active_view {
                 ActiveView::Thread { .. } => parent
                     .relative()
@@ -3014,25 +3104,14 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
     }
 }
 
-const DISMISSED_TRIAL_UPSELL_KEY: &str = "dismissed-trial-upsell";
+struct TrialUpsell;
 
-fn dismissed_trial_upsell() -> bool {
-    db::kvp::KEY_VALUE_STORE
-        .read_kvp(DISMISSED_TRIAL_UPSELL_KEY)
-        .log_err()
-        .map_or(false, |s| s.is_some())
+impl Dismissable for TrialUpsell {
+    const KEY: &'static str = "dismissed-trial-upsell";
 }
 
-fn set_trial_upsell_dismissed(is_dismissed: bool, cx: &mut App) {
-    db::write_and_log(cx, move || async move {
-        if is_dismissed {
-            db::kvp::KEY_VALUE_STORE
-                .write_kvp(DISMISSED_TRIAL_UPSELL_KEY.into(), "1".into())
-                .await
-        } else {
-            db::kvp::KEY_VALUE_STORE
-                .delete_kvp(DISMISSED_TRIAL_UPSELL_KEY.into())
-                .await
-        }
-    })
+struct TrialEndUpsell;
+
+impl Dismissable for TrialEndUpsell {
+    const KEY: &'static str = "dismissed-trial-end-upsell";
 }

crates/agent/src/inline_prompt_editor.rs 🔗

@@ -11,6 +11,7 @@ use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist};
 use crate::{RemoveAllContext, ToggleContextPicker};
 use client::ErrorExt;
 use collections::VecDeque;
+use db::kvp::Dismissable;
 use editor::display_map::EditorMargins;
 use editor::{
     ContextMenuOptions, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, MultiBuffer,
@@ -33,7 +34,6 @@ use ui::utils::WithRemSize;
 use ui::{
     CheckboxWithLabel, IconButtonShape, KeyBinding, Popover, PopoverMenuHandle, Tooltip, prelude::*,
 };
-use util::ResultExt;
 use workspace::Workspace;
 
 pub struct PromptEditor<T> {
@@ -722,7 +722,7 @@ impl<T: 'static> PromptEditor<T> {
                         .child(CheckboxWithLabel::new(
                             "dont-show-again",
                             Label::new("Don't show again"),
-                            if dismissed_rate_limit_notice() {
+                            if RateLimitNotice::dismissed() {
                                 ui::ToggleState::Selected
                             } else {
                                 ui::ToggleState::Unselected
@@ -734,7 +734,7 @@ impl<T: 'static> PromptEditor<T> {
                                     ui::ToggleState::Selected => true,
                                 };
 
-                                set_rate_limit_notice_dismissed(is_dismissed, cx)
+                                RateLimitNotice::set_dismissed(is_dismissed, cx);
                             },
                         ))
                         .child(
@@ -974,7 +974,7 @@ impl PromptEditor<BufferCodegen> {
             CodegenStatus::Error(error) => {
                 if cx.has_flag::<ZedProFeatureFlag>()
                     && error.error_code() == proto::ErrorCode::RateLimitExceeded
-                    && !dismissed_rate_limit_notice()
+                    && !RateLimitNotice::dismissed()
                 {
                     self.show_rate_limit_notice = true;
                     cx.notify();
@@ -1180,27 +1180,10 @@ impl PromptEditor<TerminalCodegen> {
     }
 }
 
-const DISMISSED_RATE_LIMIT_NOTICE_KEY: &str = "dismissed-rate-limit-notice";
+struct RateLimitNotice;
 
-fn dismissed_rate_limit_notice() -> bool {
-    db::kvp::KEY_VALUE_STORE
-        .read_kvp(DISMISSED_RATE_LIMIT_NOTICE_KEY)
-        .log_err()
-        .map_or(false, |s| s.is_some())
-}
-
-fn set_rate_limit_notice_dismissed(is_dismissed: bool, cx: &mut App) {
-    db::write_and_log(cx, move || async move {
-        if is_dismissed {
-            db::kvp::KEY_VALUE_STORE
-                .write_kvp(DISMISSED_RATE_LIMIT_NOTICE_KEY.into(), "1".into())
-                .await
-        } else {
-            db::kvp::KEY_VALUE_STORE
-                .delete_kvp(DISMISSED_RATE_LIMIT_NOTICE_KEY.into())
-                .await
-        }
-    })
+impl Dismissable for RateLimitNotice {
+    const KEY: &'static str = "dismissed-rate-limit-notice";
 }
 
 pub enum CodegenStatus {

crates/db/src/kvp.rs 🔗

@@ -1,6 +1,8 @@
+use gpui::App;
 use sqlez_macros::sql;
+use util::ResultExt as _;
 
-use crate::{define_connection, query};
+use crate::{define_connection, query, write_and_log};
 
 define_connection!(pub static ref KEY_VALUE_STORE: KeyValueStore<()> =
     &[sql!(
@@ -11,6 +13,29 @@ define_connection!(pub static ref KEY_VALUE_STORE: KeyValueStore<()> =
     )];
 );
 
+pub trait Dismissable {
+    const KEY: &'static str;
+
+    fn dismissed() -> bool {
+        KEY_VALUE_STORE
+            .read_kvp(Self::KEY)
+            .log_err()
+            .map_or(false, |s| s.is_some())
+    }
+
+    fn set_dismissed(is_dismissed: bool, cx: &mut App) {
+        write_and_log(cx, move || async move {
+            if is_dismissed {
+                KEY_VALUE_STORE
+                    .write_kvp(Self::KEY.into(), "1".into())
+                    .await
+            } else {
+                KEY_VALUE_STORE.delete_kvp(Self::KEY.into()).await
+            }
+        })
+    }
+}
+
 impl KeyValueStore {
     query! {
         pub fn read_kvp(key: &str) -> Result<Option<String>> {

crates/gpui/src/util.rs 🔗

@@ -27,6 +27,19 @@ pub trait FluentBuilder {
         self.map(|this| if condition { then(this) } else { this })
     }
 
+    /// Conditionally modify self with the given closure.
+    fn when_else(
+        self,
+        condition: bool,
+        then: impl FnOnce(Self) -> Self,
+        else_fn: impl FnOnce(Self) -> Self,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        self.map(|this| if condition { then(this) } else { else_fn(this) })
+    }
+
     /// Conditionally unwrap and modify self with the given closure, if the given option is Some.
     fn when_some<T>(self, option: Option<T>, then: impl FnOnce(Self, T) -> Self) -> Self
     where

crates/inline_completion/src/inline_completion.rs 🔗

@@ -83,6 +83,13 @@ impl EditPredictionUsage {
 
         Ok(Self { limit, amount })
     }
+
+    pub fn over_limit(&self) -> bool {
+        match self.limit {
+            UsageLimit::Limited(limit) => self.amount >= limit,
+            UsageLimit::Unlimited => false,
+        }
+    }
 }
 
 pub trait EditPredictionProvider: 'static + Sized {

crates/inline_completion_button/src/inline_completion_button.rs 🔗

@@ -33,7 +33,7 @@ use workspace::{
     StatusItemView, Toast, Workspace, create_and_open_local_file, item::ItemHandle,
     notifications::NotificationId,
 };
-use zed_actions::OpenBrowser;
+use zed_actions::{OpenBrowser, OpenZedUrl};
 use zed_llm_client::UsageLimit;
 use zeta::RateCompletions;
 
@@ -277,14 +277,31 @@ impl Render for InlineCompletionButton {
                     );
                 }
 
+                let mut over_limit = false;
+
+                if let Some(usage) = self
+                    .edit_prediction_provider
+                    .as_ref()
+                    .and_then(|provider| provider.usage(cx))
+                {
+                    over_limit = usage.over_limit()
+                }
+
                 let show_editor_predictions = self.editor_show_predictions;
 
                 let icon_button = IconButton::new("zed-predict-pending-button", zeta_icon)
                     .shape(IconButtonShape::Square)
-                    .when(enabled && !show_editor_predictions, |this| {
-                        this.indicator(Indicator::dot().color(Color::Muted))
+                    .when(
+                        enabled && (!show_editor_predictions || over_limit),
+                        |this| {
+                            this.indicator(Indicator::dot().when_else(
+                                over_limit,
+                                |dot| dot.color(Color::Error),
+                                |dot| dot.color(Color::Muted),
+                            ))
                             .indicator_border_color(Some(cx.theme().colors().status_bar_background))
-                    })
+                        },
+                    )
                     .when(!self.popover_menu_handle.is_deployed(), |element| {
                         element.tooltip(move |window, cx| {
                             if enabled {
@@ -440,6 +457,16 @@ impl InlineCompletionButton {
                     },
                     move |_, cx| cx.open_url(&zed_urls::account_url(cx)),
                 )
+                .when(usage.over_limit(), |menu| -> ContextMenu {
+                    menu.entry("Subscribe to increase your limit", None, |window, cx| {
+                        window.dispatch_action(
+                            Box::new(OpenZedUrl {
+                                url: zed_urls::account_url(cx),
+                            }),
+                            cx,
+                        );
+                    })
+                })
                 .separator();
         }
 

crates/ui/src/components/progress/progress_bar.rs 🔗

@@ -13,6 +13,7 @@ pub struct ProgressBar {
     value: f32,
     max_value: f32,
     bg_color: Hsla,
+    over_color: Hsla,
     fg_color: Hsla,
 }
 
@@ -23,6 +24,7 @@ impl ProgressBar {
             value,
             max_value,
             bg_color: cx.theme().colors().background,
+            over_color: cx.theme().status().error,
             fg_color: cx.theme().status().info,
         }
     }
@@ -50,6 +52,12 @@ impl ProgressBar {
         self.fg_color = color;
         self
     }
+
+    /// Sets the over limit color of the progress bar.
+    pub fn over_color(mut self, color: Hsla) -> Self {
+        self.over_color = color;
+        self
+    }
 }
 
 impl RenderOnce for ProgressBar {
@@ -74,7 +82,8 @@ impl RenderOnce for ProgressBar {
                 div()
                     .h_full()
                     .rounded_full()
-                    .bg(self.fg_color)
+                    .when(self.value > self.max_value, |div| div.bg(self.over_color))
+                    .when(self.value <= self.max_value, |div| div.bg(self.fg_color))
                     .w(relative(fill_width)),
             )
     }

crates/zeta/src/onboarding_modal.rs 🔗

@@ -2,7 +2,7 @@ use std::{sync::Arc, time::Duration};
 
 use crate::{ZED_PREDICT_DATA_COLLECTION_CHOICE, onboarding_event};
 use anyhow::Context as _;
-use client::{Client, UserStore, zed_urls};
+use client::{Client, UserStore};
 use db::kvp::KEY_VALUE_STORE;
 use fs::Fs;
 use gpui::{
@@ -384,47 +384,29 @@ impl Render for ZedPredictModal {
             } else {
                 (IconName::ChevronDown, IconName::ChevronUp)
             };
+            let plan = plan.unwrap_or(proto::Plan::Free);
 
             base.child(Label::new(copy).color(Color::Muted))
-                .child(h_flex().map(|parent| {
-                    if let Some(plan) = plan {
-                        parent.child(
-                            Checkbox::new("plan", ToggleState::Selected)
-                                .fill()
-                                .disabled(true)
-                                .label(format!(
-                                    "You get {} edit predictions through your {}.",
-                                    if plan == proto::Plan::Free {
-                                        "2,000"
-                                    } else {
-                                        "unlimited"
-                                    },
-                                    match plan {
-                                        proto::Plan::Free => "Zed Free plan",
-                                        proto::Plan::ZedPro => "Zed Pro plan",
-                                        proto::Plan::ZedProTrial => "Zed Pro trial",
-                                    }
-                                )),
-                        )
-                    } else {
-                        parent
-                            .child(
-                                Checkbox::new("plan-required", ToggleState::Unselected)
-                                    .fill()
-                                    .disabled(true)
-                                    .label("To get started with edit prediction"),
-                            )
-                            .child(
-                                Button::new("subscribe", "choose a plan")
-                                    .icon(IconName::ArrowUpRight)
-                                    .icon_size(IconSize::Indicator)
-                                    .icon_color(Color::Muted)
-                                    .on_click(|_event, _window, cx| {
-                                        cx.open_url(&zed_urls::account_url(cx));
-                                    }),
-                            )
-                    }
-                }))
+                .child(
+                    h_flex().child(
+                        Checkbox::new("plan", ToggleState::Selected)
+                            .fill()
+                            .disabled(true)
+                            .label(format!(
+                                "You get {} edit predictions through your {}.",
+                                if plan == proto::Plan::Free {
+                                    "2,000"
+                                } else {
+                                    "unlimited"
+                                },
+                                match plan {
+                                    proto::Plan::Free => "Zed Free plan",
+                                    proto::Plan::ZedPro => "Zed Pro plan",
+                                    proto::Plan::ZedProTrial => "Zed Pro trial",
+                                }
+                            )),
+                    ),
+                )
                 .child(
                     h_flex()
                         .child(
@@ -495,7 +477,7 @@ impl Render for ZedPredictModal {
                         .w_full()
                         .child(
                             Button::new("accept-tos", "Enable Edit Prediction")
-                                .disabled(plan.is_none() || !self.terms_of_service)
+                                .disabled(!self.terms_of_service)
                                 .style(ButtonStyle::Tinted(TintColor::Accent))
                                 .full_width()
                                 .on_click(cx.listener(Self::accept_and_enable)),