commit 3d36437c70bd86cfcd4ae87a91c0296d8448f825
parent 91b653de3344007f5e848ba63e741b737697163d
Author: triesap <tyson@radroots.org>
Date: Fri, 17 Apr 2026 20:46:30 +0000
state: share settings state and add the today projection
Diffstat:
3 files changed, 325 insertions(+), 71 deletions(-)
diff --git a/crates/launchers/desktop/src/runtime.rs b/crates/launchers/desktop/src/runtime.rs
@@ -4,10 +4,10 @@ use std::{
};
use radroots_app_core::{AppRuntimePathsError, AppRuntimeRoots};
-use radroots_app_models::{AppMode, SettingsSection};
+use radroots_app_models::{AppMode, SettingsSection, TodayAgendaProjection};
use radroots_app_sqlite::{AppSqliteError, AppSqliteStore, DatabaseTarget};
use radroots_app_state::{
- AppShellCommand, AppShellProjection, AppStateStore, AppStateStoreError,
+ AppShellProjection, AppStateCommand, AppStateStore, AppStateStoreError,
InMemoryAppStateRepository, SettingsPreference,
};
use radroots_app_sync::{AppSyncProjection, SyncCheckpointStatus, SyncConflictStatus};
@@ -38,7 +38,8 @@ impl DesktopAppRuntime {
logs_dir: state.logs_dir.clone(),
database_path: state.database_path.clone(),
sqlite_schema_version: state.sqlite_schema_version,
- shell_projection: state.shell_store.projection().clone(),
+ shell_projection: state.state_store.shell_projection().clone(),
+ today_projection: state.state_store.today_projection().clone(),
sync_projection: state.sync_projection.clone(),
startup_issue: state.startup_issue.clone(),
}
@@ -46,27 +47,34 @@ impl DesktopAppRuntime {
pub fn selected_settings_section(&self) -> SettingsSection {
self.lock_state()
- .shell_store
- .projection()
+ .state_store
+ .shell_projection()
.settings
.selected_section
}
pub fn select_settings_section(&self, section: SettingsSection) -> bool {
self.lock_state_mut()
- .shell_store
- .apply_in_memory(AppShellCommand::select_settings_section(section))
+ .state_store
+ .apply_in_memory(AppStateCommand::select_settings_section(section))
}
pub fn set_settings_preference(&self, preference: SettingsPreference, enabled: bool) -> bool {
self.lock_state_mut()
- .shell_store
- .apply_in_memory(AppShellCommand::SetSettingsPreference {
+ .state_store
+ .apply_in_memory(AppStateCommand::SetSettingsPreference {
preference,
enabled,
})
}
+ #[allow(dead_code)]
+ pub fn replace_today_agenda(&self, projection: TodayAgendaProjection) -> bool {
+ self.lock_state_mut()
+ .state_store
+ .apply_in_memory(AppStateCommand::replace_today_agenda(projection))
+ }
+
fn from_state(state: DesktopAppRuntimeState) -> Self {
Self {
state: Arc::new(Mutex::new(state)),
@@ -89,6 +97,8 @@ pub struct DesktopAppRuntimeSummary {
pub database_path: Option<PathBuf>,
pub sqlite_schema_version: Option<u32>,
pub shell_projection: AppShellProjection,
+ #[allow(dead_code)]
+ pub today_projection: TodayAgendaProjection,
pub sync_projection: AppSyncProjection,
pub startup_issue: Option<String>,
}
@@ -99,7 +109,7 @@ struct DesktopAppRuntimeState {
logs_dir: Option<PathBuf>,
database_path: Option<PathBuf>,
sqlite_schema_version: Option<u32>,
- shell_store: AppStateStore<InMemoryAppStateRepository>,
+ state_store: AppStateStore<InMemoryAppStateRepository>,
sync_projection: AppSyncProjection,
startup_issue: Option<String>,
}
@@ -109,7 +119,7 @@ impl DesktopAppRuntimeState {
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())?;
+ let state_store = AppStateStore::load(InMemoryAppStateRepository::default())?;
let sync_projection = AppSyncProjection {
checkpoint: SyncCheckpointStatus::never_synced(),
conflict_status: SyncConflictStatus::clear(),
@@ -121,7 +131,7 @@ impl DesktopAppRuntimeState {
logs_dir: Some(roots.logs),
database_path: Some(database_path),
sqlite_schema_version: Some(sqlite_store.schema_version()?),
- shell_store,
+ state_store,
sync_projection,
startup_issue: None,
})
@@ -133,7 +143,7 @@ impl DesktopAppRuntimeState {
logs_dir: None,
database_path: None,
sqlite_schema_version: None,
- shell_store: AppStateStore::in_memory(AppShellProjection {
+ state_store: AppStateStore::in_memory(AppShellProjection {
app_mode: AppMode::Farmer,
..AppShellProjection::default()
}),
@@ -158,6 +168,7 @@ mod tests {
use std::path::PathBuf;
use radroots_app_core::{AppRuntimeHostEnvironment, AppRuntimePlatform, AppRuntimeRoots};
+ use radroots_app_models::TodayAgendaProjection;
use radroots_app_state::{AppStateStore, InMemoryAppStateRepository, SettingsPreference};
use radroots_app_sync::AppSyncProjection;
@@ -197,7 +208,7 @@ mod tests {
logs_dir: None,
database_path: None,
sqlite_schema_version: None,
- shell_store: AppStateStore::load(InMemoryAppStateRepository::default())
+ state_store: AppStateStore::load(InMemoryAppStateRepository::default())
.expect("in-memory state store should load"),
sync_projection: AppSyncProjection::default(),
startup_issue: None,
@@ -206,13 +217,25 @@ mod tests {
assert!(runtime.select_settings_section(SettingsSection::About));
assert!(cloned_runtime.set_settings_preference(SettingsPreference::LaunchAtLogin, true));
+ assert!(cloned_runtime.replace_today_agenda(TodayAgendaProjection {
+ setup_checklist: vec![radroots_app_models::TodaySetupTask {
+ kind: radroots_app_models::TodaySetupTaskKind::AddFulfillmentWindow,
+ is_complete: false,
+ }],
+ ..TodayAgendaProjection::default()
+ }));
let summary = runtime.summary();
assert_eq!(
+ summary.shell_projection.selected_section,
+ radroots_app_models::ShellSection::Home
+ );
+ assert_eq!(
summary.shell_projection.settings.selected_section,
SettingsSection::About
);
assert!(summary.shell_projection.settings.general.launch_at_login);
+ assert!(summary.today_projection.needs_setup());
assert_eq!(
cloned_runtime.selected_settings_section(),
SettingsSection::About
diff --git a/crates/shared/models/src/lib.rs b/crates/shared/models/src/lib.rs
@@ -238,6 +238,14 @@ pub struct FarmSummary {
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
+pub struct FulfillmentWindowSummary {
+ pub fulfillment_window_id: FulfillmentWindowId,
+ pub farm_id: FarmId,
+ pub starts_at: String,
+ pub ends_at: String,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct TodaySummary {
pub farm_id: FarmId,
pub orders_needing_action: u32,
@@ -257,21 +265,64 @@ pub struct ProductListRow {
pub farm_id: FarmId,
pub title: String,
pub status: ProductStatus,
+ pub stock_count: u32,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct OrderListRow {
pub order_id: OrderId,
pub farm_id: FarmId,
+ pub fulfillment_window_id: Option<FulfillmentWindowId>,
pub order_number: String,
pub customer_display_name: String,
pub status: OrderStatus,
}
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
+#[serde(rename_all = "snake_case")]
+pub enum TodaySetupTaskKind {
+ AddFulfillmentWindow,
+ PublishProduct,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
+pub struct TodaySetupTask {
+ pub kind: TodaySetupTaskKind,
+ pub is_complete: bool,
+}
+
+#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
+pub struct TodayAgendaProjection {
+ pub farm: Option<FarmSummary>,
+ pub summary: Option<TodaySummary>,
+ pub orders_needing_action: Vec<OrderListRow>,
+ pub low_stock_products: Vec<ProductListRow>,
+ pub draft_products: Vec<ProductListRow>,
+ pub next_fulfillment_window: Option<FulfillmentWindowSummary>,
+ pub setup_checklist: Vec<TodaySetupTask>,
+}
+
+impl TodayAgendaProjection {
+ pub fn has_attention_items(&self) -> bool {
+ self.summary
+ .as_ref()
+ .is_some_and(TodaySummary::has_attention_items)
+ || !self.orders_needing_action.is_empty()
+ || !self.low_stock_products.is_empty()
+ || !self.draft_products.is_empty()
+ }
+
+ pub fn needs_setup(&self) -> bool {
+ self.setup_checklist.iter().any(|item| !item.is_complete)
+ }
+}
+
#[cfg(test)]
mod tests {
use super::{
- AppMode, BuyerSection, FarmId, FarmerSection, SettingsSection, ShellSection, TodaySummary,
+ AppMode, BuyerSection, FarmId, FarmerSection, OrderListRow, ProductListRow,
+ SettingsSection, ShellSection, TodayAgendaProjection, TodaySetupTask, TodaySetupTaskKind,
+ TodaySummary,
};
use std::{collections::BTreeSet, str::FromStr};
use uuid::Uuid;
@@ -353,4 +404,59 @@ mod tests {
assert!(!quiet.has_attention_items());
assert!(busy.has_attention_items());
}
+
+ #[test]
+ fn today_agenda_projection_tracks_attention_and_setup_independently() {
+ let calm = TodayAgendaProjection::default();
+ let with_attention = TodayAgendaProjection {
+ draft_products: vec![ProductListRow {
+ product_id: super::ProductId::new(),
+ farm_id: FarmId::new(),
+ title: "Spring onions".to_owned(),
+ status: super::ProductStatus::Draft,
+ stock_count: 0,
+ }],
+ ..TodayAgendaProjection::default()
+ };
+ let with_setup = TodayAgendaProjection {
+ setup_checklist: vec![TodaySetupTask {
+ kind: TodaySetupTaskKind::AddFulfillmentWindow,
+ is_complete: false,
+ }],
+ ..TodayAgendaProjection::default()
+ };
+
+ assert!(!calm.has_attention_items());
+ assert!(!calm.needs_setup());
+ assert!(with_attention.has_attention_items());
+ assert!(!with_attention.needs_setup());
+ assert!(!with_setup.has_attention_items());
+ assert!(with_setup.needs_setup());
+ }
+
+ #[test]
+ fn today_agenda_projection_can_hold_truthful_lists() {
+ let projection = TodayAgendaProjection {
+ orders_needing_action: vec![OrderListRow {
+ order_id: super::OrderId::new(),
+ farm_id: FarmId::new(),
+ fulfillment_window_id: Some(super::FulfillmentWindowId::new()),
+ order_number: "R-1001".to_owned(),
+ customer_display_name: "Casey".to_owned(),
+ status: super::OrderStatus::NeedsAction,
+ }],
+ low_stock_products: vec![ProductListRow {
+ product_id: super::ProductId::new(),
+ farm_id: FarmId::new(),
+ title: "Carrots".to_owned(),
+ status: super::ProductStatus::Published,
+ stock_count: 2,
+ }],
+ ..TodayAgendaProjection::default()
+ };
+
+ assert_eq!(projection.orders_needing_action.len(), 1);
+ assert_eq!(projection.low_stock_products[0].stock_count, 2);
+ assert!(projection.has_attention_items());
+ }
}
diff --git a/crates/shared/state/src/lib.rs b/crates/shared/state/src/lib.rs
@@ -1,6 +1,6 @@
#![forbid(unsafe_code)]
-use radroots_app_models::{AppMode, SettingsSection, ShellSection};
+use radroots_app_models::{AppMode, SettingsSection, ShellSection, TodayAgendaProjection};
use thiserror::Error;
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -101,6 +101,26 @@ impl AppShellProjection {
self.settings.selected_section = settings_section;
}
}
+
+ fn select_settings_section(&mut self, selected_section: SettingsSection) {
+ self.settings.selected_section = selected_section;
+
+ if matches!(self.selected_section, ShellSection::Settings(_)) {
+ self.selected_section = ShellSection::Settings(selected_section);
+ }
+ }
+}
+
+#[derive(Clone, Debug, Default, Eq, PartialEq)]
+pub struct AppProjection {
+ pub shell: AppShellProjection,
+ pub today: TodayAgendaProjection,
+}
+
+impl AppProjection {
+ pub fn new(shell: AppShellProjection, today: TodayAgendaProjection) -> Self {
+ Self { shell, today }
+ }
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -111,18 +131,24 @@ pub enum SettingsPreference {
LaunchAtLogin,
}
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum AppShellCommand {
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum AppStateCommand {
SelectSection(ShellSection),
+ SelectSettingsSection(SettingsSection),
SetSettingsPreference {
preference: SettingsPreference,
enabled: bool,
},
+ ReplaceTodayAgenda(TodayAgendaProjection),
}
-impl AppShellCommand {
+impl AppStateCommand {
pub const fn select_settings_section(section: SettingsSection) -> Self {
- Self::SelectSection(ShellSection::Settings(section))
+ Self::SelectSettingsSection(section)
+ }
+
+ pub fn replace_today_agenda(projection: TodayAgendaProjection) -> Self {
+ Self::ReplaceTodayAgenda(projection)
}
}
@@ -205,12 +231,15 @@ pub enum AppStateStoreError {
#[derive(Clone, Debug)]
pub struct AppStateStore<R> {
repository: R,
- projection: AppShellProjection,
+ projection: AppProjection,
}
impl<R: AppStateRepository> AppStateStore<R> {
pub fn load(repository: R) -> Result<Self, AppStateStoreError> {
- let projection = repository.load_shell_projection()?;
+ let projection = AppProjection::new(
+ repository.load_shell_projection()?,
+ TodayAgendaProjection::default(),
+ );
Ok(Self {
repository,
@@ -218,25 +247,40 @@ impl<R: AppStateRepository> AppStateStore<R> {
})
}
- pub fn projection(&self) -> &AppShellProjection {
+ pub fn projection(&self) -> &AppProjection {
&self.projection
}
+ pub fn shell_projection(&self) -> &AppShellProjection {
+ &self.projection.shell
+ }
+
+ pub fn today_projection(&self) -> &TodayAgendaProjection {
+ &self.projection.today
+ }
+
pub fn repository(&self) -> &R {
&self.repository
}
- pub fn apply(&mut self, command: AppShellCommand) -> Result<bool, AppStateStoreError> {
+ pub fn apply(&mut self, command: AppStateCommand) -> Result<bool, AppStateStoreError> {
let mut next_projection = self.projection.clone();
- if !apply_command(&mut next_projection, command) {
- return Ok(false);
- }
+ match apply_command(&mut next_projection, command) {
+ AppStateMutation::NoChange => Ok(false),
+ AppStateMutation::ShellChanged => {
+ self.repository
+ .save_shell_projection(&next_projection.shell)?;
+ self.projection = next_projection;
- self.repository.save_shell_projection(&next_projection)?;
- self.projection = next_projection;
+ Ok(true)
+ }
+ AppStateMutation::TodayChanged => {
+ self.projection = next_projection;
- Ok(true)
+ Ok(true)
+ }
+ }
}
}
@@ -244,52 +288,82 @@ impl AppStateStore<InMemoryAppStateRepository> {
pub fn in_memory(projection: AppShellProjection) -> Self {
Self {
repository: InMemoryAppStateRepository::new(projection.clone()),
- projection,
+ projection: AppProjection::new(projection, TodayAgendaProjection::default()),
}
}
- pub fn apply_in_memory(&mut self, command: AppShellCommand) -> bool {
+ pub fn apply_in_memory(&mut self, command: AppStateCommand) -> bool {
let mut next_projection = self.projection.clone();
- if !apply_command(&mut next_projection, command) {
- return false;
- }
+ match apply_command(&mut next_projection, command) {
+ AppStateMutation::NoChange => false,
+ AppStateMutation::ShellChanged => {
+ self.repository.overwrite(next_projection.shell.clone());
+ self.projection = next_projection;
- self.repository.overwrite(next_projection.clone());
- self.projection = next_projection;
+ true
+ }
+ AppStateMutation::TodayChanged => {
+ self.projection = next_projection;
- true
+ true
+ }
+ }
}
}
-fn apply_command(projection: &mut AppShellProjection, command: AppShellCommand) -> bool {
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+enum AppStateMutation {
+ NoChange,
+ ShellChanged,
+ TodayChanged,
+}
+
+fn apply_command(projection: &mut AppProjection, command: AppStateCommand) -> AppStateMutation {
let before = projection.clone();
match command {
- AppShellCommand::SelectSection(selected_section) => {
- projection.select_section(selected_section);
+ AppStateCommand::SelectSection(selected_section) => {
+ projection.shell.select_section(selected_section);
}
- AppShellCommand::SetSettingsPreference {
+ AppStateCommand::SelectSettingsSection(selected_section) => {
+ projection.shell.select_settings_section(selected_section);
+ }
+ AppStateCommand::SetSettingsPreference {
preference,
enabled,
} => {
projection
+ .shell
.settings
.general
.set_preference(preference, enabled);
}
+ AppStateCommand::ReplaceTodayAgenda(today_projection) => {
+ projection.today = today_projection;
+ }
}
- *projection != before
+ if *projection == before {
+ AppStateMutation::NoChange
+ } else if projection.shell != before.shell {
+ AppStateMutation::ShellChanged
+ } else {
+ AppStateMutation::TodayChanged
+ }
}
#[cfg(test)]
mod tests {
use super::{
- AppShellCommand, AppShellProjection, AppStateRepository, AppStateRepositoryError,
- AppStateStore, AppStateStoreError, InMemoryAppStateRepository, SettingsPreference,
+ AppProjection, AppShellProjection, AppStateCommand, AppStateRepository,
+ AppStateRepositoryError, AppStateStore, AppStateStoreError, InMemoryAppStateRepository,
+ SettingsPreference,
+ };
+ use radroots_app_models::{
+ AppMode, FarmerSection, SettingsSection, ShellSection, TodayAgendaProjection,
+ TodaySetupTask, TodaySetupTaskKind,
};
- use radroots_app_models::{AppMode, SettingsSection, ShellSection};
struct FailingRepository;
@@ -308,18 +382,19 @@ mod tests {
#[test]
fn default_projection_starts_on_farmer_home() {
- let projection = AppShellProjection::default();
+ let projection = AppProjection::default();
- assert_eq!(projection.app_mode, AppMode::Farmer);
- assert_eq!(projection.selected_section, ShellSection::Home);
+ assert_eq!(projection.shell.app_mode, AppMode::Farmer);
+ assert_eq!(projection.shell.selected_section, ShellSection::Home);
assert_eq!(
- projection.settings.selected_section,
+ projection.shell.settings.selected_section,
SettingsSection::Account
);
- assert!(projection.settings.general.allow_relay_connections);
- assert!(projection.settings.general.use_media_servers);
- assert!(projection.settings.general.use_nip05);
- assert!(!projection.settings.general.launch_at_login);
+ assert!(projection.shell.settings.general.allow_relay_connections);
+ assert!(projection.shell.settings.general.use_media_servers);
+ assert!(projection.shell.settings.general.use_nip05);
+ assert!(!projection.shell.settings.general.launch_at_login);
+ assert_eq!(projection.today, TodayAgendaProjection::default());
}
#[test]
@@ -329,39 +404,64 @@ mod tests {
));
let store = AppStateStore::load(repository).expect("in-memory repository should load");
- assert_eq!(store.projection().app_mode, AppMode::Farmer);
+ assert_eq!(store.projection().shell.app_mode, AppMode::Farmer);
assert_eq!(
- store.projection().selected_section,
+ store.projection().shell.selected_section,
ShellSection::Settings(SettingsSection::About)
);
assert_eq!(
- store.projection().settings.selected_section,
+ store.projection().shell.settings.selected_section,
SettingsSection::About
);
+ assert_eq!(store.projection().today, TodayAgendaProjection::default());
}
#[test]
- fn select_settings_section_updates_projection_and_repository() {
+ fn select_settings_section_updates_shared_settings_without_clobbering_home() {
let mut store = AppStateStore::load(InMemoryAppStateRepository::default())
.expect("in-memory repository should load");
- let changed = store.apply(AppShellCommand::select_settings_section(
+ let changed = store.apply(AppStateCommand::select_settings_section(
SettingsSection::Settings,
));
assert_eq!(changed, Ok(true));
- assert_eq!(store.projection().app_mode, AppMode::Farmer);
+ assert_eq!(store.projection().shell.app_mode, AppMode::Farmer);
assert_eq!(
- store.projection().selected_section,
- ShellSection::Settings(SettingsSection::Settings)
+ store.projection().shell.selected_section,
+ ShellSection::Home
);
assert_eq!(
- store.projection().settings.selected_section,
+ store.projection().shell.settings.selected_section,
SettingsSection::Settings
);
assert_eq!(
store.repository().projection().selected_section,
- ShellSection::Settings(SettingsSection::Settings)
+ ShellSection::Home
+ );
+ assert_eq!(
+ store.repository().projection().settings.selected_section,
+ SettingsSection::Settings
+ );
+ }
+
+ #[test]
+ fn select_section_still_updates_the_root_shell() {
+ let mut store = AppStateStore::load(InMemoryAppStateRepository::default())
+ .expect("in-memory repository should load");
+
+ let changed = store.apply(AppStateCommand::SelectSection(ShellSection::Farmer(
+ FarmerSection::Products,
+ )));
+
+ assert_eq!(changed, Ok(true));
+ assert_eq!(
+ store.projection().shell.selected_section,
+ ShellSection::Farmer(FarmerSection::Products)
+ );
+ assert_eq!(
+ store.repository().projection().selected_section,
+ ShellSection::Farmer(FarmerSection::Products)
);
}
@@ -370,13 +470,13 @@ mod tests {
let mut store = AppStateStore::load(InMemoryAppStateRepository::default())
.expect("in-memory repository should load");
- let changed = store.apply(AppShellCommand::SetSettingsPreference {
+ let changed = store.apply(AppStateCommand::SetSettingsPreference {
preference: SettingsPreference::UseNip05,
enabled: true,
});
assert_eq!(changed, Ok(false));
- assert!(store.projection().settings.general.use_nip05);
+ assert!(store.projection().shell.settings.general.use_nip05);
}
#[test]
@@ -384,13 +484,13 @@ mod tests {
let mut store = AppStateStore::load(InMemoryAppStateRepository::default())
.expect("in-memory repository should load");
- let changed = store.apply(AppShellCommand::SetSettingsPreference {
+ let changed = store.apply(AppStateCommand::SetSettingsPreference {
preference: SettingsPreference::LaunchAtLogin,
enabled: true,
});
assert_eq!(changed, Ok(true));
- assert!(store.projection().settings.general.launch_at_login);
+ assert!(store.projection().shell.settings.general.launch_at_login);
assert!(
store
.repository()
@@ -407,7 +507,7 @@ mod tests {
AppStateStore::load(FailingRepository).expect("failing repository should still load");
let error = store
- .apply(AppShellCommand::select_settings_section(
+ .apply(AppStateCommand::select_settings_section(
SettingsSection::About,
))
.expect_err("save should fail");
@@ -419,17 +519,42 @@ mod tests {
}
#[test]
+ fn replace_today_agenda_updates_in_memory_state_without_touching_repository() {
+ let mut store =
+ AppStateStore::load(FailingRepository).expect("failing repository should still load");
+ let today = TodayAgendaProjection {
+ setup_checklist: vec![TodaySetupTask {
+ kind: TodaySetupTaskKind::AddFulfillmentWindow,
+ is_complete: false,
+ }],
+ ..TodayAgendaProjection::default()
+ };
+
+ let changed = store.apply(AppStateCommand::replace_today_agenda(today.clone()));
+
+ assert_eq!(changed, Ok(true));
+ assert_eq!(store.projection().today, today);
+ }
+
+ #[test]
fn in_memory_store_construction_and_updates_are_infallible() {
let mut store =
AppStateStore::in_memory(AppShellProjection::for_settings(SettingsSection::Account));
- let changed = store.apply_in_memory(AppShellCommand::SetSettingsPreference {
+ let changed = store.apply_in_memory(AppStateCommand::SetSettingsPreference {
preference: SettingsPreference::AllowRelayConnections,
enabled: false,
});
assert!(changed);
- assert!(!store.projection().settings.general.allow_relay_connections);
+ assert!(
+ !store
+ .projection()
+ .shell
+ .settings
+ .general
+ .allow_relay_connections
+ );
assert!(
!store
.repository()