Restore settings shell tab topology
This commit is contained in:
1 parent
16b5bcb825
commit
f0d37fe56e
4 files changed
+216
-131
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, RunFilter};
|
||||
pub use state::{AppState, InboxFilter, OnboardingStep, RunFilter, SettingsTab};
|
||||
@@ -31,6 +31,17 @@ pub enum RunFilter {
|
||||
Finished,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub enum SettingsTab {
|
||||
#[default]
|
||||
General,
|
||||
Mode,
|
||||
Providers,
|
||||
Shortcuts,
|
||||
Data,
|
||||
Secrets,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct AppState {
|
||||
pub section: SectionId,
|
||||
@@ -44,6 +55,7 @@ pub struct AppState {
|
||||
pub onboarding_execution_mode: &'static str,
|
||||
pub inbox_filter: InboxFilter,
|
||||
pub run_filter: RunFilter,
|
||||
pub settings_tab: SettingsTab,
|
||||
pub message_unread_only: bool,
|
||||
pub status_line: Option<String>,
|
||||
}
|
||||
|
||||
@@ -2,147 +2,219 @@ use super::*;
|
||||
|
||||
impl AppRoot {
|
||||
pub(super) fn render_settings(&self, cx: &mut Context<Self>) -> impl IntoElement + '_ {
|
||||
let current = self.snapshot.current_company();
|
||||
let execution_mode = current
|
||||
.map(|item| item.execution_mode.clone())
|
||||
.unwrap_or_else(|| "ai_ceo".to_string());
|
||||
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_6()
|
||||
.child(
|
||||
div()
|
||||
.w(px(220.0))
|
||||
.p_4()
|
||||
.gap_2()
|
||||
.rounded_lg()
|
||||
.border_1()
|
||||
.border_color(theme::panel_border())
|
||||
.bg(theme::panel())
|
||||
.child(settings_nav_row("General", true))
|
||||
.child(settings_nav_row("Execution mode", false))
|
||||
.child(settings_nav_row("Providers", false))
|
||||
.child(settings_nav_row("Shortcuts", false))
|
||||
.child(settings_nav_row("Data", false))
|
||||
.child(settings_nav_row("Secrets", false)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.gap_6()
|
||||
.child(settings_panel(
|
||||
"General",
|
||||
vec![
|
||||
settings_action_row(
|
||||
"Theme",
|
||||
self.snapshot.theme.clone(),
|
||||
vec![
|
||||
settings_action_button(
|
||||
"System",
|
||||
self.snapshot.theme == "system",
|
||||
cx,
|
||||
|this, cx| this.update_theme("system", cx),
|
||||
)
|
||||
.into_any_element(),
|
||||
settings_action_button(
|
||||
"Light",
|
||||
self.snapshot.theme == "light",
|
||||
cx,
|
||||
|this, cx| this.update_theme("light", cx),
|
||||
)
|
||||
.into_any_element(),
|
||||
settings_action_button(
|
||||
"Dark",
|
||||
self.snapshot.theme == "dark",
|
||||
cx,
|
||||
|this, cx| this.update_theme("dark", cx),
|
||||
)
|
||||
.into_any_element(),
|
||||
],
|
||||
),
|
||||
settings_action_row(
|
||||
"Locale",
|
||||
self.snapshot.locale.clone(),
|
||||
vec![
|
||||
settings_action_button(
|
||||
"EN",
|
||||
self.snapshot.locale == "en",
|
||||
cx,
|
||||
|this, cx| this.update_locale("en", cx),
|
||||
)
|
||||
.into_any_element(),
|
||||
settings_action_button(
|
||||
"ZH",
|
||||
self.snapshot.locale == "zh",
|
||||
cx,
|
||||
|this, cx| this.update_locale("zh", cx),
|
||||
)
|
||||
.into_any_element(),
|
||||
],
|
||||
),
|
||||
settings_readonly_row(
|
||||
"Profile path",
|
||||
self.snapshot.profile_path.clone(),
|
||||
),
|
||||
],
|
||||
))
|
||||
.child(settings_panel(
|
||||
"Execution mode",
|
||||
vec![settings_action_row(
|
||||
"Company mode",
|
||||
execution_mode.clone(),
|
||||
vec![
|
||||
settings_action_button(
|
||||
"AI CEO",
|
||||
execution_mode == "ai_ceo",
|
||||
cx,
|
||||
|this, cx| this.update_execution_mode("ai_ceo", cx),
|
||||
)
|
||||
.into_any_element(),
|
||||
settings_action_button(
|
||||
"AI Co-Founder",
|
||||
execution_mode == "ai_cofounder",
|
||||
cx,
|
||||
|this, cx| this.update_execution_mode("ai_cofounder", cx),
|
||||
)
|
||||
.into_any_element(),
|
||||
],
|
||||
)],
|
||||
))
|
||||
.child(settings_panel(
|
||||
"Providers",
|
||||
vec![settings_readonly_row(
|
||||
"Configured connectors",
|
||||
self.snapshot.connectors.len().to_string(),
|
||||
)],
|
||||
))
|
||||
.child(settings_panel(
|
||||
"Data",
|
||||
vec![
|
||||
settings_readonly_row("Stored profile", "Local SQLite".to_string()),
|
||||
settings_readonly_row("Current company", current_company_name(current)),
|
||||
],
|
||||
)),
|
||||
)
|
||||
.child(settings_tabs(self.state.settings_tab, cx))
|
||||
.child(self.render_active_settings_panel(cx))
|
||||
}
|
||||
|
||||
fn render_active_settings_panel(&self, cx: &mut Context<Self>) -> gpui::AnyElement {
|
||||
match self.state.settings_tab {
|
||||
SettingsTab::General => self.render_general_settings_panel(cx).into_any_element(),
|
||||
SettingsTab::Mode => self.render_mode_settings_panel(cx).into_any_element(),
|
||||
SettingsTab::Providers => self.render_providers_settings_panel().into_any_element(),
|
||||
SettingsTab::Shortcuts => self.render_shortcuts_settings_panel().into_any_element(),
|
||||
SettingsTab::Data => self.render_data_settings_panel().into_any_element(),
|
||||
SettingsTab::Secrets => self.render_secrets_settings_panel().into_any_element(),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_general_settings_panel(&self, cx: &mut Context<Self>) -> impl IntoElement + '_ {
|
||||
settings_panel(
|
||||
"General",
|
||||
vec![
|
||||
settings_action_row(
|
||||
"Theme",
|
||||
self.snapshot.theme.clone(),
|
||||
vec![
|
||||
settings_action_button(
|
||||
"System",
|
||||
self.snapshot.theme == "system",
|
||||
cx,
|
||||
|this, cx| this.update_theme("system", cx),
|
||||
)
|
||||
.into_any_element(),
|
||||
settings_action_button(
|
||||
"Light",
|
||||
self.snapshot.theme == "light",
|
||||
cx,
|
||||
|this, cx| this.update_theme("light", cx),
|
||||
)
|
||||
.into_any_element(),
|
||||
settings_action_button(
|
||||
"Dark",
|
||||
self.snapshot.theme == "dark",
|
||||
cx,
|
||||
|this, cx| this.update_theme("dark", cx),
|
||||
)
|
||||
.into_any_element(),
|
||||
],
|
||||
),
|
||||
settings_action_row(
|
||||
"Locale",
|
||||
self.snapshot.locale.clone(),
|
||||
vec![
|
||||
settings_action_button(
|
||||
"EN",
|
||||
self.snapshot.locale == "en",
|
||||
cx,
|
||||
|this, cx| this.update_locale("en", cx),
|
||||
)
|
||||
.into_any_element(),
|
||||
settings_action_button(
|
||||
"ZH",
|
||||
self.snapshot.locale == "zh",
|
||||
cx,
|
||||
|this, cx| this.update_locale("zh", cx),
|
||||
)
|
||||
.into_any_element(),
|
||||
],
|
||||
),
|
||||
settings_readonly_row("Profile path", self.snapshot.profile_path.clone()),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
fn render_mode_settings_panel(&self, cx: &mut Context<Self>) -> impl IntoElement + '_ {
|
||||
let execution_mode = self
|
||||
.snapshot
|
||||
.current_company()
|
||||
.map(|item| item.execution_mode.clone())
|
||||
.unwrap_or_else(|| "ai_ceo".to_string());
|
||||
settings_panel(
|
||||
"Execution mode",
|
||||
vec![settings_action_row(
|
||||
"Company mode",
|
||||
execution_mode.clone(),
|
||||
vec![
|
||||
settings_action_button("AI CEO", execution_mode == "ai_ceo", cx, |this, cx| {
|
||||
this.update_execution_mode("ai_ceo", cx)
|
||||
})
|
||||
.into_any_element(),
|
||||
settings_action_button(
|
||||
"AI Co-Founder",
|
||||
execution_mode == "ai_cofounder",
|
||||
cx,
|
||||
|this, cx| this.update_execution_mode("ai_cofounder", cx),
|
||||
)
|
||||
.into_any_element(),
|
||||
],
|
||||
)],
|
||||
)
|
||||
}
|
||||
|
||||
fn render_providers_settings_panel(&self) -> impl IntoElement + '_ {
|
||||
settings_panel(
|
||||
"Providers",
|
||||
vec![
|
||||
settings_readonly_row(
|
||||
"Configured connectors",
|
||||
self.snapshot.connectors.len().to_string(),
|
||||
),
|
||||
settings_readonly_row(
|
||||
"Ready connectors",
|
||||
self.snapshot
|
||||
.connectors
|
||||
.iter()
|
||||
.filter(|item| item.status == "ready" || item.auth_state == "ready")
|
||||
.count()
|
||||
.to_string(),
|
||||
),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
fn render_shortcuts_settings_panel(&self) -> impl IntoElement + '_ {
|
||||
settings_panel(
|
||||
"Shortcuts",
|
||||
vec![
|
||||
settings_readonly_row("Open inbox", "g then i".to_string()),
|
||||
settings_readonly_row("Open runs", "g then r".to_string()),
|
||||
settings_readonly_row("Open settings", "g then s".to_string()),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
fn render_data_settings_panel(&self) -> impl IntoElement + '_ {
|
||||
settings_panel(
|
||||
"Data",
|
||||
vec![
|
||||
settings_readonly_row("Stored profile", "Local SQLite".to_string()),
|
||||
settings_readonly_row(
|
||||
"Current company",
|
||||
current_company_name(self.snapshot.current_company()),
|
||||
),
|
||||
settings_readonly_row("Documents", self.snapshot.documents.len().to_string()),
|
||||
settings_readonly_row("Artifacts", self.snapshot.artifacts.len().to_string()),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
fn render_secrets_settings_panel(&self) -> impl IntoElement + '_ {
|
||||
settings_panel(
|
||||
"Secrets",
|
||||
vec![settings_readonly_row(
|
||||
"Connector auth surfaces",
|
||||
self.snapshot.connectors.len().to_string(),
|
||||
)],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn settings_nav_row(label: &'static str, active: bool) -> impl IntoElement {
|
||||
fn settings_tabs(cx_tab: SettingsTab, cx: &mut Context<AppRoot>) -> impl IntoElement {
|
||||
div()
|
||||
.flex()
|
||||
.gap_1()
|
||||
.border_b_1()
|
||||
.border_color(theme::panel_border())
|
||||
.children([
|
||||
settings_tab_button("General", SettingsTab::General, cx_tab, cx).into_any_element(),
|
||||
settings_tab_button("Mode", SettingsTab::Mode, cx_tab, cx).into_any_element(),
|
||||
settings_tab_button("Providers", SettingsTab::Providers, cx_tab, cx).into_any_element(),
|
||||
settings_tab_button("Shortcuts", SettingsTab::Shortcuts, cx_tab, cx).into_any_element(),
|
||||
settings_tab_button("Data", SettingsTab::Data, cx_tab, cx).into_any_element(),
|
||||
settings_tab_button("Secrets", SettingsTab::Secrets, cx_tab, cx).into_any_element(),
|
||||
])
|
||||
}
|
||||
|
||||
fn settings_tab_button(
|
||||
label: &'static str,
|
||||
tab: SettingsTab,
|
||||
active_tab: SettingsTab,
|
||||
cx: &mut Context<AppRoot>,
|
||||
) -> impl IntoElement {
|
||||
let active = tab == active_tab;
|
||||
div()
|
||||
.id(SharedString::from(format!("settings-tab-{label}")))
|
||||
.relative()
|
||||
.px_4()
|
||||
.py_3()
|
||||
.rounded_lg()
|
||||
.bg(if active {
|
||||
theme::selection_fill()
|
||||
} else {
|
||||
theme::panel()
|
||||
})
|
||||
.py_2()
|
||||
.cursor_pointer()
|
||||
.text_sm()
|
||||
.text_color(if active {
|
||||
theme::sidebar_text_active()
|
||||
theme::text_primary()
|
||||
} else {
|
||||
theme::text_secondary()
|
||||
theme::text_muted()
|
||||
})
|
||||
.hover(|style| style.text_color(theme::text_primary()))
|
||||
.on_click(cx.listener(move |this, _, _, cx| {
|
||||
this.state.settings_tab = tab;
|
||||
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 settings_panel(label: &'static str, rows: Vec<gpui::AnyElement>) -> impl IntoElement {
|
||||
|
||||
@@ -14,4 +14,5 @@ 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- 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
|
||||
- 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.
|
||||
Reference in new issue
Block a user