lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

commit 4bc4154e0ef6a5e02a62e3f6cddf9cb71af96299
parent 8165cd3ccb20798ac41e26219d720347a464117c
Author: triesap <tyson@radroots.org>
Date:   Sun,  2 Aug 2026 19:05:58 +0000

core(accounts): implement selected account persistence

- validate selection targets against the saved account registry
- persist the selected public identity before snapshot publication
- keep signer and active-session state independent from selection
- reject missing targets without changing the current snapshot

Diffstat:
Mcrates/studio_application/src/accounts.rs | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/studio_storage/src/application_adapter.rs | 12+++++++++++-
2 files changed, 61 insertions(+), 1 deletion(-)

diff --git a/crates/studio_application/src/accounts.rs b/crates/studio_application/src/accounts.rs @@ -42,6 +42,24 @@ impl GenerateAccountReceipt { } impl AppCore { + /// Persists and publishes a saved account selection without activating it. + /// + /// # Errors + /// + /// Returns a safe account, persistence, or application-state error. + pub fn select_account( + &self, + public_key: PublicKey, + accounts: &(impl AccountRepository + ?Sized), + app_state: &(impl AppStateRepository + ?Sized), + ) -> Result<crate::AppSnapshot, SafeError> { + if accounts.find_account(public_key)?.is_none() { + return Err(account_not_found()); + } + app_state.save_selected_account(Some(public_key))?; + self.apply_transition(StateTransition::Select(public_key)) + } + /// Generates, stores, and selects one local Nostr account without activating it. /// /// # Errors @@ -726,4 +744,36 @@ mod tests { assert!(!format!("{pending:?}").contains("nsec1")); assert!(core.snapshot().accounts().is_empty()); } + + #[test] + fn select_account_persists_existing_choice_without_activating() { + let core = AppCore::in_memory(RelayConfiguration::default()); + let accounts = InMemoryAccountRepository::default(); + let secrets = InMemorySecretStore::default(); + let journal = InMemoryOperationJournal::default(); + core.bootstrap().expect("bootstrap"); + let first = core + .generate_account(&accounts, &accounts, &secrets, &journal, &FixedClock) + .expect("first") + .account() + .public_key(); + core.generate_account(&accounts, &accounts, &secrets, &journal, &FixedClock) + .expect("second"); + + let selected = core + .select_account(first, &accounts, &accounts) + .expect("select first"); + assert_eq!(selected.selected_account(), Some(first)); + assert_eq!(selected.session(), SessionState::SignedOut); + assert!(selected.active_account().is_none()); + assert_eq!( + accounts.load_selected_account().expect("saved"), + Some(first) + ); + let missing = core + .select_account(PublicKey::from_bytes([0xff; 32]), &accounts, &accounts) + .expect_err("missing account"); + assert_eq!(missing.code(), SafeErrorCode::AccountNotFound); + assert_eq!(core.snapshot(), selected); + } } diff --git a/crates/studio_storage/src/application_adapter.rs b/crates/studio_storage/src/application_adapter.rs @@ -4,7 +4,7 @@ use radroots_studio_application::{ AppCore, AppSnapshot, Clock, GenerateAccountReceipt, ImportAccountReceipt, RelayConfiguration, SecretStore, }; -use radroots_studio_domain::{SafeError, SecretKeyInput}; +use radroots_studio_domain::{PublicKey, SafeError, SecretKeyInput}; use crate::Database; @@ -88,6 +88,16 @@ impl PersistentAppCore { ) } + /// Persists and publishes one saved-account selection without activation. + /// + /// # Errors + /// + /// Returns a safe account, storage, or application-state error. + pub fn select_account(&self, public_key: PublicKey) -> Result<AppSnapshot, SafeError> { + self.core + .select_account(public_key, &self.database, &self.database) + } + #[must_use] pub const fn core(&self) -> &AppCore { &self.core