Restore onboarding shell grammar
This commit is contained in:
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()
|
||||
}
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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.
|
||||
Reference in new issue
Block a user