v0.3.8-pre.006

This commit is contained in:
2026-09-03 16:26:18 +02:00
parent 62d6cf9dac
commit 3a38914be7
36 changed files with 1300 additions and 167 deletions

View File

@@ -1,18 +1,20 @@
// file: crates/ksp-app-store-desk/src/app_state.rs
// version: 1
// version: 2
//! Shared backend state owned by the Store Desk Tauri scaffold.
//! Shared backend state owned by the Store Desk Tauri application.
/// Shared Store Desk application state managed by Tauri.
pub(crate) struct AppState {
config_management: ksp_config_lib::ConfigManagement,
logging_runtime: std::sync::Mutex<LoggingRuntimeState>,
shutdown_started: std::sync::atomic::AtomicBool,
splash_settings: crate::SplashSettings,
splash_sequence_started: std::sync::atomic::AtomicBool,
store_startup: crate::StoreStartup,
}
impl crate::AppState {
/// Initializes Config ownership, transient standard Logging and common splash settings.
/// Initializes Config ownership, composite-managed Logging, Store readiness and common splash settings.
pub(crate) fn initialize(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<Self> {
let config_management = crate::config_management(arguments);
let config_management = match config_management {
@@ -29,6 +31,7 @@ impl crate::AppState {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let store_startup = tauri::async_runtime::block_on(crate::initialize_store(&config_management));
let splash_settings = crate::SplashSettings::load();
let splash_settings = match splash_settings {
std::result::Result::Ok(value) => value,
@@ -63,12 +66,14 @@ impl crate::AppState {
fallback_active: logging_startup.fallback_active,
startup_diagnostic: logging_startup.startup_diagnostic,
}),
shutdown_started: std::sync::atomic::AtomicBool::new(false),
splash_settings,
splash_sequence_started: std::sync::atomic::AtomicBool::new(false),
store_startup,
});
}
/// Builds the safe runtime status exposed by the Store Desk scaffold shell.
/// Builds the safe shell/Config/Logging status exposed by Store Desk diagnostics.
pub(crate) fn shell_status(&self) -> ksp_core_lib::Result<crate::ShellStatusDto> {
let document_count = self.config_management.engine().registry().descriptors().count();
let document_count = u32::try_from(document_count);
@@ -100,11 +105,16 @@ impl crate::AppState {
application_version: env!("CARGO_PKG_VERSION").to_owned(),
config_document_count: document_count,
fallback_logging_active: runtime.fallback_active,
shell_phase: "pre.002-scaffold".to_owned(),
shell_phase: "pre.006-store-overview".to_owned(),
startup_diagnostic: runtime.startup_diagnostic.clone(),
});
}
/// Builds one fresh safe Store runtime/health projection for the Overview and Diagnostics screens.
pub(crate) async fn store_runtime_status(&self) -> crate::StoreRuntimeStatusDto {
return self.store_startup.status().await;
}
/// Returns the resolved splash timings captured during application bootstrap.
#[must_use]
pub(crate) const fn splash_settings(&self) -> crate::SplashSettings {
@@ -118,6 +128,16 @@ impl crate::AppState {
.compare_exchange(false, true, std::sync::atomic::Ordering::AcqRel, std::sync::atomic::Ordering::Acquire)
.is_ok();
}
/// Marks graceful application shutdown as started and reports whether this caller won the one-shot transition.
pub(crate) fn begin_shutdown(&self) -> bool {
return self.shutdown_started.compare_exchange(false, true, std::sync::atomic::Ordering::AcqRel, std::sync::atomic::Ordering::Acquire).is_ok();
}
/// Explicitly closes the retained Store runtime when application shutdown begins.
pub(crate) async fn close_store(&self) -> ksp_core_lib::Result<()> {
return self.store_startup.close().await;
}
}
struct LoggingRuntimeState {

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-app-store-desk/src/bootstrap.rs
// version: 2
// version: 3
//! Standard Config and verbose development Logging bootstrap for Store Desk.
//! Composite Config and verbose development Logging bootstrap for Store Desk.
/// Crate-internal Logging startup state shared by the application state.
pub(crate) struct LoggingStartup {
@@ -31,7 +31,48 @@ pub(crate) fn config_management(arguments: &[std::ffi::OsString]) -> ksp_core_li
return std::result::Result::Ok(ksp_config_lib::ConfigManagement::new(engine));
}
/// Initializes development Logging from the standard `supertrace` profile, with a trace-level in-memory fallback.
/// Loads the concrete Store Desk Config composite using its registered logical `file_id` and autonomous default profile.
pub(crate) fn load_store_desk_composite(management: &ksp_config_lib::ConfigManagement) -> ksp_core_lib::Result<ksp_config_lib::ResolvedConfigComposite> {
let file_id = ksp_config_lib::ConfigFileId::new(ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_STORE_DESK);
let file_id = match file_id {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return management.engine().load_resolved_composite(&file_id, std::option::Option::None);
}
/// Returns one required standard profile from the Store Desk composite after validating the referenced logical `file_id`.
pub(crate) fn required_composite_component_profile(
composite: &ksp_config_lib::ResolvedConfigComposite,
component_id: &str,
expected_file_id: &str,
) -> ksp_core_lib::Result<ksp_config_lib::ResolvedConfigProfile> {
let component = composite.component(component_id);
let component = match component {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(
ksp_core_lib::Error::new(crate::ERROR_CODE_CONFIG_COMPOSITE_INVALID, "Store Desk composite is missing a required component")
.with_context("composite_file_id", composite.file_id().as_str())
.with_context("composite_profile_id", composite.profile_id())
.with_context("component_id", component_id),
);
},
};
if component.resolved().file_id().as_str() != expected_file_id {
return std::result::Result::Err(
ksp_core_lib::Error::new(crate::ERROR_CODE_CONFIG_COMPOSITE_INVALID, "Store Desk composite component references an unexpected Config document")
.with_context("composite_file_id", composite.file_id().as_str())
.with_context("composite_profile_id", composite.profile_id())
.with_context("component_id", component_id)
.with_context("expected_file_id", expected_file_id)
.with_context("actual_file_id", component.resolved().file_id().as_str()),
);
}
return std::result::Result::Ok(component.resolved().clone());
}
/// Initializes development Logging from the Store Desk composite, with a trace-level in-memory fallback.
pub(crate) fn initialize_logging(
management: &ksp_config_lib::ConfigManagement,
runtime_identity: &ksp_logging_lib::LoggingRuntimeIdentity,
@@ -41,7 +82,29 @@ pub(crate) fn initialize_logging(
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return initialize_fallback_logging(error, runtime_identity),
};
let resolved = management.engine().load_resolved_logging_config(std::option::Option::Some(crate::DEVELOPMENT_LOGGING_PROFILE), &environment);
let composite = crate::load_store_desk_composite(management);
let composite = match composite {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return initialize_fallback_logging(error, runtime_identity),
};
let logging_profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_LOGGING, ksp_config_lib::FILE_ID_STD_LOGGING);
let logging_profile = match logging_profile {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return initialize_fallback_logging(error, runtime_identity),
};
let store_profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_STORE, ksp_config_lib::FILE_ID_STD_STORE);
if let std::result::Result::Err(error) = store_profile {
return initialize_fallback_logging(error, runtime_identity);
}
if logging_profile.profile_id() != crate::DEVELOPMENT_LOGGING_PROFILE {
let error =
ksp_core_lib::Error::new(crate::ERROR_CODE_CONFIG_COMPOSITE_INVALID, "Store Desk development composite must select the supertrace Logging profile")
.with_context("component_id", crate::COMPOSITE_COMPONENT_ID_LOGGING)
.with_context("expected_profile_id", crate::DEVELOPMENT_LOGGING_PROFILE)
.with_context("actual_profile_id", logging_profile.profile_id());
return initialize_fallback_logging(error, runtime_identity);
}
let resolved = management.engine().resolve_logging_config_profile(&logging_profile, &environment);
let resolved = match resolved {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return initialize_fallback_logging(error, runtime_identity),
@@ -55,7 +118,8 @@ pub(crate) fn initialize_logging(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_BOOTSTRAP,
active_profile = active_profile_id.as_str(),
"initialized Store Desk development logging from explicit supertrace Config profile"
composite_profile = composite.profile_id(),
"initialized Store Desk development logging from composite-managed supertrace configuration"
);
std::result::Result::Ok(crate::LoggingStartup {
guard,
@@ -96,7 +160,7 @@ fn initialize_fallback_logging(
domain = crate::TRACING_DOMAIN_BOOTSTRAP,
error_domain = diagnostic.domain.as_str(),
error_code = diagnostic.code.as_str(),
"Store Desk scaffold uses transient fallback Logging"
"Store Desk uses transient trace-level fallback Logging"
);
return std::result::Result::Ok(crate::LoggingStartup {
guard,

View File

@@ -1,8 +1,12 @@
// file: crates/ksp-app-store-desk/src/constants.rs
// version: 2
// version: 3
//! Application-owned tracing targets and domains.
//! Application-owned tracing targets, domains and composite component identifiers.
/// Composite component identifier for Logging.
pub(crate) const COMPOSITE_COMPONENT_ID_LOGGING: &str = "logging";
/// Composite component identifier for Store.
pub(crate) const COMPOSITE_COMPONENT_ID_STORE: &str = "store";
/// Logging profile used while Store Desk is under active development.
pub(crate) const DEVELOPMENT_LOGGING_PROFILE: &str = "supertrace";
/// Structured domain used while bootstrapping Config and Logging.
@@ -11,6 +15,8 @@ pub(crate) const TRACING_DOMAIN_BOOTSTRAP: &str = "store.bootstrap";
pub(crate) const TRACING_DOMAIN_FRONTEND: &str = "frontend";
/// Structured domain used by the Store Desk shell.
pub(crate) const TRACING_DOMAIN_SHELL: &str = "store.shell";
/// Structured domain used by Store readiness, inspection admission and shutdown operations.
pub(crate) const TRACING_DOMAIN_STORE: &str = "store.runtime";
/// Structured domain used by Tauri window lifecycle operations.
pub(crate) const TRACING_DOMAIN_WINDOWS: &str = "desktop.window";
/// Owning target for backend events emitted by Store Desk.

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-app-store-desk/src/dto_common.rs
// version: 1
// version: 2
//! Common Tauri DTOs shared by the Store Desk scaffold shell.
//! Common Tauri DTOs shared by the Store Desk shell and Store runtime Overview.
use ts_rs::TS; // rust-rules: trait-import
@@ -30,7 +30,7 @@ impl crate::CommandErrorDto {
}
}
/// Safe scaffold/runtime snapshot exposed to the Store Desk shell.
/// Safe shell/Config/Logging snapshot exposed to Store Desk diagnostics.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_store_desk/dto_common/ShellStatusDto.ts")]
@@ -49,6 +49,37 @@ pub(crate) struct ShellStatusDto {
pub(crate) startup_diagnostic: std::option::Option<CommandErrorDto>,
}
/// Backend-neutral Store runtime and health projection used by the Overview screen.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_store_desk/dto_common/StoreRuntimeStatusDto.ts")]
pub(crate) struct StoreRuntimeStatusDto {
/// Stable selected Store backend kind, when configuration resolution reached that phase.
pub(crate) backend_kind: std::option::Option<String>,
/// Safe Store startup/health diagnostic, when present.
pub(crate) diagnostic: std::option::Option<CommandErrorDto>,
/// Portable Store health code (`ready`, `not_ready`, `unavailable`, `closed`, or `unknown`).
pub(crate) health_state: String,
/// Applied migration version rendered as an exact decimal string.
pub(crate) migration_version_decimal: std::option::Option<String>,
/// Logical Store network, when configuration resolution reached that phase.
pub(crate) network: std::option::Option<String>,
/// Number of embedded migrations newer than the applied migration version.
pub(crate) pending_migration_count: u32,
/// Number of currently available pooled backend objects.
pub(crate) pool_available: u32,
/// Configured pool capacity.
pub(crate) pool_capacity: u32,
/// Current pool size.
pub(crate) pool_size: u32,
/// Number of tasks currently waiting for pool capacity.
pub(crate) pool_waiting: u32,
/// Store profile selected by the Store Desk composite.
pub(crate) profile_id: std::option::Option<String>,
/// Whether the application currently retains an opened Store facade.
pub(crate) store_open: bool,
}
#[cfg(test)]
#[path = "../unit_tests/dto_common.rs"]
mod tests;

View File

@@ -1,12 +1,14 @@
// file: crates/ksp-app-store-desk/src/errors.rs
// version: 1
// version: 2
//! Application-local error codes for Store Desk scaffold and desktop runtime surfaces.
//! Application-local error codes for Store Desk desktop runtime surfaces.
/// Shared Store Desk runtime state is internally inconsistent.
pub(crate) const ERROR_CODE_APP_STATE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_desk", "app_state_invalid");
/// Shared Store Desk runtime state cannot be locked safely.
pub(crate) const ERROR_CODE_APP_STATE_LOCK_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_desk", "app_state_lock_failed");
/// Store Desk composite configuration is missing or references an unexpected document/profile.
pub(crate) const ERROR_CODE_CONFIG_COMPOSITE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_desk", "config_composite_invalid");
/// Frontend logging requested an unsupported level.
pub(crate) const ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_desk", "frontend_log_level_invalid");
/// Frontend logging requested a target outside the application whitelist.
@@ -17,6 +19,8 @@ pub(crate) const ERROR_CODE_LOGGING_BOOTSTRAP_FAILED: ksp_core_lib::ErrorCode =
pub(crate) const ERROR_CODE_SPLASH_ORIGIN_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_desk", "splash_origin_invalid");
/// A KSP desk splash environment duration is malformed or exceeds its safety bound.
pub(crate) const ERROR_CODE_SPLASH_SETTING_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_desk", "splash_setting_invalid");
/// Store Desk cannot complete the bounded Store shutdown lifecycle.
pub(crate) const ERROR_CODE_STORE_SHUTDOWN_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_desk", "store_shutdown_failed");
/// Tauri runtime assembly or execution failed.
pub(crate) const ERROR_CODE_TAURI_RUNTIME_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_desk", "tauri_runtime_failed");
/// A required Tauri window is missing from the configured application runtime.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-store-desk/src/lib.rs
// version: 2
// version: 3
//! Tauri desktop scaffold for backend-neutral KSP Store inspection.
@@ -15,6 +15,7 @@ mod errors;
mod frontend_logging;
mod logging_runtime;
mod splash;
mod store_runtime;
mod tauri;
mod tw_main;
mod tw_splash;
@@ -28,8 +29,16 @@ pub(crate) use self::app_state::AppState;
pub(crate) use self::bootstrap::LoggingStartup;
/// Builds the Config management facade from the common KSP CLI bootstrap contract.
pub(crate) use self::bootstrap::config_management;
/// Initializes verbose development Logging from the standard Config document.
/// Initializes verbose development Logging from the Store Desk composite.
pub(crate) use self::bootstrap::initialize_logging;
/// Loads the concrete Store Desk Config composite.
pub(crate) use self::bootstrap::load_store_desk_composite;
/// Returns one required standard component profile from the Store Desk composite.
pub(crate) use self::bootstrap::required_composite_component_profile;
/// Composite component identifier for Logging.
pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_LOGGING;
/// Composite component identifier for Store.
pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_STORE;
/// Logging profile used while Store Desk is under active development.
pub(crate) use self::constants::DEVELOPMENT_LOGGING_PROFILE;
/// Structured domain used while bootstrapping Config and Logging.
@@ -38,6 +47,8 @@ pub(crate) use self::constants::TRACING_DOMAIN_BOOTSTRAP;
pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND;
/// Structured domain used by the Store Desk shell.
pub(crate) use self::constants::TRACING_DOMAIN_SHELL;
/// Structured domain used by Store readiness and shutdown operations.
pub(crate) use self::constants::TRACING_DOMAIN_STORE;
/// Structured domain used by Tauri window lifecycle operations.
pub(crate) use self::constants::TRACING_DOMAIN_WINDOWS;
/// Owning target for backend events emitted by Store Desk.
@@ -50,12 +61,16 @@ pub(crate) use self::constants::TRACING_TARGET_FRONTEND_MAIN;
pub(crate) use self::constants::TRACING_TARGET_FRONTEND_SPLASH;
/// Safe command error projection exposed to Tauri commands.
pub(crate) use self::dto_common::CommandErrorDto;
/// Safe scaffold/runtime snapshot exposed to the shell.
/// Safe shell/Config/Logging snapshot exposed to the shell.
pub(crate) use self::dto_common::ShellStatusDto;
/// Backend-neutral Store runtime and health projection exposed to Overview.
pub(crate) use self::dto_common::StoreRuntimeStatusDto;
/// Shared Store Desk runtime state is internally inconsistent.
pub(crate) use self::errors::ERROR_CODE_APP_STATE_INVALID;
/// Shared Store Desk runtime state cannot be locked safely.
pub(crate) use self::errors::ERROR_CODE_APP_STATE_LOCK_FAILED;
/// Store Desk composite configuration is missing or references an unexpected document/profile.
pub(crate) use self::errors::ERROR_CODE_CONFIG_COMPOSITE_INVALID;
/// Frontend logging requested an unsupported level.
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID;
/// Frontend logging requested a target outside the application whitelist.
@@ -66,6 +81,8 @@ pub(crate) use self::errors::ERROR_CODE_LOGGING_BOOTSTRAP_FAILED;
pub(crate) use self::errors::ERROR_CODE_SPLASH_ORIGIN_INVALID;
/// A KSP desk splash environment duration is malformed or exceeds its safety bound.
pub(crate) use self::errors::ERROR_CODE_SPLASH_SETTING_INVALID;
/// Store Desk cannot complete the bounded Store shutdown lifecycle.
pub(crate) use self::errors::ERROR_CODE_STORE_SHUTDOWN_FAILED;
/// Tauri runtime assembly or execution failed.
pub(crate) use self::errors::ERROR_CODE_TAURI_RUNTIME_FAILED;
/// A required Tauri window is missing from the configured application runtime.
@@ -82,6 +99,12 @@ pub(crate) use self::logging_runtime::launch_identity;
pub(crate) use self::splash::SplashOrderDto;
/// Runtime timings used by the common desk splash lifecycle.
pub(crate) use self::splash::SplashSettings;
/// Store runtime retained behind the Store Desk lifecycle boundary.
pub(crate) use self::store_runtime::StoreRuntime;
/// Store startup state retained by the application shell.
pub(crate) use self::store_runtime::StoreStartup;
/// Initializes Store from the Store Desk composite.
pub(crate) use self::store_runtime::initialize_store;
/// Resolves the required main window or returns a typed error.
pub(crate) use self::tw_main::require_main_window;
/// Shows and focuses the main Store Desk window.

View File

@@ -0,0 +1,249 @@
// file: crates/ksp-app-store-desk/src/store_runtime.rs
// version: 1
//! Composite-selected Store readiness, status and bounded shutdown lifecycle owned by Store Desk.
/// Startup result for Store, including safe diagnostics when the desktop shell remains available without an opened runtime.
pub(crate) struct StoreStartup {
backend_kind: std::option::Option<String>,
diagnostic: std::option::Option<crate::CommandErrorDto>,
network: std::option::Option<String>,
profile_id: std::option::Option<String>,
runtime: std::option::Option<StoreRuntime>,
}
impl StoreStartup {
/// Builds a fresh backend-neutral runtime/health projection for the Overview screen.
pub(crate) async fn status(&self) -> crate::StoreRuntimeStatusDto {
let runtime = self.runtime.as_ref();
return match runtime {
std::option::Option::Some(value) => value.status().await,
std::option::Option::None => crate::StoreRuntimeStatusDto {
backend_kind: self.backend_kind.clone(),
diagnostic: self.diagnostic.clone(),
health_state: "unavailable".to_owned(),
migration_version_decimal: std::option::Option::None,
network: self.network.clone(),
pending_migration_count: 0,
pool_available: 0,
pool_capacity: 0,
pool_size: 0,
pool_waiting: 0,
profile_id: self.profile_id.clone(),
store_open: false,
},
};
}
/// Closes the retained Store runtime if startup reached the physical Store-open phase.
pub(crate) async fn close(&self) -> ksp_core_lib::Result<()> {
let runtime = self.runtime.as_ref();
return match runtime {
std::option::Option::Some(value) => value.close().await,
std::option::Option::None => std::result::Result::Ok(()),
};
}
}
/// Executable Store runtime retained behind an async read/write lock so read-only Desk queries may coexist while shutdown remains exclusive.
pub(crate) struct StoreRuntime {
profile_id: String,
store: tokio::sync::RwLock<std::option::Option<ksp_store_lib::Store>>,
}
impl StoreRuntime {
/// Builds one fresh safe Store status from the retained facade without exposing backend connection details.
pub(crate) async fn status(&self) -> crate::StoreRuntimeStatusDto {
let locked = self.store.read().await;
let store = locked.as_ref();
let store = match store {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return crate::StoreRuntimeStatusDto {
backend_kind: std::option::Option::None,
diagnostic: std::option::Option::None,
health_state: "closed".to_owned(),
migration_version_decimal: std::option::Option::None,
network: std::option::Option::None,
pending_migration_count: 0,
pool_available: 0,
pool_capacity: 0,
pool_size: 0,
pool_waiting: 0,
profile_id: std::option::Option::Some(self.profile_id.clone()),
store_open: false,
};
},
};
let runtime = store.runtime_snapshot();
let health = store.health().await;
let health_state = store_health_state_code(health.state()).to_owned();
let diagnostic = non_ready_health_diagnostic(&health);
return crate::StoreRuntimeStatusDto {
backend_kind: std::option::Option::Some(runtime.backend_kind().code().to_owned()),
diagnostic,
health_state,
migration_version_decimal: health.migration_version().map(|value| return value.to_string()),
network: std::option::Option::Some(runtime.network().as_str().to_owned()),
pending_migration_count: health.pending_migration_count(),
pool_available: runtime.pool_available(),
pool_capacity: runtime.pool_capacity(),
pool_size: runtime.pool_size(),
pool_waiting: runtime.pool_waiting(),
profile_id: std::option::Option::Some(self.profile_id.clone()),
store_open: true,
};
}
/// Explicitly closes the Store exactly once through its backend-neutral facade after all read guards have drained.
pub(crate) async fn close(&self) -> ksp_core_lib::Result<()> {
let mut locked = self.store.write().await;
let store = locked.take();
drop(locked);
let store = match store {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::result::Result::Ok(()),
};
let snapshot = store.runtime_snapshot();
let backend_kind = snapshot.backend_kind().code().to_owned();
let network = snapshot.network().as_str().to_owned();
let closed = store.close().await;
return match closed {
std::result::Result::Ok(()) => {
ksp_logging_lib::debug!(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_STORE,
store_profile = self.profile_id.as_str(),
backend = backend_kind.as_str(),
network = network.as_str(),
"closed Store Desk Store runtime"
);
std::result::Result::Ok(())
},
std::result::Result::Err(error) => std::result::Result::Err(
ksp_core_lib::Error::new(crate::ERROR_CODE_STORE_SHUTDOWN_FAILED, "Store Desk Store shutdown failed").with_source(error),
),
};
}
}
/// Resolves the composite Store target, opens Store through the facade and captures one initial readiness probe.
pub(crate) async fn initialize_store(management: &ksp_config_lib::ConfigManagement) -> StoreStartup {
let environment = ksp_config_lib::ConfigEnvironment::load();
let environment = match environment {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return unavailable_startup(std::option::Option::None, std::option::Option::None, std::option::Option::None, error),
};
let composite = crate::load_store_desk_composite(management);
let composite = match composite {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return unavailable_startup(std::option::Option::None, std::option::Option::None, std::option::Option::None, error),
};
let profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_STORE, ksp_config_lib::FILE_ID_STD_STORE);
let profile = match profile {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return unavailable_startup(
std::option::Option::Some(composite.profile_id().to_owned()),
std::option::Option::None,
std::option::Option::None,
error,
);
},
};
let resolved = management.engine().resolve_store_config_profile(&profile, &environment);
let resolved = match resolved {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return unavailable_startup(
std::option::Option::Some(profile.profile_id().to_owned()),
std::option::Option::None,
std::option::Option::None,
error,
);
},
};
let profile_id = resolved.profile_id().to_owned();
let backend_kind = resolved.settings().backend_kind().code().to_owned();
let network = resolved.settings().network().as_str().to_owned();
let store = ksp_store_lib::Store::open(resolved.into_settings()).await;
let store = match store {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return unavailable_startup(
std::option::Option::Some(profile_id),
std::option::Option::Some(backend_kind),
std::option::Option::Some(network),
error,
);
},
};
let health = store.health().await;
ksp_logging_lib::debug!(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_STORE,
store_profile = profile_id.as_str(),
backend = backend_kind.as_str(),
network = network.as_str(),
health_state = store_health_state_code(health.state()),
pending_migration_count = health.pending_migration_count(),
"initialized Store Desk Store from composite-managed configuration"
);
return StoreStartup {
backend_kind: std::option::Option::Some(backend_kind),
diagnostic: non_ready_health_diagnostic(&health),
network: std::option::Option::Some(network),
profile_id: std::option::Option::Some(profile_id.clone()),
runtime: std::option::Option::Some(StoreRuntime { profile_id, store: tokio::sync::RwLock::new(std::option::Option::Some(store)) }),
};
}
fn non_ready_health_diagnostic(health: &ksp_store_lib::StoreHealthSnapshot) -> std::option::Option<crate::CommandErrorDto> {
if matches!(health.state(), ksp_store_lib::StoreHealthState::Ready) {
return std::option::Option::None;
}
let code = health.last_error_code();
return match code {
std::option::Option::Some(value) => std::option::Option::Some(crate::CommandErrorDto {
code: value.code().to_owned(),
domain: value.domain().to_owned(),
message: "Store health probe did not prove readiness".to_owned(),
}),
std::option::Option::None => std::option::Option::Some(crate::CommandErrorDto {
code: "store_not_ready".to_owned(),
domain: "store_desk".to_owned(),
message: "Store health probe did not prove readiness".to_owned(),
}),
};
}
fn store_health_state_code(state: ksp_store_lib::StoreHealthState) -> &'static str {
return match state {
ksp_store_lib::StoreHealthState::Ready => "ready",
ksp_store_lib::StoreHealthState::NotReady => "not_ready",
_ => "unknown",
};
}
fn unavailable_startup(
profile_id: std::option::Option<String>,
backend_kind: std::option::Option<String>,
network: std::option::Option<String>,
error: ksp_core_lib::Error,
) -> StoreStartup {
let diagnostic = crate::CommandErrorDto::from_error(&error);
ksp_logging_lib::warn!(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_STORE,
error_domain = diagnostic.domain.as_str(),
error_code = diagnostic.code.as_str(),
"Store Desk Store readiness is unavailable; keeping desktop shell available"
);
return StoreStartup {
backend_kind,
diagnostic: std::option::Option::Some(diagnostic),
network,
profile_id,
runtime: std::option::Option::None,
};
}

