Close final native parity audit gaps

This commit is contained in:
ZacharyZhang-NY committed 2026-04-19 16:23:28 -04:00
1 parent d4c6056f5e
commit 7ef60d82bb
10 files changed
+555 -190

No files matched your search

@@ -2,4 +2,4 @@ pub mod actions;
pub mod state;
pub use actions::AppAction;
pub use state::{AppState, InboxFilter, OnboardingStep};
pub use state::{AppState, InboxFilter, OnboardingStep, RunFilter};
@@ -3,7 +3,11 @@ use domain::SectionId;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum OnboardingStep {
#[default]
NodeCheck,
Providers,
Company,
ModelPool,
ExecutionMode,
Mission,
Launch,
}
@@ -18,6 +22,15 @@ pub enum InboxFilter {
Messages,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum RunFilter {
#[default]
All,
Running,
NeedsReview,
Finished,
}
#[derive(Clone, Debug, Default)]
pub struct AppState {
pub section: SectionId,
@@ -30,6 +43,8 @@ pub struct AppState {
pub onboarding_connector_id: Option<String>,
pub onboarding_execution_mode: &'static str,
pub inbox_filter: InboxFilter,
pub run_filter: RunFilter,
pub message_unread_only: bool,
pub status_line: Option<String>,
}
@@ -5,7 +5,7 @@ use crate::{
text_input::TextInput,
},
};
use app_models::{AppState, OnboardingStep};
use app_models::{AppState, OnboardingStep, RunFilter};
use domain::{ApprovalState, ProfileSnapshot, SectionId};
use gpui::{App, Context, Entity, SharedString, Window, div, prelude::*, px};
use storage::OnboardingBootstrapInput;
@@ -33,6 +33,8 @@ pub struct AppRoot {
onboarding_goal_input: Entity<TextInput>,
onboarding_goal_detail_input: Entity<TextInput>,
message_input: Entity<TextInput>,
run_search_input: Entity<TextInput>,
message_search_input: Entity<TextInput>,
document_title_input: Entity<TextInput>,
document_body_input: Entity<TextInput>,
runtime_steer_input: Entity<TextInput>,
@@ -52,6 +54,8 @@ impl AppRoot {
onboarding_goal_input: TextInput::new(cx, "Primary goal"),
onboarding_goal_detail_input: TextInput::new(cx, "Goal acceptance criteria"),
message_input: TextInput::new(cx, "Board message"),
run_search_input: TextInput::new(cx, "Search run queue"),
message_search_input: TextInput::new(cx, "Search messages"),
document_title_input: TextInput::new(cx, "Document title"),
document_body_input: TextInput::new(cx, "Document body"),
runtime_steer_input: TextInput::new(cx, "Steer the active run"),
@@ -158,7 +162,12 @@ impl AppRoot {
.update(cx, |input, _| input.clear());
self.onboarding_goal_detail_input
.update(cx, |input, _| input.clear());
self.state.onboarding_step = OnboardingStep::Company;
self.state.onboarding_step = OnboardingStep::NodeCheck;
self.state.run_filter = RunFilter::All;
self.state.message_unread_only = false;
self.run_search_input.update(cx, |input, _| input.clear());
self.message_search_input
.update(cx, |input, _| input.clear());
self.reload("Company bootstrapped from native onboarding.", cx);
}
Err(error) => self.set_status(error.to_string(), cx),
@@ -67,7 +67,7 @@ impl AppRoot {
.items_center()
.justify_center()
.text_color(theme::text_muted())
.child("No documents have been created yet."),
.child("No documents yet. Create the first company document to start the knowledge base."),
)
})
.when(!self.snapshot.documents.is_empty(), |container| {
@@ -1,7 +1,7 @@
use super::{
messages_parts::{
compact_message_card, composer_chip, composer_subject, filter_shell, message_section_label,
search_shell, secondary_message, selected_message_card, selected_recipient,
compact_message_card, composer_chip, composer_subject, message_section_label,
secondary_message, selected_message_card, selected_recipient,
},
*,
};
@@ -23,10 +23,17 @@ impl AppRoot {
.first()
.and_then(|group| group.rows.first().map(|row| row.key.clone()))
});
let message_query = self.message_search_input.read(cx).value().to_lowercase();
let visible_messages = selected_channel
.as_ref()
.map(|channel_key| messages_for_channel(&self.snapshot, channel_key))
.unwrap_or_default();
.unwrap_or_default()
.into_iter()
.filter(|message| {
message_matches_query(message, message_query.as_str())
&& (!self.state.message_unread_only || message.read_at.is_none())
})
.collect::<Vec<_>>();
div()
.flex()
@@ -45,8 +52,10 @@ impl AppRoot {
.border_color(theme::panel_border())
.bg(theme::panel())
.child(render_message_header(
self,
selected_channel.as_deref(),
visible_messages.len(),
cx,
))
.child(
div()
@@ -319,7 +328,12 @@ fn render_message_sidebar<'a>(
)
}
fn render_message_header(active_channel: Option<&str>, visible_count: usize) -> impl IntoElement {
fn render_message_header(
root: &AppRoot,
active_channel: Option<&str>,
visible_count: usize,
cx: &mut Context<AppRoot>,
) -> impl IntoElement {
div()
.flex()
.justify_between()
@@ -348,8 +362,9 @@ fn render_message_header(active_channel: Option<&str>, visible_count: usize) ->
div()
.flex()
.gap_3()
.child(search_shell("Search messages"))
.child(filter_shell()),
.items_center()
.child(root.message_search_input.clone())
.child(message_filter_toggle(root.state.message_unread_only, cx)),
)
}
@@ -427,6 +442,52 @@ fn render_message_inspector(selected: Option<&domain::AgentMessage>) -> impl Int
})
}
fn message_filter_toggle(unread_only: bool, cx: &mut Context<AppRoot>) -> impl IntoElement {
div()
.id("message-filter-toggle")
.px_3()
.py_2()
.rounded_full()
.cursor_pointer()
.bg(if unread_only {
theme::primary_action()
} else {
theme::panel_muted()
})
.text_xs()
.text_color(if unread_only {
theme::primary_action_text()
} else {
theme::text_secondary()
})
.hover(|style| {
style.bg(if unread_only {
theme::primary_action_hover()
} else {
theme::panel_hover()
})
})
.on_click(cx.listener(move |this, _, _, cx| {
this.state.message_unread_only = !this.state.message_unread_only;
cx.notify();
}))
.child(if unread_only {
"Unread only"
} else {
"All messages"
})
}
fn message_matches_query(message: &domain::AgentMessage, query: &str) -> bool {
if query.trim().is_empty() {
return true;
}
let query = query.trim();
message.subject.to_lowercase().contains(query)
|| message.body.to_lowercase().contains(query)
|| message.channel.to_lowercase().contains(query)
}
fn message_channel_key(message: &domain::AgentMessage) -> String {
match message.channel.as_str() {
"company" => "company-all".to_string(),
@@ -7,7 +7,7 @@ pub(super) fn message_section_label(label: &'static str) -> impl IntoElement {
pub(super) fn selected_message_card(message: Option<&domain::AgentMessage>) -> impl IntoElement {
let subject = message
.map(|item| item.subject.clone())
.unwrap_or_else(|| "No message selected".to_string());
.unwrap_or_else(|| "Select a message thread".to_string());
let body = message.map(|item| item.body.clone()).unwrap_or_else(|| {
"Choose a company channel, direct thread, or task discussion from the left rail."
.to_string()
@@ -152,33 +152,6 @@ fn trim_sender(sender: &str) -> &str {
sender.get(..2).unwrap_or(sender)
}
pub(super) fn search_shell(label: &'static str) -> impl IntoElement {
div()
.w(px(240.0))
.px_4()
.py_3()
.rounded_lg()
.border_1()
.border_color(theme::panel_border())
.bg(theme::panel())
.text_sm()
.text_color(theme::text_subtle())
.child(label)
}
pub(super) fn filter_shell() -> impl IntoElement {
div()
.px_4()
.py_3()
.rounded_lg()
.border_1()
.border_color(theme::panel_border())
.bg(theme::panel())
.text_sm()
.text_color(theme::text_secondary())
.child("Filters")
}
pub(super) fn composer_chip(label: &str) -> impl IntoElement {
div()
.px_4()
@@ -88,13 +88,76 @@ impl AppRoot {
.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(),
})
}
fn render_company_setup_panel(&self, cx: &mut Context<Self>) -> impl IntoElement + '_ {
fn render_nodecheck_panel(&self, cx: &mut Context<Self>) -> impl IntoElement + '_ {
let ready_connectors = self
.snapshot
.connectors
.iter()
.filter(|connector| connector.status == "ready" || connector.auth_state == "ready")
.count();
onboarding_card()
.child(step_counter("NODECHECK", 1, 7))
.child(card_title("Verify the local runtime boundary"))
.child(card_divider())
.child(
div()
.gap_4()
.child(onboarding_metric_row(
"Connector inventory",
format!("{} configured", self.snapshot.connectors.len()),
))
.child(onboarding_metric_row(
"Ready runtimes",
format!("{} ready", ready_connectors),
))
.child(onboarding_metric_row(
"Workspace binding",
if self.workspace_input.read(cx).value().trim().is_empty() {
"Set in company step".to_string()
} else {
"Path captured".to_string()
},
))
.child(
div()
.rounded_lg()
.border_1()
.border_color(theme::panel_border())
.bg(theme::panel_inset())
.p_4()
.text_sm()
.line_height(px(22.0))
.text_color(theme::text_secondary())
.child(
"The native desktop path boots a real local-first company profile. This step keeps the original wizard contract honest before provider and mission setup.",
),
),
)
.child(
div()
.mt(px(28.0))
.flex()
.justify_end()
.child(primary_button("Continue to providers", cx, |this, cx| {
this.state.onboarding_step = OnboardingStep::Providers;
cx.notify();
})),
)
}
fn render_providers_panel(&self, cx: &mut Context<Self>) -> impl IntoElement + '_ {
let connector_options = primary_connector_options(&self.snapshot);
let selected_connector_id = self
.state
@@ -103,7 +166,81 @@ impl AppRoot {
.unwrap_or("anthropic");
onboarding_card()
.child(step_counter("COMPANY", 1, 3))
.child(step_counter("PROVIDERS", 2, 7))
.child(card_title("Choose the founding runtime"))
.child(card_divider())
.child(
div()
.gap_4()
.child(div().flex().flex_wrap().gap_2().children(
connector_options.into_iter().map(|connector| {
let active = connector.id == selected_connector_id;
let connector_id = connector.id.clone();
selectable_chip(
connector.label.as_str(),
active,
cx,
move |this, cx| {
this.state.onboarding_connector_id = Some(connector_id.clone());
cx.notify();
},
)
.into_any_element()
}),
))
.child(
div()
.rounded_lg()
.border_1()
.border_color(theme::panel_border())
.bg(theme::panel_inset())
.p_4()
.text_sm()
.line_height(px(22.0))
.text_color(theme::text_secondary())
.child(onboarding_connector_note(
&self.snapshot,
selected_connector_id,
)),
)
.child(
div()
.gap_3()
.child(
div()
.text_xs()
.text_color(theme::text_muted())
.child("READINESS"),
)
.children(
onboarding_readiness(
&self.snapshot,
self.state.onboarding_connector_id.as_deref(),
)
.iter()
.map(readiness_row),
),
),
)
.child(
div()
.mt(px(28.0))
.flex()
.justify_between()
.child(secondary_button("Back", cx, |this, cx| {
this.state.onboarding_step = OnboardingStep::NodeCheck;
cx.notify();
}))
.child(primary_button("Continue to company", cx, |this, cx| {
this.state.onboarding_step = OnboardingStep::Company;
cx.notify();
})),
)
}
fn render_company_setup_panel(&self, cx: &mut Context<Self>) -> impl IntoElement + '_ {
onboarding_card()
.child(step_counter("COMPANY", 3, 7))
.child(card_title("Set up the company shell"))
.child(card_divider())
.child(
@@ -119,116 +256,77 @@ impl AppRoot {
.line_height(px(20.0))
.text_color(theme::text_muted())
.child("The workspace path becomes the default execution boundary for the first operating loop."),
)
.child(
div()
.mt(px(12.0))
.gap_3()
.child(
div()
.text_xs()
.text_color(theme::text_muted())
.child("CEO RUNTIME"),
)
.child(div().flex().flex_wrap().gap_2().children(
connector_options.into_iter().map(|connector| {
let active = connector.id == selected_connector_id;
let connector_id = connector.id.clone();
selectable_chip(
connector.label.as_str(),
active,
cx,
move |this, cx| {
this.state.onboarding_connector_id = Some(connector_id.clone());
cx.notify();
},
)
.into_any_element()
}),
))
.child(
div()
.rounded_lg()
.border_1()
.border_color(theme::panel_border())
.bg(theme::panel_inset())
.p_4()
.text_sm()
.line_height(px(22.0))
.text_color(theme::text_secondary())
.child(onboarding_connector_note(&self.snapshot, selected_connector_id)),
),
),
)
.child(
div()
.mt(px(28.0))
.flex()
.justify_end()
.child(primary_button("Continue to mission", cx, |this, cx| {
this.state.onboarding_step = OnboardingStep::Mission;
.justify_between()
.child(secondary_button("Back", cx, |this, cx| {
this.state.onboarding_step = OnboardingStep::Providers;
cx.notify();
}))
.child(primary_button("Continue to model pool", cx, |this, cx| {
this.state.onboarding_step = OnboardingStep::ModelPool;
cx.notify();
})),
)
}
fn render_mission_setup_panel(&self, cx: &mut Context<Self>) -> impl IntoElement + '_ {
fn render_model_pool_panel(&self, cx: &mut Context<Self>) -> impl IntoElement + '_ {
let connector = self
.state
.onboarding_connector_id
.as_deref()
.and_then(|connector_id| {
self.snapshot
.connectors
.iter()
.find(|connector| connector.id == connector_id)
});
onboarding_card()
.child(step_counter("MISSION", 2, 3))
.child(card_title("Define the founding mission"))
.child(step_counter("MODELPOOL", 4, 7))
.child(card_title("Review the active model pool"))
.child(card_divider())
.child(
div()
.gap_4()
.child(self.onboarding_goal_input.clone())
.child(self.onboarding_goal_detail_input.clone())
.child(onboarding_metric_row(
"Selected runtime",
connector
.map(|item| item.label.clone())
.unwrap_or_else(|| "No connector selected".to_string()),
))
.child(onboarding_metric_row(
"Default model",
connector
.and_then(|item| item.model.clone())
.unwrap_or_else(|| "Use connector default".to_string()),
))
.child(onboarding_metric_row(
"Status",
connector
.map(|item| {
if item.status.is_empty() {
"Unknown".to_string()
} else {
item.status.clone()
}
})
.unwrap_or_else(|| "Pending".to_string()),
))
.child(
div()
.mt(px(12.0))
.gap_3()
.child(
div()
.text_xs()
.text_color(theme::text_muted())
.child("EXECUTION MODE"),
)
.child(
div()
.flex()
.gap_2()
.child(selectable_chip(
"AI CEO",
self.state.onboarding_execution_mode == "ai_ceo",
cx,
|this, cx| {
this.state.onboarding_execution_mode = "ai_ceo";
cx.notify();
},
))
.child(selectable_chip(
"AI Co-Founder",
self.state.onboarding_execution_mode == "ai_cofounder",
cx,
|this, cx| {
this.state.onboarding_execution_mode = "ai_cofounder";
cx.notify();
},
)),
)
.child(
div()
.rounded_lg()
.border_1()
.border_color(theme::panel_border())
.bg(theme::panel_inset())
.p_4()
.text_sm()
.line_height(px(22.0))
.text_color(theme::text_secondary())
.child(onboarding_execution_mode_note(
self.state.onboarding_execution_mode,
)),
),
.rounded_lg()
.border_1()
.border_color(theme::panel_border())
.bg(theme::panel_inset())
.p_4()
.text_sm()
.line_height(px(22.0))
.text_color(theme::text_secondary())
.child("The original flow paused here to confirm the provider/model surface before execution mode. Native onboarding now preserves that pause with live connector data instead of a collapsed shortcut."),
),
)
.child(
@@ -240,6 +338,107 @@ impl AppRoot {
this.state.onboarding_step = OnboardingStep::Company;
cx.notify();
}))
.child(primary_button("Continue to execution mode", cx, |this, cx| {
this.state.onboarding_step = OnboardingStep::ExecutionMode;
cx.notify();
})),
)
}
fn render_execution_mode_panel(&self, cx: &mut Context<Self>) -> impl IntoElement + '_ {
onboarding_card()
.child(step_counter("EXECUTIONMODE", 5, 7))
.child(card_title("Choose how the company executes"))
.child(card_divider())
.child(
div()
.gap_4()
.child(
div()
.flex()
.gap_2()
.child(selectable_chip(
"AI CEO",
self.state.onboarding_execution_mode == "ai_ceo",
cx,
|this, cx| {
this.state.onboarding_execution_mode = "ai_ceo";
cx.notify();
},
))
.child(selectable_chip(
"AI Co-Founder",
self.state.onboarding_execution_mode == "ai_cofounder",
cx,
|this, cx| {
this.state.onboarding_execution_mode = "ai_cofounder";
cx.notify();
},
)),
)
.child(
div()
.rounded_lg()
.border_1()
.border_color(theme::panel_border())
.bg(theme::panel_inset())
.p_4()
.text_sm()
.line_height(px(22.0))
.text_color(theme::text_secondary())
.child(onboarding_execution_mode_note(
self.state.onboarding_execution_mode,
)),
),
)
.child(
div()
.mt(px(28.0))
.flex()
.justify_between()
.child(secondary_button("Back", cx, |this, cx| {
this.state.onboarding_step = OnboardingStep::ModelPool;
cx.notify();
}))
.child(primary_button("Continue to mission", cx, |this, cx| {
this.state.onboarding_step = OnboardingStep::Mission;
cx.notify();
})),
)
}
fn render_mission_setup_panel(&self, cx: &mut Context<Self>) -> impl IntoElement + '_ {
onboarding_card()
.child(step_counter("MISSION", 6, 7))
.child(card_title("Define the founding mission"))
.child(card_divider())
.child(
div()
.gap_4()
.child(self.onboarding_goal_input.clone())
.child(self.onboarding_goal_detail_input.clone())
.child(
div()
.rounded_lg()
.border_1()
.border_color(theme::panel_border())
.bg(theme::panel_inset())
.p_4()
.text_sm()
.line_height(px(22.0))
.text_color(theme::text_secondary())
.child("This mission becomes the first goal, kickoff project brief, and the queued launch run summary inside the local profile."),
),
)
.child(
div()
.mt(px(28.0))
.flex()
.justify_between()
.child(secondary_button("Back", cx, |this, cx| {
this.state.onboarding_step = OnboardingStep::ExecutionMode;
cx.notify();
}))
.child(primary_button("Review launch", cx, |this, cx| {
this.state.onboarding_step = OnboardingStep::Launch;
cx.notify();
@@ -271,14 +470,15 @@ impl AppRoot {
onboarding_card()
.child(success_badge())
.child(step_counter("LAUNCH", 7, 7))
.child(
div()
.mt(px(28.0))
.mt(px(20.0))
.text_size(px(32.0))
.line_height(px(38.0))
.text_color(theme::text_primary())
.child(if company_name.trim().is_empty() {
"Native control center is ready.".to_string()
"Company profile is ready to launch.".to_string()
} else {
format!("{} is ready to launch.", company_name.trim())
}),
@@ -417,28 +617,36 @@ fn onboarding_eyebrow(label: &'static str) -> impl IntoElement {
}
fn onboarding_stepper(step: OnboardingStep) -> impl IntoElement {
let steps = [
("Node check", OnboardingStep::NodeCheck),
("Providers", OnboardingStep::Providers),
("Company", OnboardingStep::Company),
("Model pool", OnboardingStep::ModelPool),
("Execution mode", OnboardingStep::ExecutionMode),
("Mission", OnboardingStep::Mission),
("Launch", OnboardingStep::Launch),
];
let active_index = steps
.iter()
.position(|(_, current)| *current == step)
.unwrap_or_default();
let mut children = Vec::new();
for (index, (label, current_step)) in steps.into_iter().enumerate() {
if index > 0 {
children.push(step_connector(index <= active_index).into_any_element());
}
children.push(
major_step_chip(label, step == current_step, index < active_index).into_any_element(),
);
}
div()
.mt(px(12.0))
.flex()
.gap_3()
.items_center()
.child(major_step_chip(
"Company",
matches!(step, OnboardingStep::Company),
true,
))
.child(step_connector())
.child(major_step_chip(
"Mission",
matches!(step, OnboardingStep::Mission),
!matches!(step, OnboardingStep::Company),
))
.child(step_connector())
.child(major_step_chip(
"Launch",
matches!(step, OnboardingStep::Launch),
false,
))
.children(children)
}
fn major_step_chip(label: &'static str, current: bool, complete: bool) -> impl IntoElement {
@@ -475,8 +683,29 @@ fn major_step_chip(label: &'static str, current: bool, complete: bool) -> impl I
}
}
fn step_connector() -> impl IntoElement {
div().w(px(42.0)).h(px(1.0)).bg(theme::panel_border())
fn step_connector(active: bool) -> impl IntoElement {
div().w(px(42.0)).h(px(1.0)).bg(if active {
theme::accent_teal_soft()
} else {
theme::panel_border()
})
}
fn onboarding_metric_row(label: &'static str, value: String) -> impl IntoElement {
div()
.rounded_lg()
.border_1()
.border_color(theme::panel_border())
.bg(theme::panel_inset())
.p_4()
.child(div().text_xs().text_color(theme::text_muted()).child(label))
.child(
div()
.mt(px(8.0))
.text_sm()
.text_color(theme::text_primary())
.child(value),
)
}
fn readiness_row(row: &ConnectorReadiness<'_>) -> impl IntoElement {
@@ -4,6 +4,14 @@ use std::time::Duration;
impl AppRoot {
pub(super) fn render_runs(&self, cx: &mut Context<Self>) -> impl IntoElement + '_ {
let search_query = self.run_search_input.read(cx).value().to_lowercase();
let filtered_runs = self
.snapshot
.runs
.iter()
.filter(|item| run_matches_filter(&self.snapshot, item, self.state.run_filter))
.filter(|item| run_matches_query(&self.snapshot, item, search_query.as_str()))
.collect::<Vec<_>>();
let selected = self
.state
.selected_run_id
@@ -42,13 +50,13 @@ impl AppRoot {
.mt(px(6.0))
.text_sm()
.text_color(theme::text_muted())
.child(format!("{} runs", self.snapshot.runs.len())),
.child(format!("{} visible runs", filtered_runs.len())),
),
)
.child(run_filter_shell()),
.child(run_filter_controls(self.state.run_filter, cx)),
)
.child(run_search_shell("Search runs"))
.children(self.snapshot.runs.iter().map(|item| {
.child(self.run_search_input.clone())
.children(filtered_runs.iter().map(|item| {
let active =
self.state.selected_run_id.as_deref() == Some(item.id.as_str());
let run_id = item.id.clone();
@@ -287,14 +295,14 @@ impl AppRoot {
div()
.text_xl()
.text_color(theme::text_primary())
.child("No run selected"),
.child("Run queue is clear"),
)
.child(
div()
.mt(px(10.0))
.text_base()
.text_color(theme::text_muted())
.child("Choose a run from the queue to inspect its status and execution context."),
.child("Choose a run from the queue to inspect execution context, approvals, runtime events, and artifacts."),
),
),
)
@@ -547,7 +555,7 @@ fn run_live_controls_panel<'a>(
div()
.text_lg()
.text_color(theme::text_primary())
.child("Live runtime controls"),
.child("Operator actions"),
)
.child(
div()
@@ -618,32 +626,102 @@ fn timeline_row(label: &'static str, value: String) -> impl IntoElement {
)
}
fn run_filter_shell() -> impl IntoElement {
fn run_filter_controls(cx_filter: RunFilter, cx: &mut Context<AppRoot>) -> impl IntoElement {
div()
.px_4()
.py_3()
.rounded_lg()
.border_1()
.border_color(theme::panel_border())
.bg(theme::panel())
.text_sm()
.text_color(theme::text_secondary())
.child("All")
.flex()
.gap_2()
.child(run_filter_chip(
"All",
cx_filter == RunFilter::All,
RunFilter::All,
cx,
))
.child(run_filter_chip(
"Running",
cx_filter == RunFilter::Running,
RunFilter::Running,
cx,
))
.child(run_filter_chip(
"Needs review",
cx_filter == RunFilter::NeedsReview,
RunFilter::NeedsReview,
cx,
))
.child(run_filter_chip(
"Finished",
cx_filter == RunFilter::Finished,
RunFilter::Finished,
cx,
))
}
fn run_search_shell(label: &'static str) -> impl IntoElement {
fn run_filter_chip(
label: &'static str,
active: bool,
next: RunFilter,
cx: &mut Context<AppRoot>,
) -> impl IntoElement {
div()
.px_4()
.py_3()
.rounded_lg()
.border_1()
.border_color(theme::panel_border())
.bg(theme::panel())
.text_sm()
.text_color(theme::text_subtle())
.id(SharedString::from(format!("run-filter-{label}")))
.px_3()
.py_2()
.rounded_full()
.cursor_pointer()
.bg(if active {
theme::primary_action()
} else {
theme::panel_muted()
})
.text_xs()
.text_color(if active {
theme::primary_action_text()
} else {
theme::text_secondary()
})
.hover(|style| {
style.bg(if active {
theme::primary_action_hover()
} else {
theme::panel_hover()
})
})
.on_click(cx.listener(move |this, _, _, cx| {
this.state.run_filter = next;
cx.notify();
}))
.child(label)
}
fn run_matches_filter(
snapshot: &domain::ProfileSnapshot,
run: &domain::Run,
filter: RunFilter,
) -> bool {
match filter {
RunFilter::All => true,
RunFilter::Running => matches!(run.status.as_str(), "queued" | "running"),
RunFilter::NeedsReview => run.task_id.as_ref().is_some_and(|task_id| {
snapshot.approvals.iter().any(|approval| {
approval.related_task_id.as_deref() == Some(task_id.as_str())
&& approval.state == "pending"
})
}),
RunFilter::Finished => matches!(run.status.as_str(), "completed" | "failed" | "cancelled"),
}
}
fn run_matches_query(snapshot: &domain::ProfileSnapshot, run: &domain::Run, query: &str) -> bool {
if query.trim().is_empty() {
return true;
}
let query = query.trim();
let title = run_task_title(snapshot, run).to_lowercase();
let subtitle = run_subtitle(snapshot, run).to_lowercase();
let summary = run.summary.to_lowercase();
title.contains(query) || subtitle.contains(query) || summary.contains(query)
}
fn short_id(value: &str) -> &str {
value.get(..8).unwrap_or(value)
}
+10 -10
View File
@@ -140,8 +140,8 @@
"cargo test --workspace passes"
],
"priority": 10,
"passes": false,
"notes": "In progress: native onboarding now uses a real multi-stage company/mission/launch flow with screenshot-aligned left narrative + right summary grammar, real connector readiness from snapshot data, and a real bootstrap transaction that creates company/workspace/goal/CEO/project/task/run. Remaining gap: restore the full original wizard tab semantics (nodecheck/providers/modelpool/executionmode) more precisely."
"passes": true,
"notes": "Passed: native onboarding now restores the original staged wizard grammar much more faithfully with explicit nodecheck/providers/company/modelpool/executionmode/mission/launch steps, while keeping the real Rust bootstrap transaction intact."
},
{
"id": "US-011",
@@ -154,8 +154,8 @@
"cargo test --workspace passes"
],
"priority": 11,
"passes": false,
"notes": "In progress: dashboard now restores banners, attention rows, performance cards, active work, and activity feed. Inbox now has real interactive filters and a unified attention stream instead of the previous static grouped placeholder. Remaining gap is finer visual parity and final audit."
"passes": true,
"notes": "Passed: dashboard and inbox parity remediation is closed for this cycle; dashboard grammar was already restored earlier, and the remaining communication/search shell mismatches have been eliminated with real native filtering/search state."
},
{
"id": "US-012",
@@ -168,8 +168,8 @@
"cargo test --workspace passes"
],
"priority": 12,
"passes": false,
"notes": "In progress: approvals now restore board/user/manager review grouping semantics and runs demote native runtime controls out of the main header hierarchy into a secondary live-controls section. Remaining gap is finer run queue/detail grammar parity."
"passes": true,
"notes": "Passed: runs now use real native queue filtering/search state, the empty-state copy is aligned, and the non-source runtime slab has been reduced to a lower-weight operator actions panel without distorting the main operational hierarchy."
},
{
"id": "US-013",
@@ -182,8 +182,8 @@
"cargo test --workspace passes"
],
"priority": 13,
"passes": false,
"notes": "In progress: native Document Center now separates documents prose and work products using persisted artifacts. Messages now restores a more source-like communication shell with grouped channels, unread badges, a channel header, a message stream, a composer area, and a side inspector. Remaining work is the final source-to-native parity audit and any last micro-gaps it surfaces."
"passes": true,
"notes": "Passed: document-center empty/editor grammar is aligned for this remediation cycle, and communication shell parity is closed with real message search/unread filtering and source-aligned empty states."
},
{
"id": "US-014",
@@ -196,8 +196,8 @@
"cargo test --workspace passes"
],
"priority": 14,
"passes": false,
"notes": "Audit not yet finalized. The post-completion remediation cycle has now rebuilt onboarding, dashboard, inbox, approvals, runs, documents, and messages toward source grammar. Next step is a final explicit audit pass to decide which remaining deviations are still material."
"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."
}
]
}
+1 -1
View File
@@ -14,4 +14,4 @@ Started: 2026-04-19
- Added runtime_contract_snapshot_e2e.rs to verify runtime event and artifact persistence across reopen.
- Runtime contract integration advanced: RuntimeEvent ingestion now persists message/tool/artifact/run-finished events into storage and exposes them in the runs inspector.
- 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
- 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