Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4ffd139
Reduce the theme tiles size a bit
danilo-leal Apr 1, 2026
a7aed17
Make the default window size big enough to fit the entire onb page
danilo-leal Apr 1, 2026
9ae62d2
Start with the AI section within the onboarding page
danilo-leal Apr 1, 2026
ae68cb5
Mock up with the Zed agent
danilo-leal Apr 1, 2026
9990cd0
Remove old ai upsell card component
danilo-leal Apr 1, 2026
66d4221
Remove outdated AI grid SVG
danilo-leal Apr 1, 2026
1e65be7
Add new stamps for all plans
danilo-leal Apr 1, 2026
0ee08f4
Refine agent panel onboarding cards design
danilo-leal Apr 1, 2026
fd8cb2f
Adjust edit prediction onboarding modal design
danilo-leal Apr 1, 2026
2a82f0b
Add more design tweaks to the AI setup section in the onboarding
danilo-leal Apr 1, 2026
5914d8a
Add AI section in the welcome page
danilo-leal Apr 1, 2026
0a3234d
Add static new layout banner content
danilo-leal Apr 1, 2026
58747c1
Add the callback infra for the "new layout" button
danilo-leal Apr 1, 2026
e8ba1d2
Merge remote-tracking branch 'origin/main' into agent-v2-onboarding
danilo-leal Apr 1, 2026
d033bc6
Wire up external agents installation from the onboarding page
danilo-leal Apr 2, 2026
c36af99
Display new layout onboarding card for existing users
danilo-leal Apr 3, 2026
e995a7e
Add telemetry events to buttons
danilo-leal Apr 3, 2026
383ffaf
Tweak small visual stuff
danilo-leal Apr 3, 2026
923bc1c
Merge remote-tracking branch 'origin/main' into agent-v2-onboarding
danilo-leal Apr 6, 2026
c500bad
Add Zed Agent to onboarding page
bennetbo Apr 6, 2026
83b5faa
Share code
bennetbo Apr 6, 2026
e8277a4
announcement toast logic
bennetbo Apr 6, 2026
01e9498
Wire up dismissal
bennetbo Apr 6, 2026
7370bc5
Merge remote-tracking branch 'origin/main' into agent-v2-onboarding
danilo-leal Apr 6, 2026
3c0d966
Display "signing in" status for the Zed agent
danilo-leal Apr 6, 2026
81e856c
Wire up the revert button
danilo-leal Apr 7, 2026
f75fc98
Merge remote-tracking branch 'origin/main' into agent-v2-onboarding
danilo-leal Apr 7, 2026
9edede3
Refine copywriting throughout touch points
danilo-leal Apr 7, 2026
920b0f0
Allow user to go to the trial from the onbarding page
danilo-leal Apr 7, 2026
46ec86b
Fix telemetry and main button callback
danilo-leal Apr 7, 2026
894f954
Add illustration to announcement toast
danilo-leal Apr 8, 2026
0651edf
Clean up
danilo-leal Apr 8, 2026
2cea2a5
Merge remote-tracking branch 'origin/main' into agent-v2-onboarding
danilo-leal Apr 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

334 changes: 0 additions & 334 deletions assets/images/ai_grid.svg

This file was deleted.

1 change: 1 addition & 0 deletions assets/images/business_stamp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion assets/images/pro_trial_stamp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion assets/images/pro_user_stamp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions assets/images/student_stamp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
157 changes: 136 additions & 21 deletions crates/agent_ui/src/agent_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ use crate::{DEFAULT_THREAD_TITLE, ui::AcpOnboardingModal};
use crate::{ExpandMessageEditor, ThreadHistoryView};
use crate::{ManageProfiles, ThreadHistoryViewEvent};
use crate::{ThreadHistory, agent_connection_store::AgentConnectionStore};
use agent_settings::AgentSettings;
use agent_settings::{AgentSettings, WindowLayout};
use ai_onboarding::AgentPanelOnboarding;
use anyhow::{Context as _, Result, anyhow};
use client::UserStore;
Expand Down Expand Up @@ -279,7 +279,7 @@ pub fn init(cx: &mut App) {
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
panel.update(cx, |panel, _| {
panel
.on_boarding_upsell_dismissed
.new_user_onboarding_upsell_dismissed
.store(false, Ordering::Release);
});
}
Expand Down Expand Up @@ -797,15 +797,17 @@ pub struct AgentPanel {
_project_subscription: Subscription,
zoomed: bool,
pending_serialization: Option<Task<Result<()>>>,
onboarding: Entity<AgentPanelOnboarding>,
new_user_onboarding: Entity<AgentPanelOnboarding>,
new_user_onboarding_upsell_dismissed: AtomicBool,
agent_layout_onboarding: Entity<ai_onboarding::AgentLayoutOnboarding>,
agent_layout_onboarding_dismissed: AtomicBool,
selected_agent: Agent,
start_thread_in: StartThreadIn,
worktree_creation_status: Option<WorktreeCreationStatus>,
_thread_view_subscription: Option<Subscription>,
_active_thread_focus_subscription: Option<Subscription>,
_worktree_creation_task: Option<Task<()>>,
show_trust_workspace_message: bool,
on_boarding_upsell_dismissed: AtomicBool,
_active_view_observation: Option<Subscription>,
}