View File

@@ -1,12 +1,18 @@
// file: crates/ksp-app-store-desk/src/tauri.rs
// version: 1
// version: 2
//! Tauri runtime assembly for the KSP Store desktop scaffold.
//! Tauri runtime assembly for the KSP Store desktop application.
use tauri::Manager; // rust-rules: trait-import
/// Runs the Store desktop application.
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<()> {
let context = tauri::generate_context!();
let runtime_layout = configure_packaged_runtime(&context);
if let std::result::Result::Err(error) = runtime_layout {
return std::result::Result::Err(error);
}
let app_state = crate::AppState::initialize(arguments);
let app_state = match app_state {
std::result::Result::Ok(value) => value,
@@ -17,6 +23,7 @@ pub fn run(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<()> {
builder = configure_plugins(builder);
builder = configure_commands(builder);
builder = configure_setup(builder);
builder = configure_window_events(builder);
let run_result = builder.run(context);
return match run_result {
std::result::Result::Ok(()) => std::result::Result::Ok(()),
@@ -27,6 +34,36 @@ pub fn run(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<()> {
};
}
fn configure_packaged_runtime(context: &tauri::Context<tauri::Wry>) -> ksp_core_lib::Result<()> {
if cfg!(debug_assertions) {
return std::result::Result::Ok(());
}
let resource_root = tauri::utils::platform::resource_dir(context.package_info(), &tauri::Env::default());
let resource_root = match resource_root {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(
ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_RUNTIME_FAILED, "Cannot resolve packaged Tauri resource directory")
.with_context("tauri_error", error.to_string()),
);
},
};
let layout = ksp_config_lib::prepare_packaged_runtime(resource_root.as_path());
let layout = match layout {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let working_directory = std::env::set_current_dir(layout.runtime_root());
return match working_directory {
std::result::Result::Ok(()) => std::result::Result::Ok(()),
std::result::Result::Err(error) => std::result::Result::Err(
ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_RUNTIME_FAILED, "Cannot activate packaged KSP runtime directory")
.with_context("runtime_root", layout.runtime_root().to_string_lossy().into_owned())
.with_source(error),
),
};
}
fn configure_state(builder: tauri::Builder<tauri::Wry>, app_state: crate::AppState) -> tauri::Builder<tauri::Wry> {
return builder.manage(app_state);
}
@@ -38,7 +75,7 @@ fn configure_plugins(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<taur
#[allow(clippy::question_mark_used)] // Tauri generates the question-mark operator internally for async command dispatch.
fn configure_commands(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
return builder.invoke_handler(tauri::generate_handler![emit_frontend_log, get_shell_status, splash_frontend_ready]);
return builder.invoke_handler(tauri::generate_handler![emit_frontend_log, get_shell_status, splash_frontend_ready, store_runtime_status]);
}
fn configure_setup(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
@@ -51,11 +88,55 @@ fn configure_setup(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri:
if let std::result::Result::Err(error) = main {
return std::result::Result::Err(std::boxed::Box::new(error));
}
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_SHELL, "Store Desk scaffold Tauri shell setup completed");
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_SHELL, "Store Desk Tauri shell setup completed with composite Store lifecycle");
return std::result::Result::Ok(());
});
}
fn configure_window_events(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
return builder.on_window_event(|window, event| {
if window.label() != "main" {
return;
}
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
api.prevent_close();
let app_handle = window.app_handle().clone();
let state = app_handle.state::<crate::AppState>();
if !state.begin_shutdown() {
return;
}
ksp_logging_lib::debug!(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_WINDOWS,
"Store Desk main-window close requested; starting bounded Store shutdown"
);
tauri::async_runtime::spawn(async move {
let state = app_handle.state::<crate::AppState>();
let closed = state.close_store().await;
match closed {
std::result::Result::Ok(()) => {
ksp_logging_lib::debug!(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_WINDOWS,
"Store Desk Store shutdown completed; exiting application"
);
},
std::result::Result::Err(error) => {
ksp_logging_lib::warn!(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_WINDOWS,
error_domain = error.code().domain(),
error_code = error.code().code(),
"Store Desk Store shutdown failed; exiting application after bounded close attempt"
);
},
}
app_handle.exit(0);
});
}
});
}
fn project_command_error(command: &'static str, domain: &'static str, error: &ksp_core_lib::Error) -> crate::CommandErrorDto {
ksp_logging_lib::warn!(
target: crate::TRACING_TARGET,
@@ -86,6 +167,11 @@ fn get_shell_status(state: tauri::State<'_, crate::AppState>) -> std::result::Re
};
}
#[tauri::command]
async fn store_runtime_status(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::StoreRuntimeStatusDto, crate::CommandErrorDto> {
return std::result::Result::Ok(state.store_runtime_status().await);
}
#[tauri::command]
async fn splash_frontend_ready(
app: tauri::AppHandle,