app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

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:
MCargo.lock | 1+
Mcrates/launchers/desktop/src/app.rs | 14++++----------
Mcrates/launchers/desktop/src/lib.rs | 1+
Acrates/launchers/desktop/src/window.rs | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/shared/i18n/src/keys.rs | 23+++++++++++++++++++++++
Mcrates/shared/ui/Cargo.toml | 1+
Mcrates/shared/ui/src/lib.rs | 4++--
Dcrates/shared/ui/src/placeholder.rs | 21---------------------
Acrates/shared/ui/src/text.rs | 141+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/shared/ui/src/theme.rs | 2++
Mi18n/locales/en/messages.json | 25++++++++++++++++++++++++-
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" }