Restore onboarding shell grammar

This commit is contained in:
ZacharyZhang-NY committed 2026-04-19 17:20:50 -04:00
1 parent 5910447821
commit 88dfe1a01f
5 files changed
+255 -79

No files matched your search

@@ -3,6 +3,7 @@ use domain::SectionId;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum OnboardingStep {
#[default]
Welcome,
NodeCheck,
Providers,
Company,
@@ -6,98 +6,159 @@ use std::time::Duration;
impl AppRoot {
pub(super) fn render_onboarding(&self, cx: &mut Context<Self>) -> impl IntoElement + '_ {
let step = self.state.onboarding_step;
let readiness = onboarding_readiness(
&self.snapshot,
self.state.onboarding_connector_id.as_deref(),
);
div().size_full().bg(theme::window_bg()).child(
div()
.size_full()
.flex()
.child(self.render_onboarding_story(step, &readiness))
.child(div().w(px(12.0)).bg(theme::split_divider()))
.child(self.render_onboarding_panel(step, cx)),
)
if step == OnboardingStep::Welcome {
return self.render_onboarding_welcome(cx).into_any_element();
}
div()
.size_full()
.bg(theme::window_bg())
.child(
div()
.size_full()
.flex()
.child(self.render_onboarding_hero(step))
.child(self.render_onboarding_shell(step, cx)),
)
.into_any_element()
}
fn render_onboarding_story(
&self,
step: OnboardingStep,
readiness: &[ConnectorReadiness<'_>],
) -> impl IntoElement + '_ {
fn render_onboarding_welcome(&self, cx: &mut Context<Self>) -> impl IntoElement + '_ {
div()
.size_full()
.bg(theme::window_bg())
.flex()
.items_center()
.justify_center()
.relative()
.child(
div()
.absolute()
.top_0()
.left_0()
.right_0()
.h(px(160.0))
.bg(theme::window_bg()),
)
.child(
div()
.w(px(680.0))
.px_8()
.flex()
.flex_col()
.items_center()
.text_center()
.child(
div()
.text_size(px(64.0))
.line_height(px(68.0))
.text_color(theme::text_primary())
.child("AgentCompany"),
)
.child(
div()
.mt(px(16.0))
.max_w(px(480.0))
.text_size(px(16.0))
.line_height(px(28.0))
.text_color(theme::text_muted())
.child("Set up your company, connect a runtime, choose an execution mode, and launch the first native operating loop."),
)
.child(div().mt(px(40.0)).child(primary_button("Get Started", cx, |this, cx| {
this.state.onboarding_step = OnboardingStep::NodeCheck;
cx.notify();
})))
.child(
div()
.mt(px(24.0))
.flex()
.gap_3()
.items_center()
.child(div().text_sm().text_color(theme::text_muted()).child("Language"))
.child(selectable_chip("EN", self.snapshot.locale == "en", cx, |this, cx| {
this.update_locale("en", cx);
}))
.child(selectable_chip("ZH", self.snapshot.locale == "zh", cx, |this, cx| {
this.update_locale("zh", cx);
})),
),
)
}
fn render_onboarding_hero(&self, step: OnboardingStep) -> impl IntoElement + '_ {
div()
.flex_1()
.flex()
.items_center()
.justify_center()
.px_8()
.py_12()
.child(
div()
.w(px(620.0))
.gap_8()
.w(px(460.0))
.child(onboarding_eyebrow("AGENTCOMPANY DESKTOP"))
.child(
div()
.mt(px(24.0))
.text_size(px(44.0))
.line_height(px(46.0))
.text_color(theme::text_primary())
.child(
div()
.text_size(px(68.0))
.line_height(px(68.0))
.child("Start a real local-first"),
)
.child(
div()
.text_size(px(68.0))
.line_height(px(68.0))
.child("agent company."),
),
.child("Start a real local-first\nagent company."),
)
.child(
div()
.max_w(px(560.0))
.text_size(px(18.0))
.line_height(px(30.0))
.mt(px(16.0))
.max_w(px(400.0))
.text_sm()
.line_height(px(24.0))
.text_color(theme::text_muted())
.child("Set up your company, connect a runtime, choose an execution mode, and launch the first native operating loop."),
)
.child(onboarding_stepper(step))
.child(
div()
.mt(px(44.0))
.gap_5()
.child(
div()
.text_xs()
.text_color(theme::text_muted())
.child("CONNECTOR READINESS"),
)
.children(readiness.iter().map(|item| readiness_row(item))),
)
.child(div().mt(px(56.0)).child(onboarding_eyebrow("BY ELYDORA"))),
.child(div().mt(px(40.0)).child(onboarding_hero_stepper(step))),
)
}
fn render_onboarding_panel(
fn render_onboarding_shell(
&self,
step: OnboardingStep,
cx: &mut Context<Self>,
) -> impl IntoElement + '_ {
div()
.w(px(620.0))
.flex()
.items_center()
.justify_center()
.child(match step {
OnboardingStep::NodeCheck => self.render_nodecheck_panel(cx).into_any_element(),
OnboardingStep::Providers => self.render_providers_panel(cx).into_any_element(),
OnboardingStep::Company => self.render_company_setup_panel(cx).into_any_element(),
OnboardingStep::ModelPool => self.render_model_pool_panel(cx).into_any_element(),
OnboardingStep::ExecutionMode => {
self.render_execution_mode_panel(cx).into_any_element()
}
OnboardingStep::Mission => self.render_mission_setup_panel(cx).into_any_element(),
OnboardingStep::Launch => self.render_launch_summary_panel(cx).into_any_element(),
})
.w(px(540.0))
.border_l_1()
.border_color(theme::panel_border())
.bg(theme::window_bg())
.child(
div()
.px_10()
.py_10()
.child(onboarding_tab_strip(step, cx))
.child(div().mt(px(24.0)).flex().items_center().child(match step {
OnboardingStep::Welcome => div().into_any_element(),
OnboardingStep::NodeCheck => {
self.render_nodecheck_panel(cx).into_any_element()
}
OnboardingStep::Providers => {
self.render_providers_panel(cx).into_any_element()
}
OnboardingStep::Company => {
self.render_company_setup_panel(cx).into_any_element()
}
OnboardingStep::ModelPool => {
self.render_model_pool_panel(cx).into_any_element()
}
OnboardingStep::ExecutionMode => {
self.render_execution_mode_panel(cx).into_any_element()
}
OnboardingStep::Mission => {
self.render_mission_setup_panel(cx).into_any_element()
}
OnboardingStep::Launch => {
self.render_launch_summary_panel(cx).into_any_element()
}
})),
)
}
fn render_nodecheck_panel(&self, cx: &mut Context<Self>) -> impl IntoElement + '_ {
@@ -149,7 +210,11 @@ impl AppRoot {
div()
.mt(px(28.0))
.flex()
.justify_end()
.justify_between()
.child(secondary_button("Back", cx, |this, cx| {
this.state.onboarding_step = OnboardingStep::Welcome;
cx.notify();
}))
.child(primary_button("Continue to providers", cx, |this, cx| {
this.state.onboarding_step = OnboardingStep::Providers;
cx.notify();
@@ -616,20 +681,79 @@ fn onboarding_eyebrow(label: &'static str) -> impl IntoElement {
.child(div().text_xs().text_color(theme::text_muted()).child(label))
}
fn onboarding_stepper(step: OnboardingStep) -> impl IntoElement {
fn onboarding_tab_strip(step: OnboardingStep, cx: &mut Context<AppRoot>) -> impl IntoElement {
let steps = [
("Node check", OnboardingStep::NodeCheck),
("Node Check", OnboardingStep::NodeCheck),
("Providers", OnboardingStep::Providers),
("Company", OnboardingStep::Company),
("Model pool", OnboardingStep::ModelPool),
("Execution mode", OnboardingStep::ExecutionMode),
("Model Pool", OnboardingStep::ModelPool),
("Mode", OnboardingStep::ExecutionMode),
("Mission", OnboardingStep::Mission),
];
div()
.flex()
.gap_1()
.border_b_1()
.border_color(theme::panel_border())
.children(steps.into_iter().map(|(label, target_step)| {
let active = step == target_step;
let completed = onboarding_step_index(step) > onboarding_step_index(target_step);
onboarding_tab_button(label, target_step, active, completed, cx).into_any_element()
}))
}
fn onboarding_tab_button(
label: &'static str,
target_step: OnboardingStep,
active: bool,
completed: bool,
cx: &mut Context<AppRoot>,
) -> impl IntoElement {
div()
.id(SharedString::from(format!("onboarding-tab-{label}")))
.relative()
.px_3()
.py_2()
.cursor_pointer()
.text_xs()
.text_color(if active {
theme::text_primary()
} else if completed {
theme::accent_teal()
} else {
theme::text_muted()
})
.hover(|style| style.text_color(theme::text_primary()))
.on_click(cx.listener(move |this, _, _, cx| {
this.state.onboarding_step = target_step;
cx.notify();
}))
.child(label)
.when(active, |element| {
element.child(
div()
.absolute()
.left(px(8.0))
.right(px(8.0))
.bottom(px(-1.0))
.h(px(2.0))
.bg(theme::primary_action()),
)
})
}
fn onboarding_hero_stepper(step: OnboardingStep) -> impl IntoElement {
let steps = [
("Node Check", OnboardingStep::NodeCheck),
("Providers", OnboardingStep::Providers),
("Company", OnboardingStep::Company),
("Model Pool", OnboardingStep::ModelPool),
("Mode", OnboardingStep::ExecutionMode),
("Mission", OnboardingStep::Mission),
("Launch", OnboardingStep::Launch),
];
let active_index = steps
.iter()
.position(|(_, current)| *current == step)
.unwrap_or_default();
let active_index = onboarding_step_index(step);
let mut children = Vec::new();
for (index, (label, current_step)) in steps.into_iter().enumerate() {
@@ -649,6 +773,19 @@ fn onboarding_stepper(step: OnboardingStep) -> impl IntoElement {
.children(children)
}
fn onboarding_step_index(step: OnboardingStep) -> usize {
match step {
OnboardingStep::Welcome => 0,
OnboardingStep::NodeCheck => 0,
OnboardingStep::Providers => 1,
OnboardingStep::Company => 2,
OnboardingStep::ModelPool => 3,
OnboardingStep::ExecutionMode => 4,
OnboardingStep::Mission => 5,
OnboardingStep::Launch => 6,
}
}
fn major_step_chip(label: &'static str, current: bool, complete: bool) -> impl IntoElement {
let bg = if complete {
theme::accent_teal_soft()
@@ -753,8 +890,8 @@ fn readiness_dot(tone: ReadinessTone) -> impl IntoElement {
fn onboarding_card() -> gpui::Div {
div()
.w(px(650.0))
.p_10()
.w_full()
.p_8()
.rounded_lg()
.border_1()
.border_color(theme::panel_border())
@@ -32,10 +32,6 @@ pub fn panel_muted() -> Hsla {
rgb(0xf1f4f8).into()
}
pub fn split_divider() -> Hsla {
rgb(0xdbe2ea).into()
}
pub fn text_primary() -> Hsla {
rgb(0x171c25).into()
}
+40
View File
@@ -198,6 +198,46 @@
"priority": 14,
"passes": true,
"notes": "Passed: final source-to-native parity re-audit completed. Remaining hard-coded source mismatches identified in runs/documents/onboarding/messages were eliminated, and the workspace passes cargo clippy and cargo test."
},
{
"id": "US-015",
"title": "Restore onboarding shell topology to original wizard grammar",
"description": "As a first-time operator, I want the native onboarding shell to mirror the original Electron wizard structure so that setup feels identical page-to-page.",
"acceptanceCriteria": [
"Native onboarding uses original wizard-like shell semantics instead of the custom story/panel split",
"Step progress and CTA placement align with the original topography",
"Typecheck passes",
"cargo test --workspace passes"
],
"priority": 15,
"passes": true,
"notes": "Passed: native onboarding now restores the original shell grammar with a dedicated welcome screen, left hero plus fixed right panel topology, top step tabs, and source-aligned back/continue flow while preserving the real Rust bootstrap logic."
},
{
"id": "US-016",
"title": "Restore dashboard topology to original banner-chart-attention grammar",
"description": "As an operator, I want the native dashboard structure to mirror the original Electron dashboard so that information hierarchy is unchanged.",
"acceptanceCriteria": [
"Dashboard restores original banner, metrics, chart, attention, active work, and recent activity section topology",
"Typecheck passes",
"cargo test --workspace passes"
],
"priority": 16,
"passes": false,
"notes": "Reopened after strict source audit: current overview is closer than before but still custom in grammar and component topology."
},
{
"id": "US-017",
"title": "Restore runs detail and queue shell to original operational grammar",
"description": "As an operator, I want the native runs page to preserve the original RunQueue and RunDetail shell structure so that run inspection feels identical.",
"acceptanceCriteria": [
"Runs page restores original queue/detail shell semantics and hierarchy",
"Typecheck passes",
"cargo test --workspace passes"
],
"priority": 17,
"passes": false,
"notes": "Reopened after strict source audit: current runs page keeps real runtime data but still uses custom card grammar."
}
]
}
+2
View File
@@ -16,3 +16,5 @@ Started: 2026-04-19
- Wired a live native runtime session into the GPUI Runs surface. Selected runs now launch Pi Mono through env-configured stdio sidecar settings, runtime tool calls create approval rows, approval decisions send ToolApproval back into the live session, runtime messages project into agent_messages, and run lifecycle still persists into run_events/artifacts/runs.\n- Closed the final native verification matrix. The workspace now has explicit storage-backed e2e coverage for onboarding bootstrap, approvals/documents/runs visibility, settings/workspace mutations, live runtime contract flows, and inbox attention derivation.\n- Reopened Ralph parity work after a direct source audit. The current native app has real runtime/data flows, but several surfaces still diverge from Electron page grammar: onboarding is too collapsed, dashboard misses banner/attention/chart structure, inbox filters are not real, run detail is dominated by native control slabs not present in source, approvals flatten board/user/manager semantics, and documents still lack work-product/editor hierarchy. Added US-010 through US-014 to close these parity gaps honestly.
- Rebuilt native onboarding away from the old static split shell. It now has a real multi-stage company/mission/launch flow, screenshot-aligned narrative/summary composition, real connector readiness derived from snapshot data, and a storage bootstrap transaction that creates company, workspace, goal, CEO, kickoff project, kickoff task, and queued run. Remaining US-010 work is the finer original wizard-tab parity.\n- Rebuilt the native dashboard away from the placeholder metrics/activity shell. It now has real recovery and budget banners, attention rows, a performance section, active work, and recent activity, all driven by persisted local data. Remaining US-011 work is the inbox-side parity and finer dashboard detail matching.\n- Restored more source grammar across inbox, approvals, and runs. Inbox now has real interactive filters driving a unified attention stream; approvals again distinguish board/user/manager review groupings; and run detail no longer lets live runtime controls dominate the main header region.\n- Restored document-center grammar toward the source surface: the native page now separates documents prose from work products and uses persisted artifacts as the work-product list instead of hiding them behind the runs inspector only.\n- Restored the communication shell toward the source product grammar: grouped channels, unread badges, channel header, message stream, composer, and side inspector. Combined with the earlier document-center rewrite, the remaining work is now the final parity audit rather than major page-grammar gaps.\n- Closed the final parity remediation slice. Native onboarding now restores explicit wizard-tab semantics (nodecheck/providers/company/modelpool/executionmode/mission/launch), runs now use real queue filtering/search state instead of static shells, messages now use real search plus unread filtering instead of static filter chrome, and the remaining off-source empty-state copy in runs/documents/messages/onboarding was eliminated. Final cargo clippy and cargo test both pass.\n
- Reopened strict source-parity remediation and corrected the Settings shell topology. Native settings no longer use the off-source left sidebar; they now render as a top tab strip with a single active content panel, matching the original Electron SettingsShell grammar while preserving real theme/locale/execution-mode mutations.
- Completed US-015 onboarding shell remediation. Native onboarding now restores the original high-level wizard grammar: dedicated welcome screen, left hero region, fixed right-side step panel, top tab strip, and source-aligned back/continue flow while keeping the real storage-backed bootstrap path intact.