app

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

commit e7270dd695e9d0f9e47c57c00af94872ec31582b
parent abc5fdd01dababbdaedebf4d2fcbcc04817f98c5
Author: triesap <tyson@radroots.org>
Date:   Fri, 17 Apr 2026 20:19:36 +0000

build: restore standalone buildability

Diffstat:
MCargo.lock | 15++++++---------
MCargo.toml | 7+++----
Mcrates/launchers/desktop/Cargo.toml | 1-
Mcrates/launchers/desktop/src/substrate.rs | 45+++++++++++++--------------------------------
Mcrates/shared/core/src/lib.rs | 5+++++
Acrates/shared/core/src/paths.rs | 291++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 318 insertions(+), 46 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -3523,6 +3523,7 @@ dependencies = [ [[package]] name = "mf2-i18n-build" version = "0.1.0" +source = "git+https://github.com/triesap/mf2-i18n.git?rev=0c3ba2729b309f27aed3e27ae4e753b0147a75ec#0c3ba2729b309f27aed3e27ae4e753b0147a75ec" dependencies = [ "blake3", "hex", @@ -3537,10 +3538,12 @@ dependencies = [ [[package]] name = "mf2-i18n-core" version = "0.1.0" +source = "git+https://github.com/triesap/mf2-i18n.git?rev=0c3ba2729b309f27aed3e27ae4e753b0147a75ec#0c3ba2729b309f27aed3e27ae4e753b0147a75ec" [[package]] name = "mf2-i18n-embedded" version = "0.1.0" +source = "git+https://github.com/triesap/mf2-i18n.git?rev=0c3ba2729b309f27aed3e27ae4e753b0147a75ec#0c3ba2729b309f27aed3e27ae4e753b0147a75ec" dependencies = [ "mf2-i18n-core", ] @@ -3548,6 +3551,7 @@ dependencies = [ [[package]] name = "mf2-i18n-native" version = "0.1.0" +source = "git+https://github.com/triesap/mf2-i18n.git?rev=0c3ba2729b309f27aed3e27ae4e753b0147a75ec#0c3ba2729b309f27aed3e27ae4e753b0147a75ec" dependencies = [ "mf2-i18n-core", "mf2-i18n-embedded", @@ -3559,6 +3563,7 @@ dependencies = [ [[package]] name = "mf2-i18n-runtime" version = "0.1.0" +source = "git+https://github.com/triesap/mf2-i18n.git?rev=0c3ba2729b309f27aed3e27ae4e753b0147a75ec#0c3ba2729b309f27aed3e27ae4e753b0147a75ec" dependencies = [ "ed25519-dalek", "hex", @@ -3573,6 +3578,7 @@ dependencies = [ [[package]] name = "mf2-i18n-std" version = "0.1.0" +source = "git+https://github.com/triesap/mf2-i18n.git?rev=0c3ba2729b309f27aed3e27ae4e753b0147a75ec#0c3ba2729b309f27aed3e27ae4e753b0147a75ec" dependencies = [ "chrono", "intl_pluralrules", @@ -4611,7 +4617,6 @@ dependencies = [ "radroots_app_state", "radroots_app_sync", "radroots_app_ui", - "radroots_runtime_paths", "thiserror 2.0.18", ] @@ -4676,14 +4681,6 @@ dependencies = [ ] [[package]] -name = "radroots_runtime_paths" -version = "0.1.0-alpha.2" -dependencies = [ - "serde", - "thiserror 1.0.69", -] - -[[package]] name = "rand" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml @@ -26,9 +26,9 @@ gpui = "0.2.2" gpui-component = "0.5.1" gpui-component-assets = "0.5.1" hex = "0.4" -mf2-i18n-build = { path = "../../../../vendor/triesap/mf2-i18n/crates/mf2-i18n-build", version = "0.1.0" } -mf2-i18n-core = { path = "../../../../vendor/triesap/mf2-i18n/crates/mf2-i18n-core", version = "0.1.0" } -mf2-i18n-native = { path = "../../../../vendor/triesap/mf2-i18n/crates/mf2-i18n-native", version = "0.1.0" } +mf2-i18n-build = { git = "https://github.com/triesap/mf2-i18n.git", rev = "0c3ba2729b309f27aed3e27ae4e753b0147a75ec" } +mf2-i18n-core = { git = "https://github.com/triesap/mf2-i18n.git", rev = "0c3ba2729b309f27aed3e27ae4e753b0147a75ec" } +mf2-i18n-native = { git = "https://github.com/triesap/mf2-i18n.git", rev = "0c3ba2729b309f27aed3e27ae4e753b0147a75ec" } radroots_app_core = { path = "crates/shared/core", version = "0.1.0" } radroots_app_i18n = { path = "crates/shared/i18n", version = "0.1.0" } radroots_app_models = { path = "crates/shared/models", version = "0.1.0" } @@ -36,7 +36,6 @@ radroots_app_sqlite = { path = "crates/shared/sqlite", version = "0.1.0" } radroots_app_state = { path = "crates/shared/state", version = "0.1.0" } radroots_app_sync = { path = "crates/shared/sync", version = "0.1.0" } radroots_app_ui = { path = "crates/shared/ui", version = "0.1.0" } -radroots_runtime_paths = { path = "../lib/crates/runtime_paths", version = "0.1.0-alpha.2" } rusqlite = { version = "0.32", features = ["bundled"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/crates/launchers/desktop/Cargo.toml b/crates/launchers/desktop/Cargo.toml @@ -18,7 +18,6 @@ radroots_app_sqlite.workspace = true radroots_app_state.workspace = true radroots_app_sync.workspace = true radroots_app_ui.workspace = true -radroots_runtime_paths.workspace = true thiserror.workspace = true [lints] diff --git a/crates/launchers/desktop/src/substrate.rs b/crates/launchers/desktop/src/substrate.rs @@ -1,15 +1,12 @@ use std::path::PathBuf; +use radroots_app_core::{AppRuntimePathsError, AppRuntimeRoots}; use radroots_app_models::AppMode; use radroots_app_sqlite::{AppSqliteError, AppSqliteStore, DatabaseTarget}; use radroots_app_state::{ AppShellProjection, AppStateStore, AppStateStoreError, InMemoryAppStateRepository, }; use radroots_app_sync::{AppSyncProjection, SyncCheckpointStatus, SyncConflictStatus}; -use radroots_runtime_paths::{ - RadrootsPathOverrides, RadrootsPathProfile, RadrootsPathResolver, RadrootsRuntimeNamespace, - RadrootsRuntimePathsError, -}; use thiserror::Error; const APP_DATABASE_FILE_NAME: &str = "app.sqlite3"; @@ -34,13 +31,7 @@ impl DesktopAppSubstrateSummary { } fn try_bootstrap() -> Result<Self, DesktopAppSubstrateError> { - let namespace = RadrootsRuntimeNamespace::app("app")?; - let roots = RadrootsPathResolver::current() - .resolve( - RadrootsPathProfile::InteractiveUser, - &RadrootsPathOverrides::default(), - )? - .namespaced(&namespace); + let roots = AppRuntimeRoots::current_desktop()?; let database_path = roots.data.join(APP_DATABASE_FILE_NAME); let sqlite_store = AppSqliteStore::open(DatabaseTarget::Path(database_path.clone()))?; let shell_store = AppStateStore::load(InMemoryAppStateRepository::default())?; @@ -80,7 +71,7 @@ impl DesktopAppSubstrateSummary { #[derive(Debug, Error)] enum DesktopAppSubstrateError { #[error(transparent)] - RuntimePaths(#[from] RadrootsRuntimePathsError), + RuntimePaths(#[from] AppRuntimePathsError), #[error(transparent)] Sqlite(#[from] AppSqliteError), #[error(transparent)] @@ -91,41 +82,31 @@ enum DesktopAppSubstrateError { mod tests { use std::path::PathBuf; - use radroots_runtime_paths::{ - RadrootsHostEnvironment, RadrootsPathOverrides, RadrootsPathProfile, RadrootsPathResolver, - RadrootsPlatform, RadrootsRuntimeNamespace, - }; + use radroots_app_core::{AppRuntimeHostEnvironment, AppRuntimePlatform, AppRuntimeRoots}; use super::APP_DATABASE_FILE_NAME; #[test] fn desktop_namespace_uses_canonical_app_data_root() { - let namespace = RadrootsRuntimeNamespace::app("app").expect("app namespace should parse"); - let resolver = RadrootsPathResolver::new( - RadrootsPlatform::Macos, - RadrootsHostEnvironment { + let roots = AppRuntimeRoots::for_desktop( + AppRuntimePlatform::Macos, + AppRuntimeHostEnvironment { home_dir: Some(PathBuf::from("/Users/treesap")), - ..RadrootsHostEnvironment::default() + ..AppRuntimeHostEnvironment::default() }, - ); - let namespaced = resolver - .resolve( - RadrootsPathProfile::InteractiveUser, - &RadrootsPathOverrides::default(), - ) - .expect("interactive user roots should resolve") - .namespaced(&namespace); + ) + .expect("interactive user roots should resolve"); assert_eq!( - namespaced.data, + roots.data, PathBuf::from("/Users/treesap/.radroots/data/apps/app") ); assert_eq!( - namespaced.logs, + roots.logs, PathBuf::from("/Users/treesap/.radroots/logs/apps/app") ); assert_eq!( - namespaced.data.join(APP_DATABASE_FILE_NAME), + roots.data.join(APP_DATABASE_FILE_NAME), PathBuf::from("/Users/treesap/.radroots/data/apps/app/app.sqlite3") ); } diff --git a/crates/shared/core/src/lib.rs b/crates/shared/core/src/lib.rs @@ -1,8 +1,13 @@ #![forbid(unsafe_code)] +mod paths; mod runtime; mod startup; +pub use paths::{ + APP_RUNTIME_NAMESPACE, APP_RUNTIME_NAMESPACE_KIND, APP_RUNTIME_NAMESPACE_VALUE, + AppRuntimeHostEnvironment, AppRuntimePathsError, AppRuntimePlatform, AppRuntimeRoots, +}; pub use runtime::{ APP_ID, APP_NAME, APP_PLATFORM_RUNTIME, APP_PROJECTION_SOURCE, APP_RUNTIME_ORIGIN, AppBuildIdentity, AppCoreRuntimeMetadata, AppHostRuntimeMetadata, AppRuntimeCapture, diff --git a/crates/shared/core/src/paths.rs b/crates/shared/core/src/paths.rs @@ -0,0 +1,291 @@ +use std::{ + env, + error::Error, + fmt, + path::{Path, PathBuf}, +}; + +pub const APP_RUNTIME_NAMESPACE_KIND: &str = "apps"; +pub const APP_RUNTIME_NAMESPACE_VALUE: &str = "app"; +pub const APP_RUNTIME_NAMESPACE: &str = "apps/app"; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum AppRuntimePlatform { + Linux, + Macos, + Windows, + Other(&'static str), +} + +impl AppRuntimePlatform { + pub fn current() -> Self { + match env::consts::OS { + "linux" => Self::Linux, + "macos" => Self::Macos, + "windows" => Self::Windows, + other => Self::Other(other), + } + } + + pub const fn label(self) -> &'static str { + match self { + Self::Linux => "linux", + Self::Macos => "macos", + Self::Windows => "windows", + Self::Other(other) => other, + } + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct AppRuntimeHostEnvironment { + pub home_dir: Option<PathBuf>, + pub appdata_dir: Option<PathBuf>, + pub localappdata_dir: Option<PathBuf>, +} + +impl AppRuntimeHostEnvironment { + pub fn from_current_process() -> Self { + Self { + home_dir: env::var_os("HOME").map(PathBuf::from), + appdata_dir: env::var_os("APPDATA").map(PathBuf::from), + localappdata_dir: env::var_os("LOCALAPPDATA").map(PathBuf::from), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AppRuntimeRoots { + pub config: PathBuf, + pub data: PathBuf, + pub cache: PathBuf, + pub logs: PathBuf, + pub run: PathBuf, + pub secrets: PathBuf, +} + +impl AppRuntimeRoots { + pub fn current_desktop() -> Result<Self, AppRuntimePathsError> { + Self::for_desktop( + AppRuntimePlatform::current(), + AppRuntimeHostEnvironment::from_current_process(), + ) + } + + pub fn for_desktop( + platform: AppRuntimePlatform, + host_environment: AppRuntimeHostEnvironment, + ) -> Result<Self, AppRuntimePathsError> { + let roots = match platform { + AppRuntimePlatform::Linux | AppRuntimePlatform::Macos => { + let home_dir = host_environment + .home_dir + .ok_or(AppRuntimePathsError::MissingHomeDir { platform })?; + Self::from_base_root(home_dir.join(".radroots")) + } + AppRuntimePlatform::Windows => { + let appdata_dir = host_environment + .appdata_dir + .ok_or(AppRuntimePathsError::MissingWindowsUserDirs)?; + let localappdata_dir = host_environment + .localappdata_dir + .ok_or(AppRuntimePathsError::MissingWindowsUserDirs)?; + let config_root = appdata_dir.join("Radroots"); + let local_root = localappdata_dir.join("Radroots"); + Self { + config: config_root.join("config"), + data: local_root.join("data"), + cache: local_root.join("cache"), + logs: local_root.join("logs"), + run: local_root.join("run"), + secrets: config_root.join("secrets"), + } + } + AppRuntimePlatform::Other(_) => { + return Err(AppRuntimePathsError::UnsupportedPlatform { platform }); + } + }; + + Ok(roots.namespaced_app()) + } + + pub fn from_base_root(base_root: impl AsRef<Path>) -> Self { + let base_root = base_root.as_ref(); + Self { + config: base_root.join("config"), + data: base_root.join("data"), + cache: base_root.join("cache"), + logs: base_root.join("logs"), + run: base_root.join("run"), + secrets: base_root.join("secrets"), + } + } + + pub fn namespaced_app(&self) -> Self { + let namespace = PathBuf::from(APP_RUNTIME_NAMESPACE_KIND).join(APP_RUNTIME_NAMESPACE_VALUE); + Self { + config: self.config.join(&namespace), + data: self.data.join(&namespace), + cache: self.cache.join(&namespace), + logs: self.logs.join(&namespace), + run: self.run.join(&namespace), + secrets: self.secrets.join(namespace), + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum AppRuntimePathsError { + MissingHomeDir { platform: AppRuntimePlatform }, + MissingWindowsUserDirs, + UnsupportedPlatform { platform: AppRuntimePlatform }, +} + +impl fmt::Display for AppRuntimePathsError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::MissingHomeDir { platform } => { + write!( + formatter, + "desktop runtime roots require HOME for {}", + platform.label() + ) + } + Self::MissingWindowsUserDirs => formatter + .write_str("desktop runtime roots require APPDATA and LOCALAPPDATA on windows"), + Self::UnsupportedPlatform { platform } => write!( + formatter, + "desktop runtime roots are unsupported on {}", + platform.label() + ), + } + } +} + +impl Error for AppRuntimePathsError {} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use super::{ + APP_RUNTIME_NAMESPACE, AppRuntimeHostEnvironment, AppRuntimePathsError, AppRuntimePlatform, + AppRuntimeRoots, + }; + + #[test] + fn desktop_runtime_roots_use_canonical_macos_namespace() { + let roots = AppRuntimeRoots::for_desktop( + AppRuntimePlatform::Macos, + AppRuntimeHostEnvironment { + home_dir: Some(PathBuf::from("/Users/treesap")), + ..AppRuntimeHostEnvironment::default() + }, + ) + .expect("macos roots should resolve"); + + assert_eq!( + roots.data, + PathBuf::from("/Users/treesap/.radroots/data").join(APP_RUNTIME_NAMESPACE) + ); + assert_eq!( + roots.logs, + PathBuf::from("/Users/treesap/.radroots/logs").join(APP_RUNTIME_NAMESPACE) + ); + } + + #[test] + fn desktop_runtime_roots_use_canonical_linux_namespace() { + let roots = AppRuntimeRoots::for_desktop( + AppRuntimePlatform::Linux, + AppRuntimeHostEnvironment { + home_dir: Some(PathBuf::from("/home/treesap")), + ..AppRuntimeHostEnvironment::default() + }, + ) + .expect("linux roots should resolve"); + + assert_eq!( + roots.data, + PathBuf::from("/home/treesap/.radroots/data").join(APP_RUNTIME_NAMESPACE) + ); + assert_eq!( + roots.logs, + PathBuf::from("/home/treesap/.radroots/logs").join(APP_RUNTIME_NAMESPACE) + ); + } + + #[test] + fn desktop_runtime_roots_use_native_windows_roots() { + let roots = AppRuntimeRoots::for_desktop( + AppRuntimePlatform::Windows, + AppRuntimeHostEnvironment { + appdata_dir: Some(PathBuf::from(r"C:\Users\treesap\AppData\Roaming")), + localappdata_dir: Some(PathBuf::from(r"C:\Users\treesap\AppData\Local")), + ..AppRuntimeHostEnvironment::default() + }, + ) + .expect("windows roots should resolve"); + + assert_eq!( + roots.config, + PathBuf::from(r"C:\Users\treesap\AppData\Roaming") + .join("Radroots") + .join("config") + .join(APP_RUNTIME_NAMESPACE) + ); + assert_eq!( + roots.data, + PathBuf::from(r"C:\Users\treesap\AppData\Local") + .join("Radroots") + .join("data") + .join(APP_RUNTIME_NAMESPACE) + ); + } + + #[test] + fn desktop_runtime_roots_require_home_dir_on_unix() { + let err = AppRuntimeRoots::for_desktop( + AppRuntimePlatform::Macos, + AppRuntimeHostEnvironment::default(), + ) + .expect_err("missing home dir should fail"); + + assert_eq!( + err, + AppRuntimePathsError::MissingHomeDir { + platform: AppRuntimePlatform::Macos, + } + ); + } + + #[test] + fn desktop_runtime_roots_require_windows_user_dirs() { + let err = AppRuntimeRoots::for_desktop( + AppRuntimePlatform::Windows, + AppRuntimeHostEnvironment { + appdata_dir: Some(PathBuf::from(r"C:\Users\treesap\AppData\Roaming")), + ..AppRuntimeHostEnvironment::default() + }, + ) + .expect_err("missing local appdata should fail"); + + assert_eq!(err, AppRuntimePathsError::MissingWindowsUserDirs); + } + + #[test] + fn desktop_runtime_roots_reject_unsupported_platforms() { + let err = AppRuntimeRoots::for_desktop( + AppRuntimePlatform::Other("freebsd"), + AppRuntimeHostEnvironment::default(), + ) + .expect_err("unsupported platform should fail"); + + assert_eq!( + err, + AppRuntimePathsError::UnsupportedPlatform { + platform: AppRuntimePlatform::Other("freebsd"), + } + ); + } +}