commit 25dc5aaef41caee60a4cdbb5f793e522be9047c5
parent e57c1e0272010b68073554ed5388bf7bac9b688a
Author: triesap <tyson@radroots.org>
Date: Sun, 22 Mar 2026 16:25:44 +0000
core: add account roster and custody model
- add shared account roster and custody types for the app shell
- narrow identity readiness to the selected account id and load roster details separately
- add home account management ui for selection and native add-account entry points
- cover roster loading and selection behavior in core tests
Diffstat:
2 files changed, 435 insertions(+), 59 deletions(-)
diff --git a/crates/core/src/account_roster.rs b/crates/core/src/account_roster.rs
@@ -0,0 +1,37 @@
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum RadrootsAccountCustody {
+ LocalManaged,
+ BrowserSigner,
+ RemoteSigner,
+}
+
+impl RadrootsAccountCustody {
+ pub fn label(self) -> &'static str {
+ match self {
+ Self::LocalManaged => "local managed",
+ Self::BrowserSigner => "browser signer",
+ Self::RemoteSigner => "remote signer",
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct RadrootsAccountSummary {
+ pub account_id: String,
+ pub npub: String,
+ pub label: Option<String>,
+ pub custody: RadrootsAccountCustody,
+}
+
+impl RadrootsAccountSummary {
+ pub fn display_label(&self) -> String {
+ match self.label.as_deref() {
+ Some(label) if !label.trim().is_empty() => label.to_owned(),
+ _ => match self.custody {
+ RadrootsAccountCustody::LocalManaged => "local account".to_owned(),
+ RadrootsAccountCustody::BrowserSigner => "browser signer".to_owned(),
+ RadrootsAccountCustody::RemoteSigner => "remote signer".to_owned(),
+ },
+ }
+ }
+}
diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs
@@ -4,12 +4,14 @@ use eframe::egui;
use std::time::Duration;
use zeroize::Zeroizing;
+mod account_roster;
mod home_location_tools;
mod location_resolver;
mod offline_geocoder;
pub const APP_NAME: &str = "Rad Roots";
+pub use account_roster::{RadrootsAccountCustody, RadrootsAccountSummary};
pub use location_resolver::{
RadrootsLocationCountry, RadrootsLocationCountryCenterLookupResult,
RadrootsLocationCountryListResult, RadrootsLocationPoint, RadrootsLocationResolverError,
@@ -69,12 +71,15 @@ pub enum HomeActionResult {
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IdentityGateState {
Missing,
- Ready { account_id: String, npub: String },
+ Ready { account_id: String },
Unsupported { reason: String },
}
pub trait RadrootsAppBackend {
fn load_identity_state(&self) -> Result<IdentityGateState, String>;
+ fn load_account_roster(&self) -> Result<Vec<RadrootsAccountSummary>, String> {
+ Ok(Vec::new())
+ }
fn offline_geocoder_state(&self) -> Option<RadrootsOfflineGeocoderState> {
None
}
@@ -83,6 +88,12 @@ pub trait RadrootsAppBackend {
}
fn setup_action_state(&self) -> SetupActionState;
fn request_setup_action(&self) -> Result<Option<IdentityGateState>, String>;
+ fn home_setup_action_state(&self) -> Option<SetupActionState> {
+ None
+ }
+ fn request_home_setup_action(&self) -> Result<Option<IdentityGateState>, String> {
+ Ok(None)
+ }
fn import_action_state(&self) -> Option<ImportActionState> {
None
}
@@ -107,6 +118,12 @@ pub trait RadrootsAppBackend {
fn poll_home_action_result(&self) -> Result<Option<HomeActionResult>, String> {
Ok(None)
}
+ fn request_select_account(
+ &self,
+ _account_id: &str,
+ ) -> Result<Option<IdentityGateState>, String> {
+ Ok(None)
+ }
fn poll_identity_state(&self) -> Result<Option<IdentityGateState>, String> {
Ok(None)
}
@@ -164,12 +181,13 @@ pub trait RadrootsAppBackend {
#[derive(Debug, Clone, PartialEq, Eq)]
enum AppScreen {
Setup,
- Home { account_id: String, npub: String },
+ Home { account_id: String },
}
pub struct RadrootsApp {
backend: Box<dyn RadrootsAppBackend>,
screen: AppScreen,
+ account_roster: Vec<RadrootsAccountSummary>,
offline_geocoder_state: Option<RadrootsOfflineGeocoderState>,
status_message: Option<String>,
home_location_tools: HomeLocationTools,
@@ -184,6 +202,7 @@ impl RadrootsApp {
let mut app = Self {
backend,
screen: AppScreen::Setup,
+ account_roster: Vec::new(),
offline_geocoder_state: None,
status_message: None,
home_location_tools: HomeLocationTools::new(),
@@ -203,10 +222,23 @@ impl RadrootsApp {
app
}
+ fn refresh_account_roster(&mut self) {
+ match self.backend.load_account_roster() {
+ Ok(account_roster) => {
+ self.account_roster = account_roster;
+ }
+ Err(err) => {
+ self.account_roster.clear();
+ self.status_message = Some(err);
+ }
+ }
+ }
+
fn apply_identity_state(&mut self, state: IdentityGateState) {
match state {
IdentityGateState::Missing => {
self.screen = AppScreen::Setup;
+ self.account_roster.clear();
self.status_message = None;
self.home_location_tools.clear();
self.pending_home_confirmation = None;
@@ -214,9 +246,10 @@ impl RadrootsApp {
self.secret_key_input.clear();
self.revealed_secret_key = None;
}
- IdentityGateState::Ready { account_id, npub } => {
- self.screen = AppScreen::Home { account_id, npub };
+ IdentityGateState::Ready { account_id } => {
+ self.screen = AppScreen::Home { account_id };
self.status_message = None;
+ self.refresh_account_roster();
self.home_location_tools.clear();
self.pending_home_confirmation = None;
self.pending_import_entry = false;
@@ -225,6 +258,7 @@ impl RadrootsApp {
}
IdentityGateState::Unsupported { reason } => {
self.screen = AppScreen::Setup;
+ self.account_roster.clear();
self.status_message = Some(reason);
self.home_location_tools.clear();
self.pending_home_confirmation = None;
@@ -247,6 +281,21 @@ impl RadrootsApp {
}
}
+ fn request_home_setup_action(&mut self) {
+ self.status_message = None;
+ self.revealed_secret_key = None;
+ self.pending_home_confirmation = None;
+ self.pending_import_entry = false;
+ self.secret_key_input.clear();
+ match self.backend.request_home_setup_action() {
+ Ok(Some(state)) => self.apply_identity_state(state),
+ Ok(None) => self.refresh_account_roster(),
+ Err(err) => {
+ self.status_message = Some(err);
+ }
+ }
+ }
+
fn request_import_action(&mut self) {
self.status_message = None;
self.revealed_secret_key = None;
@@ -276,6 +325,21 @@ impl RadrootsApp {
}
}
+ fn request_select_account(&mut self, account_id: &str) {
+ self.status_message = None;
+ self.revealed_secret_key = None;
+ self.pending_home_confirmation = None;
+ self.pending_import_entry = false;
+ self.secret_key_input.clear();
+ match self.backend.request_select_account(account_id) {
+ Ok(Some(state)) => self.apply_identity_state(state),
+ Ok(None) => self.refresh_account_roster(),
+ Err(err) => {
+ self.status_message = Some(err);
+ }
+ }
+ }
+
fn request_home_action(&mut self, action: HomeActionKind) {
self.status_message = None;
self.revealed_secret_key = None;
@@ -368,6 +432,146 @@ impl RadrootsApp {
}
}
+ fn render_import_entry(
+ &mut self,
+ ui: &mut egui::Ui,
+ import_action: &ImportActionState,
+ import_paste_action: Option<&PasteActionState>,
+ ) {
+ ui.vertical_centered(|ui| {
+ ui.set_max_width(ui.available_width().min(560.0));
+ ui.label("Import an existing local identity by entering its nsec secret key.");
+ ui.add_space(8.0);
+ ui.add(
+ egui::TextEdit::singleline(&mut *self.secret_key_input)
+ .hint_text("nsec1...")
+ .desired_width(ui.available_width()),
+ );
+ ui.add_space(8.0);
+ if let Some(import_paste_action) = import_paste_action {
+ let paste_clicked = ui
+ .add_enabled(
+ import_paste_action.enabled,
+ egui::Button::new(import_paste_action.label.clone()),
+ )
+ .clicked();
+ if paste_clicked {
+ self.request_import_paste_action();
+ }
+ ui.add_space(8.0);
+ }
+ ui.horizontal_centered(|ui| {
+ let confirm_clicked = ui
+ .add_enabled(
+ import_action.enabled,
+ egui::Button::new(import_action.label.clone()),
+ )
+ .clicked();
+ if confirm_clicked {
+ self.request_import_action();
+ }
+
+ if ui.button("Cancel").clicked() {
+ self.pending_import_entry = false;
+ self.secret_key_input.clear();
+ self.status_message = None;
+ }
+ });
+ });
+ }
+
+ fn render_home_account_section(&mut self, ui: &mut egui::Ui) {
+ let AppScreen::Home { account_id } = &self.screen else {
+ return;
+ };
+ let selected_account_id = account_id.clone();
+ let selected_summary = self
+ .account_roster
+ .iter()
+ .find(|account| account.account_id == selected_account_id)
+ .cloned();
+
+ ui.label("home");
+ ui.add_space(8.0);
+ ui.label("A signing identity is configured.");
+ ui.add_space(12.0);
+
+ if let Some(summary) = selected_summary {
+ ui.label(summary.display_label());
+ ui.monospace(format!("account id: {}", summary.account_id));
+ ui.monospace(format!("npub: {}", summary.npub));
+ ui.monospace(format!("custody: {}", summary.custody.label()));
+ } else {
+ ui.label("Selected account details are unavailable.");
+ ui.monospace(format!("account id: {selected_account_id}"));
+ }
+
+ if !self.account_roster.is_empty() {
+ ui.add_space(16.0);
+ ui.label("Accounts");
+ let mut next_selected_account_id = None;
+ for account in &self.account_roster {
+ ui.add_space(8.0);
+ ui.horizontal_wrapped(|ui| {
+ let is_selected = account.account_id == selected_account_id;
+ ui.label(account.display_label());
+ ui.monospace(account.npub.as_str());
+ ui.monospace(account.custody.label());
+ if is_selected {
+ ui.label("selected");
+ } else if ui.button("Select Account").clicked() {
+ next_selected_account_id = Some(account.account_id.clone());
+ }
+ });
+ }
+ if let Some(account_id) = next_selected_account_id {
+ self.request_select_account(account_id.as_str());
+ }
+ }
+
+ let home_setup_action = self.backend.home_setup_action_state();
+ let import_action = self.backend.import_action_state();
+ let import_paste_action = self.backend.import_paste_action_state();
+ if home_setup_action.is_some() || import_action.is_some() {
+ ui.add_space(16.0);
+ ui.label("Add account");
+ }
+
+ if let Some(home_setup_action) = home_setup_action {
+ if home_setup_action.pending {
+ ui.ctx().request_repaint();
+ }
+ ui.add_space(8.0);
+ if ui
+ .add_enabled(
+ home_setup_action.enabled,
+ egui::Button::new(home_setup_action.label),
+ )
+ .clicked()
+ {
+ self.request_home_setup_action();
+ }
+ }
+
+ if let Some(import_action) = import_action {
+ if import_action.pending {
+ ui.ctx().request_repaint();
+ }
+ if let Some(import_paste_action) = &import_paste_action {
+ if import_paste_action.pending {
+ ui.ctx().request_repaint();
+ }
+ }
+ ui.add_space(8.0);
+ if self.pending_import_entry {
+ self.render_import_entry(ui, &import_action, import_paste_action.as_ref());
+ } else if ui.button(import_action.label).clicked() {
+ self.pending_import_entry = true;
+ self.status_message = None;
+ }
+ }
+ }
+
fn render_offline_geocoder_status(&self, ui: &mut egui::Ui) {
let Some(state) = &self.offline_geocoder_state else {
return;
@@ -424,7 +628,7 @@ impl eframe::App for RadrootsApp {
ui.heading(APP_NAME);
ui.add_space(12.0);
- match &self.screen {
+ match self.screen.clone() {
AppScreen::Setup => {
let action = self.backend.setup_action_state();
if action.pending {
@@ -457,63 +661,19 @@ impl eframe::App for RadrootsApp {
if let Some(import_action) = import_action {
ui.add_space(12.0);
if self.pending_import_entry {
- ui.vertical_centered(|ui| {
- ui.set_max_width(ui.available_width().min(560.0));
- ui.label(
- "Import an existing local identity by entering its nsec secret key.",
- );
- ui.add_space(8.0);
- ui.add(
- egui::TextEdit::singleline(&mut *self.secret_key_input)
- .hint_text("nsec1...")
- .desired_width(ui.available_width()),
- );
- ui.add_space(8.0);
- if let Some(import_paste_action) = &import_paste_action {
- let paste_clicked = ui
- .add_enabled(
- import_paste_action.enabled,
- egui::Button::new(
- import_paste_action.label.clone(),
- ),
- )
- .clicked();
- if paste_clicked {
- self.request_import_paste_action();
- }
- ui.add_space(8.0);
- }
- ui.horizontal_centered(|ui| {
- let confirm_clicked = ui
- .add_enabled(
- import_action.enabled,
- egui::Button::new(import_action.label.clone()),
- )
- .clicked();
- if confirm_clicked {
- self.request_import_action();
- }
-
- if ui.button("Cancel").clicked() {
- self.pending_import_entry = false;
- self.secret_key_input.clear();
- self.status_message = None;
- }
- });
- });
+ self.render_import_entry(
+ ui,
+ &import_action,
+ import_paste_action.as_ref(),
+ );
} else if ui.button(import_action.label).clicked() {
self.pending_import_entry = true;
self.status_message = None;
}
}
}
- AppScreen::Home { account_id, npub } => {
- ui.label("home");
- ui.add_space(8.0);
- ui.label("A signing identity is configured.");
- ui.add_space(12.0);
- ui.monospace(format!("account id: {account_id}"));
- ui.monospace(format!("npub: {npub}"));
+ AppScreen::Home { .. } => {
+ self.render_home_account_section(ui);
self.home_location_tools.render(
ui,
self.backend.as_ref(),
@@ -595,7 +755,7 @@ impl eframe::App for RadrootsApp {
#[cfg(test)]
mod tests {
use super::*;
- use radroots_app_test_support::FIXTURE_ALICE;
+ use radroots_app_test_support::{FIXTURE_ALICE, FIXTURE_BOB};
use std::cell::RefCell;
use std::collections::VecDeque;
use std::rc::Rc;
@@ -603,14 +763,17 @@ mod tests {
#[derive(Clone)]
struct MockBackend {
load: Result<IdentityGateState, String>,
+ account_roster: Rc<RefCell<Vec<RadrootsAccountSummary>>>,
offline_geocoder_state: Rc<RefCell<Option<RadrootsOfflineGeocoderState>>>,
offline_geocoder_poll:
Rc<RefCell<VecDeque<Result<Option<RadrootsOfflineGeocoderState>, String>>>>,
action_state: Rc<RefCell<SetupActionState>>,
+ home_setup_action_state: Rc<RefCell<Option<SetupActionState>>>,
import_action_state: Rc<RefCell<Option<ImportActionState>>>,
import_paste_action_state: Rc<RefCell<Option<PasteActionState>>>,
home_action_states: Rc<RefCell<Vec<HomeActionState>>>,
request: Rc<RefCell<VecDeque<Result<Option<IdentityGateState>, String>>>>,
+ home_setup_request: Rc<RefCell<VecDeque<Result<Option<IdentityGateState>, String>>>>,
import_request: Rc<RefCell<VecDeque<Result<Option<IdentityGateState>, String>>>>,
import_paste_request: Rc<RefCell<VecDeque<Result<Option<String>, String>>>>,
home_request: Rc<RefCell<VecDeque<(HomeActionKind, Result<HomeActionResult, String>)>>>,
@@ -618,6 +781,8 @@ mod tests {
reverse_lookup_request: Rc<RefCell<VecDeque<Result<(), RadrootsLocationResolverError>>>>,
reverse_lookup_poll:
Rc<RefCell<VecDeque<Result<Option<RadrootsReverseLocationLookupResult>, String>>>>,
+ select_account_request:
+ Rc<RefCell<VecDeque<(String, Result<Option<IdentityGateState>, String>)>>>,
poll: Rc<RefCell<VecDeque<Result<Option<IdentityGateState>, String>>>>,
}
@@ -630,23 +795,32 @@ mod tests {
) -> Self {
Self {
load,
+ account_roster: Rc::new(RefCell::new(Vec::new())),
offline_geocoder_state: Rc::new(RefCell::new(None)),
offline_geocoder_poll: Rc::new(RefCell::new(VecDeque::new())),
action_state: Rc::new(RefCell::new(action_state)),
+ home_setup_action_state: Rc::new(RefCell::new(None)),
import_action_state: Rc::new(RefCell::new(None)),
import_paste_action_state: Rc::new(RefCell::new(None)),
home_action_states: Rc::new(RefCell::new(Vec::new())),
request: Rc::new(RefCell::new(request.into())),
+ home_setup_request: Rc::new(RefCell::new(VecDeque::new())),
import_request: Rc::new(RefCell::new(VecDeque::new())),
import_paste_request: Rc::new(RefCell::new(VecDeque::new())),
home_request: Rc::new(RefCell::new(VecDeque::new())),
home_poll: Rc::new(RefCell::new(VecDeque::new())),
reverse_lookup_request: Rc::new(RefCell::new(VecDeque::new())),
reverse_lookup_poll: Rc::new(RefCell::new(VecDeque::new())),
+ select_account_request: Rc::new(RefCell::new(VecDeque::new())),
poll: Rc::new(RefCell::new(poll.into())),
}
}
+ fn with_account_roster(self, account_roster: Vec<RadrootsAccountSummary>) -> Self {
+ *self.account_roster.borrow_mut() = account_roster;
+ self
+ }
+
fn with_offline_geocoder_state(
self,
state: RadrootsOfflineGeocoderState,
@@ -667,6 +841,16 @@ mod tests {
self
}
+ fn with_home_setup_action(
+ self,
+ action_state: SetupActionState,
+ request: Vec<Result<Option<IdentityGateState>, String>>,
+ ) -> Self {
+ *self.home_setup_action_state.borrow_mut() = Some(action_state);
+ self.home_setup_request.borrow_mut().extend(request);
+ self
+ }
+
fn with_import_paste_action(
self,
action_state: PasteActionState,
@@ -710,6 +894,19 @@ mod tests {
self.reverse_lookup_poll.borrow_mut().extend(poll);
self
}
+
+ fn with_select_account(
+ self,
+ account_id: &str,
+ request: Vec<Result<Option<IdentityGateState>, String>>,
+ ) -> Self {
+ self.select_account_request.borrow_mut().extend(
+ request
+ .into_iter()
+ .map(|result| (account_id.to_owned(), result)),
+ );
+ self
+ }
}
impl RadrootsAppBackend for MockBackend {
@@ -717,6 +914,10 @@ mod tests {
self.load.clone()
}
+ fn load_account_roster(&self) -> Result<Vec<RadrootsAccountSummary>, String> {
+ Ok(self.account_roster.borrow().clone())
+ }
+
fn offline_geocoder_state(&self) -> Option<RadrootsOfflineGeocoderState> {
self.offline_geocoder_state.borrow().clone()
}
@@ -741,6 +942,17 @@ mod tests {
.unwrap_or_else(|| Err("missing request response".into()))
}
+ fn home_setup_action_state(&self) -> Option<SetupActionState> {
+ self.home_setup_action_state.borrow().clone()
+ }
+
+ fn request_home_setup_action(&self) -> Result<Option<IdentityGateState>, String> {
+ self.home_setup_request
+ .borrow_mut()
+ .pop_front()
+ .unwrap_or(Ok(None))
+ }
+
fn import_action_state(&self) -> Option<ImportActionState> {
self.import_action_state.borrow().clone()
}
@@ -788,6 +1000,23 @@ mod tests {
self.home_poll.borrow_mut().pop_front().unwrap_or(Ok(None))
}
+ fn request_select_account(
+ &self,
+ account_id: &str,
+ ) -> Result<Option<IdentityGateState>, String> {
+ let Some((expected_account_id, response)) =
+ self.select_account_request.borrow_mut().pop_front()
+ else {
+ return Err("missing select-account response".into());
+ };
+ if expected_account_id != account_id {
+ return Err(format!(
+ "unexpected account selection request: expected {expected_account_id}, got {account_id}"
+ ));
+ }
+ response
+ }
+
fn request_reverse_location_lookup(
&self,
_point: RadrootsLocationPoint,
@@ -813,17 +1042,33 @@ mod tests {
}
}
+ fn fixture_account_summary() -> RadrootsAccountSummary {
+ RadrootsAccountSummary {
+ account_id: FIXTURE_ALICE.account_id.into(),
+ npub: FIXTURE_ALICE.npub.into(),
+ label: Some("fixture alice".into()),
+ custody: RadrootsAccountCustody::LocalManaged,
+ }
+ }
+
+ fn fixture_bob_account_summary() -> RadrootsAccountSummary {
+ RadrootsAccountSummary {
+ account_id: FIXTURE_BOB.account_id.into(),
+ npub: FIXTURE_BOB.npub.into(),
+ label: Some("fixture bob".into()),
+ custody: RadrootsAccountCustody::LocalManaged,
+ }
+ }
+
fn fixture_ready_state() -> IdentityGateState {
IdentityGateState::Ready {
account_id: FIXTURE_ALICE.account_id.into(),
- npub: FIXTURE_ALICE.npub.into(),
}
}
fn fixture_home_screen() -> AppScreen {
AppScreen::Home {
account_id: FIXTURE_ALICE.account_id.into(),
- npub: FIXTURE_ALICE.npub.into(),
}
}
@@ -860,6 +1105,25 @@ mod tests {
}
#[test]
+ fn startup_ready_key_loads_account_roster() {
+ let app = RadrootsApp::new(Box::new(
+ MockBackend::new(
+ Ok(fixture_ready_state()),
+ vec![],
+ vec![],
+ SetupActionState {
+ label: "Generate New Key".into(),
+ enabled: true,
+ pending: false,
+ },
+ )
+ .with_account_roster(vec![fixture_account_summary()]),
+ ));
+
+ assert_eq!(app.account_roster, vec![fixture_account_summary()]);
+ }
+
+ #[test]
fn startup_unsupported_shows_reason() {
let app = RadrootsApp::new(Box::new(MockBackend::new(
Ok(IdentityGateState::Unsupported {
@@ -917,6 +1181,81 @@ mod tests {
}
#[test]
+ fn home_setup_action_transitions_to_new_selected_account() {
+ let mut app = RadrootsApp::new(Box::new(
+ MockBackend::new(
+ Ok(fixture_ready_state()),
+ vec![],
+ vec![],
+ SetupActionState {
+ label: "Generate New Key".into(),
+ enabled: true,
+ pending: false,
+ },
+ )
+ .with_account_roster(vec![
+ fixture_account_summary(),
+ fixture_bob_account_summary(),
+ ])
+ .with_home_setup_action(
+ SetupActionState {
+ label: "Generate New Key".into(),
+ enabled: true,
+ pending: false,
+ },
+ vec![Ok(Some(IdentityGateState::Ready {
+ account_id: FIXTURE_BOB.account_id.into(),
+ }))],
+ ),
+ ));
+
+ app.request_home_setup_action();
+
+ assert_eq!(
+ app.screen,
+ AppScreen::Home {
+ account_id: FIXTURE_BOB.account_id.into(),
+ }
+ );
+ assert_eq!(app.account_roster.len(), 2);
+ }
+
+ #[test]
+ fn select_account_transitions_to_requested_account() {
+ let mut app = RadrootsApp::new(Box::new(
+ MockBackend::new(
+ Ok(fixture_ready_state()),
+ vec![],
+ vec![],
+ SetupActionState {
+ label: "Generate New Key".into(),
+ enabled: true,
+ pending: false,
+ },
+ )
+ .with_account_roster(vec![
+ fixture_account_summary(),
+ fixture_bob_account_summary(),
+ ])
+ .with_select_account(
+ FIXTURE_BOB.account_id,
+ vec![Ok(Some(IdentityGateState::Ready {
+ account_id: FIXTURE_BOB.account_id.into(),
+ }))],
+ ),
+ ));
+
+ app.request_select_account(FIXTURE_BOB.account_id);
+
+ assert_eq!(
+ app.screen,
+ AppScreen::Home {
+ account_id: FIXTURE_BOB.account_id.into(),
+ }
+ );
+ }
+
+ #[test]
fn home_remove_action_transitions_to_setup() {
let mut app = RadrootsApp::new(Box::new(
MockBackend::new(