app

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

commit 96ee69fa0e9c3f1b773f0fbabafa00272cd283c6
parent 25dc5aaef41caee60a4cdbb5f793e522be9047c5
Author: triesap <tyson@radroots.org>
Date:   Sun, 22 Mar 2026 16:25:59 +0000

desktop: add local account roster support

- surface desktop local accounts through the shared roster summary contract
- allow selecting an existing desktop account from the home roster
- reuse desktop local key generation as the home add-account action
- keep desktop backup remove and reset actions tied to the selected account

Diffstat:
Mcrates/desktop/src/main.rs | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 66 insertions(+), 6 deletions(-)

diff --git a/crates/desktop/src/main.rs b/crates/desktop/src/main.rs @@ -10,10 +10,10 @@ use radroots_app_apple_security::{ }; use radroots_app_core::{ APP_NAME, HomeActionKind, HomeActionResult, HomeActionState, IdentityGateState, - ImportActionState, RadrootsApp, RadrootsAppBackend, RadrootsLocationCountry, - RadrootsLocationCountryCenterLookupResult, RadrootsLocationCountryListResult, - RadrootsLocationPoint, RadrootsLocationResolverError, RadrootsLocationReverseOptions, - RadrootsOfflineGeocoderPlatform, RadrootsOfflineGeocoderState, + ImportActionState, RadrootsAccountCustody, RadrootsAccountSummary, RadrootsApp, + RadrootsAppBackend, RadrootsLocationCountry, RadrootsLocationCountryCenterLookupResult, + RadrootsLocationCountryListResult, RadrootsLocationPoint, RadrootsLocationResolverError, + RadrootsLocationReverseOptions, RadrootsOfflineGeocoderPlatform, RadrootsOfflineGeocoderState, RadrootsOfflineGeocoderUnavailableKind, RadrootsResolvedLocation, RadrootsReverseLocationLookupResult, SetupActionState, }; @@ -164,12 +164,30 @@ impl DesktopBackend { RadrootsNostrSelectedAccountStatus::PublicOnly { .. } => IdentityGateState::Missing, RadrootsNostrSelectedAccountStatus::Ready { account } => IdentityGateState::Ready { account_id: account.account_id.to_string(), - npub: account.public_identity.public_key_npub, }, } } #[cfg(target_os = "macos")] + fn account_roster_from_manager( + manager: &RadrootsNostrAccountsManager, + ) -> Result<Vec<RadrootsAccountSummary>, String> { + manager + .list_accounts() + .map_err(|source| source.to_string())? + .into_iter() + .map(|record| { + Ok(RadrootsAccountSummary { + account_id: record.account_id.to_string(), + npub: record.public_identity.public_key_npub, + label: record.label, + custody: RadrootsAccountCustody::LocalManaged, + }) + }) + .collect() + } + + #[cfg(target_os = "macos")] fn remove_selected_local_identity( manager: &RadrootsNostrAccountsManager, ) -> Result<IdentityGateState, String> { @@ -297,6 +315,19 @@ impl RadrootsAppBackend for DesktopBackend { } } + fn load_account_roster(&self) -> Result<Vec<RadrootsAccountSummary>, String> { + #[cfg(target_os = "macos")] + { + let manager = Self::accounts_manager()?; + return Self::account_roster_from_manager(&manager); + } + + #[cfg(not(target_os = "macos"))] + { + Ok(Vec::new()) + } + } + fn offline_geocoder_state(&self) -> Option<RadrootsOfflineGeocoderState> { Some(self.offline_geocoder.current_state()) } @@ -489,6 +520,14 @@ impl RadrootsAppBackend for DesktopBackend { } } + fn home_setup_action_state(&self) -> Option<SetupActionState> { + Some(self.setup_action_state()) + } + + fn request_home_setup_action(&self) -> Result<Option<IdentityGateState>, String> { + self.request_setup_action() + } + fn import_action_state(&self) -> Option<ImportActionState> { #[cfg(target_os = "macos")] { @@ -519,6 +558,28 @@ impl RadrootsAppBackend for DesktopBackend { } } + fn request_select_account( + &self, + account_id: &str, + ) -> Result<Option<IdentityGateState>, String> { + #[cfg(target_os = "macos")] + { + let manager = Self::accounts_manager()?; + let account_id = radroots_identity::RadrootsIdentityId::try_from(account_id) + .map_err(|_| "invalid account id".to_owned())?; + manager + .select_account(&account_id) + .map_err(|source| source.to_string())?; + return self.load_identity_state().map(Some); + } + + #[cfg(not(target_os = "macos"))] + { + let _ = account_id; + Ok(None) + } + } + fn home_action_states(&self) -> Vec<HomeActionState> { #[cfg(target_os = "macos")] { @@ -707,7 +768,6 @@ mod tests { state, radroots_app_core::IdentityGateState::Ready { account_id: identity.id().to_string(), - npub: identity.npub(), } ); assert_eq!(