commit 468e1c1895b59a4a45d330a32afa08bd58384094
parent eabed322efbe6f51fd208490d64b43aff401e493
Author: triesap <tyson@radroots.org>
Date: Fri, 17 Apr 2026 17:05:38 +0000
app: add home shell metadata layout
- replace the centered placeholder with a split home shell and runtime metadata card
- add shared ui text helpers that localize runtime metadata rows from AppRuntimeSnapshot
- expand the app text catalog with home metadata and runtime label keys
- wire the launcher through a dedicated home window view and remove the old placeholder surface
Diffstat:
11 files changed, 312 insertions(+), 34 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -4259,6 +4259,7 @@ name = "radroots_app_ui"
version = "0.1.0"
dependencies = [
"gpui",
+ "radroots_app_core",
"radroots_app_i18n",
]
diff --git a/crates/launchers/desktop/src/app.rs b/crates/launchers/desktop/src/app.rs
@@ -1,15 +1,9 @@
use gpui::{AppContext, Application, WindowOptions, px, size};
use radroots_app_core::{APP_PROJECTION_SOURCE, AppBuildIdentity, AppRuntimeSnapshot};
use radroots_app_i18n::select_locale_from_host;
-use radroots_app_ui::{APP_UI_THEME, PlaceholderView};
+use radroots_app_ui::APP_UI_THEME;
-fn titlebar_options() -> gpui::TitlebarOptions {
- gpui::TitlebarOptions {
- title: None,
- appears_transparent: true,
- ..Default::default()
- }
-}
+use crate::window::{HomeView, home_titlebar_options};
pub fn launch() {
let snapshot = AppRuntimeSnapshot::capture(build_identity());
@@ -34,10 +28,10 @@ pub fn launch() {
px(APP_UI_THEME.windows.home_min_width_px),
px(APP_UI_THEME.windows.home_min_height_px),
)),
- titlebar: Some(titlebar_options()),
+ titlebar: Some(home_titlebar_options()),
..Default::default()
},
- |_, cx| cx.new(|_| PlaceholderView),
+ |_, cx| cx.new(|_| HomeView::new(snapshot.clone())),
)
.expect("main radroots app window should open");
diff --git a/crates/launchers/desktop/src/lib.rs b/crates/launchers/desktop/src/lib.rs
@@ -1,6 +1,7 @@
#![forbid(unsafe_code)]
mod app;
+mod window;
pub fn run() {
app::launch();
diff --git a/crates/launchers/desktop/src/window.rs b/crates/launchers/desktop/src/window.rs
@@ -0,0 +1,113 @@
+use gpui::{
+ Context, InteractiveElement, IntoElement, ParentElement, Render, StatefulInteractiveElement,
+ Styled, Window, div, px, rgb,
+};
+use radroots_app_core::AppRuntimeSnapshot;
+use radroots_app_i18n::AppTextKey;
+use radroots_app_ui::{
+ APP_UI_THEME, LabelValueRow, app_card, app_shared_text, app_window_shell, label_value_list,
+ runtime_metadata_rows, section_divider, utility_title_row,
+};
+
+pub fn home_titlebar_options() -> gpui::TitlebarOptions {
+ gpui::TitlebarOptions {
+ title: None,
+ appears_transparent: true,
+ ..Default::default()
+ }
+}
+
+pub struct HomeView {
+ snapshot: AppRuntimeSnapshot,
+ metadata_rows: Vec<LabelValueRow>,
+}
+
+impl HomeView {
+ pub fn new(snapshot: AppRuntimeSnapshot) -> Self {
+ let metadata_rows = runtime_metadata_rows(&snapshot);
+
+ Self {
+ snapshot,
+ metadata_rows,
+ }
+ }
+}
+
+impl Render for HomeView {
+ fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
+ app_window_shell(
+ APP_UI_THEME.surfaces.window_background,
+ div()
+ .size_full()
+ .overflow_hidden()
+ .flex()
+ .child(
+ div()
+ .h_full()
+ .w(px(APP_UI_THEME.layout.home_sidebar_width_px))
+ .bg(rgb(APP_UI_THEME.surfaces.panel_background))
+ .p(px(APP_UI_THEME.layout.home_window_padding_px))
+ .flex()
+ .flex_col()
+ .justify_between()
+ .child(
+ div()
+ .flex()
+ .flex_col()
+ .gap(px(APP_UI_THEME.layout.home_stack_gap_px))
+ .child(
+ div()
+ .text_size(px(APP_UI_THEME.typography.brand_text_px))
+ .font_weight(gpui::FontWeight::SEMIBOLD)
+ .text_color(rgb(APP_UI_THEME.text.primary))
+ .child(app_shared_text(AppTextKey::HomeBrand)),
+ )
+ .child(
+ div()
+ .text_size(px(APP_UI_THEME.typography.body_text_px))
+ .text_color(rgb(APP_UI_THEME.text.secondary))
+ .child(app_shared_text(AppTextKey::HomeTitle)),
+ ),
+ )
+ .child(
+ div()
+ .text_size(px(APP_UI_THEME.typography.utility_title_text_px))
+ .text_color(rgb(APP_UI_THEME.text.secondary))
+ .child(format!("v{}", self.snapshot.host.app_version)),
+ ),
+ )
+ .child(
+ div()
+ .h_full()
+ .w(px(APP_UI_THEME.layout.divider_thickness_px))
+ .bg(rgb(APP_UI_THEME.surfaces.divider)),
+ )
+ .child(
+ div()
+ .flex_1()
+ .h_full()
+ .bg(rgb(APP_UI_THEME.surfaces.window_background))
+ .overflow_hidden()
+ .child(
+ div()
+ .id("home-shell-scroll")
+ .size_full()
+ .overflow_y_scroll()
+ .p(px(APP_UI_THEME.layout.home_window_padding_px))
+ .child(app_card(
+ div()
+ .w_full()
+ .flex()
+ .flex_col()
+ .gap(px(APP_UI_THEME.layout.home_stack_gap_px))
+ .child(utility_title_row(app_shared_text(
+ AppTextKey::HomeMetadataTitle,
+ )))
+ .child(section_divider())
+ .child(label_value_list(self.metadata_rows.clone())),
+ )),
+ ),
+ ),
+ )
+ }
+}
diff --git a/crates/shared/i18n/src/keys.rs b/crates/shared/i18n/src/keys.rs
@@ -23,5 +23,28 @@ define_app_text_keys! {
AppName => "app.name",
HomeBrand => "home.brand",
HomeTitle => "home.title",
+ HomeMetadataTitle => "home.metadata_title",
SettingsTitle => "settings.title",
+ MetadataCorePackage => "metadata.core_package",
+ MetadataCoreVersion => "metadata.core_version",
+ MetadataCoreAuthors => "metadata.core_authors",
+ MetadataRustEdition => "metadata.rust_edition",
+ MetadataRustToolchain => "metadata.rust_toolchain",
+ MetadataTargetTriple => "metadata.target_triple",
+ MetadataBuildProfile => "metadata.build_profile",
+ MetadataProjection => "metadata.projection",
+ MetadataGitCommit => "metadata.git_commit",
+ MetadataAppName => "metadata.app_name",
+ MetadataAppId => "metadata.app_id",
+ MetadataAppVersion => "metadata.app_version",
+ MetadataAppBuild => "metadata.app_build",
+ MetadataPlatform => "metadata.platform",
+ MetadataOperatingSystem => "metadata.operating_system",
+ MetadataHostLocale => "metadata.host_locale",
+ MetadataRuntimeOrigin => "metadata.runtime_origin",
+ MetadataRuntimeMode => "metadata.runtime_mode",
+ MetadataRunId => "metadata.run_id",
+ ValueNone => "value.none",
+ ValueRuntimeModeDevelopment => "value.runtime_mode.development",
+ ValueRuntimeModeProduction => "value.runtime_mode.production",
}
diff --git a/crates/shared/ui/Cargo.toml b/crates/shared/ui/Cargo.toml
@@ -9,6 +9,7 @@ publish = false
[dependencies]
gpui.workspace = true
+radroots_app_core.workspace = true
radroots_app_i18n.workspace = true
[lints]
diff --git a/crates/shared/ui/src/lib.rs b/crates/shared/ui/src/lib.rs
@@ -1,14 +1,14 @@
#![forbid(unsafe_code)]
-mod placeholder;
mod primitives;
+mod text;
mod theme;
-pub use placeholder::PlaceholderView;
pub use primitives::{
LabelValueRow, app_card, app_center_stage, app_window_shell, label_value_list, section_divider,
utility_title_row,
};
+pub use text::{app_shared_text, runtime_metadata_rows};
pub use theme::{
APP_UI_THEME, AppLayoutTokens, AppSurfaceTokens, AppTextTokens, AppTypographyTokens,
AppUiTheme, AppWindowTokens,
diff --git a/crates/shared/ui/src/placeholder.rs b/crates/shared/ui/src/placeholder.rs
@@ -1,21 +0,0 @@
-use gpui::{Context, FontWeight, IntoElement, ParentElement, Render, Styled, Window, div, px, rgb};
-use radroots_app_i18n::{AppTextKey, app_text};
-
-use crate::{APP_UI_THEME, app_center_stage, app_window_shell};
-
-pub struct PlaceholderView;
-
-impl Render for PlaceholderView {
- fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
- app_window_shell(
- APP_UI_THEME.surfaces.window_background,
- app_center_stage(
- div()
- .text_size(px(APP_UI_THEME.typography.brand_text_px))
- .font_weight(FontWeight::SEMIBOLD)
- .text_color(rgb(APP_UI_THEME.text.primary))
- .child(app_text(AppTextKey::HomeBrand)),
- ),
- )
- }
-}
diff --git a/crates/shared/ui/src/text.rs b/crates/shared/ui/src/text.rs
@@ -0,0 +1,141 @@
+use gpui::SharedString;
+use radroots_app_core::{AppRuntimeMode, AppRuntimeSnapshot};
+use radroots_app_i18n::{AppTextKey, app_text};
+
+use crate::LabelValueRow;
+
+pub fn app_shared_text(key: AppTextKey) -> SharedString {
+ app_text(key).into()
+}
+
+pub fn runtime_metadata_rows(snapshot: &AppRuntimeSnapshot) -> Vec<LabelValueRow> {
+ vec![
+ metadata_row(
+ AppTextKey::MetadataCorePackage,
+ snapshot.core.package_name.clone(),
+ ),
+ metadata_row(
+ AppTextKey::MetadataCoreVersion,
+ snapshot.core.package_version.clone(),
+ ),
+ metadata_row(
+ AppTextKey::MetadataCoreAuthors,
+ snapshot.core.package_authors.clone(),
+ ),
+ metadata_row(
+ AppTextKey::MetadataRustEdition,
+ snapshot.core.rust_edition.clone(),
+ ),
+ metadata_row(
+ AppTextKey::MetadataRustToolchain,
+ snapshot.core.rust_toolchain.clone(),
+ ),
+ metadata_row(
+ AppTextKey::MetadataTargetTriple,
+ snapshot.build.target_triple.clone(),
+ ),
+ metadata_row(
+ AppTextKey::MetadataBuildProfile,
+ snapshot.build.build_profile.clone(),
+ ),
+ metadata_row(
+ AppTextKey::MetadataProjection,
+ snapshot.build.projection_source.clone(),
+ ),
+ metadata_row(
+ AppTextKey::MetadataGitCommit,
+ snapshot
+ .build
+ .git_commit
+ .clone()
+ .unwrap_or_else(|| app_text(AppTextKey::ValueNone)),
+ ),
+ metadata_row(AppTextKey::MetadataAppName, snapshot.host.app_name.clone()),
+ metadata_row(
+ AppTextKey::MetadataAppId,
+ snapshot.host.app_identifier.clone(),
+ ),
+ metadata_row(
+ AppTextKey::MetadataAppVersion,
+ snapshot.host.app_version.clone(),
+ ),
+ metadata_row(
+ AppTextKey::MetadataAppBuild,
+ snapshot.host.app_build.clone(),
+ ),
+ metadata_row(
+ AppTextKey::MetadataPlatform,
+ snapshot.host.platform_name.clone(),
+ ),
+ metadata_row(
+ AppTextKey::MetadataOperatingSystem,
+ snapshot.host.operating_system.clone(),
+ ),
+ metadata_row(
+ AppTextKey::MetadataHostLocale,
+ snapshot.host.host_locale.clone(),
+ ),
+ metadata_row(
+ AppTextKey::MetadataRuntimeOrigin,
+ snapshot.host.runtime_origin.clone(),
+ ),
+ metadata_row(
+ AppTextKey::MetadataRuntimeMode,
+ runtime_mode_text(&snapshot.runtime_mode),
+ ),
+ metadata_row(AppTextKey::MetadataRunId, snapshot.run_id.clone()),
+ ]
+}
+
+fn metadata_row(label: AppTextKey, value: impl Into<String>) -> LabelValueRow {
+ LabelValueRow::new(app_shared_text(label), value.into())
+}
+
+fn runtime_mode_text(mode: &AppRuntimeMode) -> String {
+ let key = match mode {
+ AppRuntimeMode::Development => AppTextKey::ValueRuntimeModeDevelopment,
+ AppRuntimeMode::Production => AppTextKey::ValueRuntimeModeProduction,
+ };
+ app_text(key)
+}
+
+#[cfg(test)]
+mod tests {
+ use radroots_app_core::{
+ APP_PROJECTION_SOURCE, AppBuildIdentity, AppRuntimeCapture, AppRuntimeMode,
+ AppRuntimeSnapshot,
+ };
+
+ use super::runtime_metadata_rows;
+
+ #[test]
+ fn runtime_metadata_rows_use_localized_labels() {
+ let snapshot = AppRuntimeSnapshot::from_capture(
+ AppBuildIdentity {
+ package_name: "radroots_app".to_owned(),
+ package_version: "0.1.0".to_owned(),
+ build_profile: "debug".to_owned(),
+ target_triple: "aarch64-apple-darwin".to_owned(),
+ projection_source: APP_PROJECTION_SOURCE.to_owned(),
+ git_commit: None,
+ },
+ AppRuntimeMode::Development,
+ AppRuntimeCapture {
+ host_locale: "en_US.UTF-8".to_owned(),
+ operating_system: "macos".to_owned(),
+ run_id: "run-development-123-pid456".to_owned(),
+ },
+ );
+
+ let rows = runtime_metadata_rows(&snapshot);
+
+ assert!(
+ rows.iter()
+ .any(|row| row.label == "runtime mode" && row.value == "development")
+ );
+ assert!(
+ rows.iter()
+ .any(|row| row.label == "app id" && row.value == "org.radroots.app")
+ );
+ }
+}
diff --git a/crates/shared/ui/src/theme.rs b/crates/shared/ui/src/theme.rs
@@ -43,6 +43,7 @@ pub struct AppTypographyTokens {
pub struct AppLayoutTokens {
pub divider_thickness_px: f32,
pub home_window_padding_px: f32,
+ pub home_sidebar_width_px: f32,
pub home_card_max_width_px: f32,
pub home_card_padding_px: f32,
pub home_stack_gap_px: f32,
@@ -83,6 +84,7 @@ pub const APP_UI_THEME: AppUiTheme = AppUiTheme {
layout: AppLayoutTokens {
divider_thickness_px: 1.0,
home_window_padding_px: 24.0,
+ home_sidebar_width_px: 240.0,
home_card_max_width_px: 960.0,
home_card_padding_px: 24.0,
home_stack_gap_px: 12.0,
diff --git a/i18n/locales/en/messages.json b/i18n/locales/en/messages.json
@@ -2,5 +2,28 @@
"app.name": "radroots",
"home.brand": "radroots",
"home.title": "home",
- "settings.title": "settings"
+ "home.metadata_title": "runtime metadata",
+ "settings.title": "settings",
+ "metadata.core_package": "core package",
+ "metadata.core_version": "core version",
+ "metadata.core_authors": "core authors",
+ "metadata.rust_edition": "rust edition",
+ "metadata.rust_toolchain": "rust toolchain",
+ "metadata.target_triple": "target triple",
+ "metadata.build_profile": "build profile",
+ "metadata.projection": "projection",
+ "metadata.git_commit": "git commit",
+ "metadata.app_name": "app name",
+ "metadata.app_id": "app id",
+ "metadata.app_version": "app version",
+ "metadata.app_build": "app build",
+ "metadata.platform": "platform",
+ "metadata.operating_system": "operating system",
+ "metadata.host_locale": "host locale",
+ "metadata.runtime_origin": "runtime origin",
+ "metadata.runtime_mode": "runtime mode",
+ "metadata.run_id": "run id",
+ "value.none": "none",
+ "value.runtime_mode.development": "development",
+ "value.runtime_mode.production": "production"
}