Expand Down Expand Up @@ -1039,18 +1041,55 @@ impl AgentPanel {
client,
move |_window, cx| {
weak_panel
.update(cx, |panel, _| {
panel
.on_boarding_upsell_dismissed
.store(true, Ordering::Release);
.update(cx, |panel, cx| {
panel.dismiss_ai_onboarding(cx);
})
.ok();
OnboardingUpsell::set_dismissed(true, cx);
},
cx,
)
});

let weak_panel = cx.entity().downgrade();

let layout = AgentSettings::get_layout(cx);
let is_agent_layout = matches!(layout, WindowLayout::Agent(_));

let agent_layout_onboarding = cx.new(|_cx| ai_onboarding::AgentLayoutOnboarding {
use_agent_layout: Arc::new({
let fs = fs.clone();
let weak_panel = weak_panel.clone();
move |_window, cx| {
AgentSettings::set_layout(WindowLayout::Agent(None), fs.clone(), cx);
weak_panel
.update(cx, |panel, cx| {
panel.dismiss_agent_layout_onboarding(cx);
})
.ok();
}
}),
revert_to_editor_layout: Arc::new({
let fs = fs.clone();
let weak_panel = weak_panel.clone();
move |_window, cx| {
AgentSettings::set_layout(WindowLayout::Editor(None), fs.clone(), cx);
weak_panel
.update(cx, |panel, cx| {
panel.dismiss_agent_layout_onboarding(cx);
})
.ok();
}
}),
dismissed: Arc::new(move |_window, cx| {
weak_panel
.update(cx, |panel, cx| {
panel.dismiss_agent_layout_onboarding(cx);
})
.ok();
}),
is_agent_layout,
});

