app

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

commit 76f987ecb6b4fb3334658731a32071471a9bc791
parent 7a800dff8d5989703b4f5955009ec177a376342a
Author: triesap <triesap@radroots.dev>
Date:   Mon, 19 Jan 2026 21:23:34 +0000

app: add tangle health check stub

- add tangle health check and report field
- include tangle status in aggregate health report
- render tangle status row in UI
- add unit tests for tangle health behavior

Diffstat:
Mapp/src/app.rs | 13+++++++++++++
Mapp/src/health.rs | 27++++++++++++++++++++++++++-
Mapp/src/lib.rs | 1+
3 files changed, 40 insertions(+), 1 deletion(-)

diff --git a/app/src/app.rs b/app/src/app.rs @@ -23,6 +23,7 @@ use crate::{ AppInitError, AppInitStage, AppNotifications, + AppTangleClientStub, }; fn health_status_color(status: AppHealthCheckStatus) -> &'static str { @@ -67,10 +68,12 @@ fn spawn_health_checks( Some(config.keystore.nostr_store), ); let notifications = AppNotifications::new(None); + let tangle = AppTangleClientStub::new(); let report = app_health_check_all( &datastore, &keystore, &notifications, + &tangle, &config.datastore.key_maps, ) .await; @@ -296,6 +299,16 @@ pub fn App() -> impl IntoView { <span style=move || format!( "display:inline-block;width:10px;height:10px;border-radius:50%;background:{};", + health_status_color(health_report.get().tangle.status) + ) + ></span> + <span>"tangle"</span> + <span>{move || health_result_label(&health_report.get().tangle)}</span> + </div> + <div style="display: flex; align-items: center; gap: 8px;"> + <span + style=move || format!( + "display:inline-block;width:10px;height:10px;border-radius:50%;background:{};", health_status_color(health_report.get().datastore_roundtrip.status) ) ></span> diff --git a/app/src/health.rs b/app/src/health.rs @@ -53,6 +53,7 @@ pub struct AppHealthReport { pub bootstrap_app_data: AppHealthCheckResult, pub app_data_active_key: AppHealthCheckResult, pub notifications: AppHealthCheckResult, + pub tangle: AppHealthCheckResult, pub datastore_roundtrip: AppHealthCheckResult, pub keystore: AppHealthCheckResult, } @@ -65,6 +66,7 @@ impl Default for AppHealthReport { bootstrap_app_data: AppHealthCheckResult::skipped(), app_data_active_key: AppHealthCheckResult::skipped(), notifications: AppHealthCheckResult::skipped(), + tangle: AppHealthCheckResult::skipped(), datastore_roundtrip: AppHealthCheckResult::skipped(), keystore: AppHealthCheckResult::skipped(), } @@ -84,6 +86,7 @@ use crate::{ app_datastore_read_app_data, app_key_maps_validate, AppNotifications, + AppTangleClient, AppKeyMapConfig, }; use radroots_app_core::datastore::{RadrootsClientDatastore, RadrootsClientDatastoreError}; @@ -159,6 +162,13 @@ pub async fn app_health_check_notifications( } } +pub fn app_health_check_tangle<T: AppTangleClient>(tangle: &T) -> AppHealthCheckResult { + match tangle.init() { + Ok(()) => AppHealthCheckResult::ok(), + Err(err) => AppHealthCheckResult::error(err.to_string()), + } +} + const APP_HEALTH_TEMP_KEY: &str = "radroots.health.temp"; pub async fn app_health_check_datastore_roundtrip<T: RadrootsClientDatastore>( @@ -205,10 +215,11 @@ pub async fn app_health_check_keystore_access<T: RadrootsClientDatastore, K: Rad } } -pub async fn app_health_check_all<T: RadrootsClientDatastore, K: RadrootsClientKeystoreNostr>( +pub async fn app_health_check_all<T: RadrootsClientDatastore, K: RadrootsClientKeystoreNostr, G: AppTangleClient>( datastore: &T, keystore: &K, notifications: &AppNotifications, + tangle: &G, key_maps: &AppKeyMapConfig, ) -> AppHealthReport { AppHealthReport { @@ -217,6 +228,7 @@ pub async fn app_health_check_all<T: RadrootsClientDatastore, K: RadrootsClientK bootstrap_app_data: app_health_check_bootstrap_app_data(datastore, key_maps).await, app_data_active_key: app_health_check_app_data_active_key(datastore, key_maps).await, notifications: app_health_check_notifications(notifications).await, + tangle: app_health_check_tangle(tangle), datastore_roundtrip: app_health_check_datastore_roundtrip(datastore).await, keystore: app_health_check_keystore_access(datastore, keystore, key_maps).await, } @@ -233,6 +245,7 @@ mod tests { app_health_check_datastore_roundtrip, app_health_check_keystore_access, app_health_check_notifications, + app_health_check_tangle, AppHealthCheckResult, AppHealthCheckStatus, AppHealthReport, @@ -282,6 +295,7 @@ mod tests { assert_eq!(report.bootstrap_app_data.status, AppHealthCheckStatus::Skipped); assert_eq!(report.app_data_active_key.status, AppHealthCheckStatus::Skipped); assert_eq!(report.notifications.status, AppHealthCheckStatus::Skipped); + assert_eq!(report.tangle.status, AppHealthCheckStatus::Skipped); assert_eq!(report.datastore_roundtrip.status, AppHealthCheckStatus::Skipped); assert_eq!(report.keystore.status, AppHealthCheckStatus::Skipped); } @@ -587,11 +601,13 @@ mod tests { let datastore = RadrootsClientWebDatastore::new(None); let keystore = RadrootsClientWebKeystoreNostr::new(None); let notifications = crate::AppNotifications::new(None); + let tangle = crate::AppTangleClientStub::new(); let key_maps = crate::app_key_maps_default(); let report = futures::executor::block_on(app_health_check_all( &datastore, &keystore, &notifications, + &tangle, &key_maps, )); assert_eq!(report.key_maps.status, AppHealthCheckStatus::Ok); @@ -599,6 +615,7 @@ mod tests { assert_eq!(report.bootstrap_app_data.status, AppHealthCheckStatus::Error); assert_eq!(report.app_data_active_key.status, AppHealthCheckStatus::Error); assert_eq!(report.notifications.status, AppHealthCheckStatus::Error); + assert_eq!(report.tangle.status, AppHealthCheckStatus::Error); assert_eq!(report.datastore_roundtrip.status, AppHealthCheckStatus::Error); assert_eq!(report.keystore.status, AppHealthCheckStatus::Error); } @@ -611,4 +628,12 @@ mod tests { assert_eq!(result.status, AppHealthCheckStatus::Error); assert_eq!(result.message.as_deref(), Some("unavailable")); } + + #[test] + fn health_check_tangle_reports_not_implemented() { + let tangle = crate::AppTangleClientStub::new(); + let result = app_health_check_tangle(&tangle); + assert_eq!(result.status, AppHealthCheckStatus::Error); + assert_eq!(result.message.as_deref(), Some("error.app.tangle.not_implemented")); + } } diff --git a/app/src/lib.rs b/app/src/lib.rs @@ -31,6 +31,7 @@ pub use health::{ app_health_check_datastore_roundtrip, app_health_check_keystore_access, app_health_check_notifications, + app_health_check_tangle, app_health_check_key_maps, AppHealthCheckResult, AppHealthCheckStatus,