// Subscribe to extension events to sync agent servers when extensions change
let extension_subscription = if let Some(extension_events) = ExtensionEvents::try_global(cx)
{
Expand Down Expand Up @@ -1115,7 +1154,8 @@ impl AgentPanel {
_project_subscription,
zoomed: false,
pending_serialization: None,
onboarding,
new_user_onboarding: onboarding,
agent_layout_onboarding,
thread_store,
selected_agent: Agent::default(),
start_thread_in: StartThreadIn::default(),
Expand All @@ -1124,7 +1164,10 @@ impl AgentPanel {
_active_thread_focus_subscription: None,
_worktree_creation_task: None,
show_trust_workspace_message: false,
on_boarding_upsell_dismissed: AtomicBool::new(OnboardingUpsell::dismissed(cx)),
new_user_onboarding_upsell_dismissed: AtomicBool::new(OnboardingUpsell::dismissed(cx)),
agent_layout_onboarding_dismissed: AtomicBool::new(AgentLayoutOnboarding::dismissed(
cx,
)),
_active_view_observation: None,
};

Expand Down Expand Up @@ -3973,8 +4016,66 @@ impl AgentPanel {
plan.is_some_and(|plan| plan == Plan::ZedFree) && has_previous_trial
}

fn should_render_onboarding(&self, cx: &mut Context<Self>) -> bool {
if self.on_boarding_upsell_dismissed.load(Ordering::Acquire) {
fn should_render_agent_layout_onboarding(&self, cx: &mut Context<Self>) -> bool {
// We only want to show this for existing users: those who
// have used the agent panel before the sidebar was introduced.
// We can infer that state by users having seen the onboarding
// at one point, but not the agent layout onboarding.

let has_messages = self.active_thread_has_messages(cx);
let is_dismissed = self
.agent_layout_onboarding_dismissed
.load(Ordering::Acquire);

if is_dismissed || has_messages {
return false;
}

match &self.active_view {
ActiveView::Uninitialized | ActiveView::History { .. } | ActiveView::Configuration => {
false
}
ActiveView::AgentThread { .. } => {
let existing_user = self
.new_user_onboarding_upsell_dismissed
.load(Ordering::Acquire);
existing_user
}
}
}

fn render_agent_layout_onboarding(
&self,
_window: &mut Window,
cx: &mut Context<Self>,
) -> Option<impl IntoElement> {
if !self.should_render_agent_layout_onboarding(cx) {
return None;
}

Some(div().child(self.agent_layout_onboarding.clone()))
}

fn dismiss_agent_layout_onboarding(&mut self, cx: &mut Context<Self>) {
self.agent_layout_onboarding_dismissed
.store(true, Ordering::Release);
AgentLayoutOnboarding::set_dismissed(true, cx);
cx.notify();
}

fn dismiss_ai_onboarding(&mut self, cx: &mut Context<Self>) {
self.new_user_onboarding_upsell_dismissed
.store(true, Ordering::Release);
OnboardingUpsell::set_dismissed(true, cx);
self.dismiss_agent_layout_onboarding(cx);
cx.notify();
}

fn should_render_new_user_onboarding(&mut self, cx: &mut Context<Self>) -> bool {
if self
.new_user_onboarding_upsell_dismissed
.load(Ordering::Acquire)
{
return false;
}

Expand All @@ -3986,9 +4087,12 @@ impl AgentPanel {
.and_then(|period| period.0.checked_add_days(chrono::Days::new(1)))
.is_some_and(|date| date < chrono::Utc::now())
{
OnboardingUpsell::set_dismissed(true, cx);
self.on_boarding_upsell_dismissed
.store(true, Ordering::Release);
if !self
.new_user_onboarding_upsell_dismissed
.load(Ordering::Acquire)
{
self.dismiss_ai_onboarding(cx);
}
return false;
}

Expand Down Expand Up @@ -4017,16 +4121,20 @@ impl AgentPanel {
}
}

fn render_onboarding(
&self,
fn render_new_user_onboarding(
&mut self,
_window: &mut Window,
cx: &mut Context<Self>,
) -> Option<impl IntoElement> {
if !self.should_render_onboarding(cx) {
if !self.should_render_new_user_onboarding(cx) {
return None;
}

Some(div().child(self.onboarding.clone()))
Some(
div()
.bg(cx.theme().colors().editor_background)
.child(self.new_user_onboarding.clone()),
)
}

fn render_trial_end_upsell(
Expand Down Expand Up @@ -4220,7 +4328,8 @@ impl Render for AgentPanel {
}))
.child(self.render_toolbar(window, cx))
.children(self.render_workspace_trust_message(cx))
.children(self.render_onboarding(window, cx))
.children(self.render_new_user_onboarding(window, cx))
.children(self.render_agent_layout_onboarding(window, cx))
.map(|parent| match &self.active_view {
ActiveView::Uninitialized => parent,
ActiveView::AgentThread {
Expand Down Expand Up @@ -4311,6 +4420,12 @@ impl Dismissable for OnboardingUpsell {
const KEY: &'static str = "dismissed-trial-upsell";
}

struct AgentLayoutOnboarding;

impl Dismissable for AgentLayoutOnboarding {
const KEY: &'static str = "dismissed-agent-layout-onboarding";
}

struct TrialEndUpsell;

impl Dismissable for TrialEndUpsell {
Expand Down
96 changes: 39 additions & 57 deletions crates/ai_onboarding/src/agent_panel_onboarding_card.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use gpui::{AnyElement, IntoElement, ParentElement, linear_color_stop, linear_gradient};
use smallvec::SmallVec;
use ui::{Vector, VectorName, prelude::*};
use ui::prelude::*;

#[derive(IntoElement)]
pub struct AgentPanelOnboardingCard {
Expand All @@ -23,61 +23,43 @@ impl ParentElement for AgentPanelOnboardingCard {

impl RenderOnce for AgentPanelOnboardingCard {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
div()
.m_2p5()
.p(px(3.))
.elevation_2(cx)
.rounded_lg()
.bg(cx.theme().colors().background.alpha(0.5))
.child(
v_flex()
.relative()
.size_full()
.px_4()
.py_3()
.gap_2()
.border_1()
.rounded(px(5.))
.border_color(cx.theme().colors().text.alpha(0.1))
.overflow_hidden()
.bg(cx.theme().colors().panel_background)
.child(
div()
.opacity(0.5)
.absolute()
.top(px(-8.0))
.right_0()
.w(px(400.))
.h(px(92.))
.rounded_md()
.child(
Vector::new(
VectorName::AiGrid,
rems_from_px(400.),
rems_from_px(92.),
)
.color(Color::Custom(cx.theme().colors().text.alpha(0.32))),
),
)
.child(
div()
.absolute()
.top_0p5()
.right_0p5()
.w(px(660.))
.h(px(401.))
.overflow_hidden()
.rounded_md()
.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),
)),
)
.children(self.children),
)
let color = cx.theme().colors();

div().min_w_0().p_2p5().bg(color.editor_background).child(
div()
.min_w_0()
.p(px(3.))
.rounded_lg()
.elevation_2(cx)
.bg(color.background.opacity(0.5))
.child(
v_flex()
.relative()
.size_full()
.min_w_0()
.px_4()
.py_3()
.gap_2()
.border_1()
.rounded(px(5.))
.border_color(color.text.opacity(0.1))
.bg(color.panel_background)
.overflow_hidden()
.child(
div()
.absolute()
.inset_0()
.size_full()
.rounded_md()
.overflow_hidden()
.bg(linear_gradient(
360.,
linear_color_stop(color.panel_background, 1.0),
linear_color_stop(color.editor_background, 0.45),
)),
)
.children(self.children),
),
)
}
}
Loading
Loading