v0.2.6-pre.014

This commit is contained in:
2026-08-22 00:21:14 +02:00
parent c07db925b1
commit 6907e4bbb8
39 changed files with 1544 additions and 370 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-config-desk/src/app_state.rs
// version: 8
// version: 9
//! Shared backend state owned by the Tauri application.
@@ -48,8 +48,10 @@ impl AppState {
domain = crate::TRACING_DOMAIN_WINDOWS,
minimum_ms = splash_settings.minimum_ms(),
minimum_source = splash_settings.minimum_source(),
fade_ms = splash_settings.fade_ms(),
fade_source = splash_settings.fade_source(),
fade_in_ms = splash_settings.fade_in_ms(),
fade_in_source = splash_settings.fade_in_source(),
fade_out_ms = splash_settings.fade_out_ms(),
fade_out_source = splash_settings.fade_out_source(),
expected_backend_lifecycle_ms = splash_settings.expected_backend_lifecycle_ms(),
"resolved Config Desk splash timings"
);

View File

@@ -1,13 +1,15 @@
// file: crates/ksp-app-config-desk/src/splash.rs
// version: 5
// version: 6
//! Common splash settings and frontend event contracts for Config Desk.
use ts_rs::TS; // rust-rules: trait-import
const DEFAULT_SPLASH_FADE_MS: u32 = 300;
const DEFAULT_SPLASH_FADE_IN_MS: u32 = 300;
const DEFAULT_SPLASH_FADE_OUT_MS: u32 = 300;
const DEFAULT_SPLASH_MINIMUM_MS: u64 = 1200;
const ENV_SPLASH_FADE_MS: &str = "KSP_DESK_SPLASH_FADE_MS";
const ENV_SPLASH_FADE_IN_MS: &str = "KSP_DESK_SPLASH_FADE_IN_MS";
const ENV_SPLASH_FADE_OUT_MS: &str = "KSP_DESK_SPLASH_FADE_OUT_MS";
const ENV_SPLASH_MINIMUM_MS: &str = "KSP_DESK_SPLASH_MINIMUM_MS";
const MAX_SPLASH_FADE_MS: u32 = 10_000;
const MAX_SPLASH_MINIMUM_MS: u64 = 60_000;
@@ -15,10 +17,12 @@ const MAX_SPLASH_MINIMUM_MS: u64 = 60_000;
/// Runtime timings used by the common desk splash lifecycle.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct SplashSettings {
fade_in_ms: u32,
minimum_ms: u64,
fade_ms: u32,
fade_out_ms: u32,
fade_in_source: ksp_config_lib::ConfigEnvironmentSource,
minimum_source: ksp_config_lib::ConfigEnvironmentSource,
fade_source: ksp_config_lib::ConfigEnvironmentSource,
fade_out_source: ksp_config_lib::ConfigEnvironmentSource,
}
impl SplashSettings {
@@ -29,13 +33,23 @@ impl SplashSettings {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let fade_in = environment.resolve_variable(ENV_SPLASH_FADE_IN_MS, std::option::Option::Some("300"));
let fade_in = match fade_in {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let minimum = environment.resolve_variable(ENV_SPLASH_MINIMUM_MS, std::option::Option::Some("1200"));
let minimum = match minimum {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let fade = environment.resolve_variable(ENV_SPLASH_FADE_MS, std::option::Option::Some("300"));
let fade = match fade {
let fade_out = environment.resolve_variable(ENV_SPLASH_FADE_OUT_MS, std::option::Option::Some("300"));
let fade_out = match fade_out {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let fade_in_ms = parse_u32_setting(ENV_SPLASH_FADE_IN_MS, fade_in.value(), MAX_SPLASH_FADE_MS);
let fade_in_ms = match fade_in_ms {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
@@ -44,35 +58,56 @@ impl SplashSettings {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let fade_ms = parse_u32_setting(ENV_SPLASH_FADE_MS, fade.value(), MAX_SPLASH_FADE_MS);
let fade_ms = match fade_ms {
let fade_out_ms = parse_u32_setting(ENV_SPLASH_FADE_OUT_MS, fade_out.value(), MAX_SPLASH_FADE_MS);
let fade_out_ms = match fade_out_ms {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(Self { minimum_ms, fade_ms, minimum_source: minimum.source(), fade_source: fade.source() });
return std::result::Result::Ok(Self {
fade_in_ms,
minimum_ms,
fade_out_ms,
fade_in_source: fade_in.source(),
minimum_source: minimum.source(),
fade_out_source: fade_out.source(),
});
}
/// Returns safe in-memory timings used when the managed environment cannot be resolved.
#[must_use]
pub(crate) const fn fallback() -> Self {
return Self {
fade_in_ms: DEFAULT_SPLASH_FADE_IN_MS,
minimum_ms: DEFAULT_SPLASH_MINIMUM_MS,
fade_ms: DEFAULT_SPLASH_FADE_MS,
fade_out_ms: DEFAULT_SPLASH_FADE_OUT_MS,
fade_in_source: ksp_config_lib::ConfigEnvironmentSource::Fallback,
minimum_source: ksp_config_lib::ConfigEnvironmentSource::Fallback,
fade_source: ksp_config_lib::ConfigEnvironmentSource::Fallback,
fade_out_source: ksp_config_lib::ConfigEnvironmentSource::Fallback,
};
}
/// Returns the minimum visible duration after splash frontend readiness.
/// Returns the configured fade-in duration.
#[must_use]
pub(crate) const fn fade_in_ms(self) -> u32 {
return self.fade_in_ms;
}
/// Returns the minimum fully-visible splash duration.
#[must_use]
pub(crate) const fn minimum_ms(self) -> u64 {
return self.minimum_ms;
}
/// Returns the fade duration used for both fade-in and fade-out.
/// Returns the configured fade-out duration.
#[must_use]
pub(crate) const fn fade_ms(self) -> u32 {
return self.fade_ms;
pub(crate) const fn fade_out_ms(self) -> u32 {
return self.fade_out_ms;
}
/// Returns the safe provenance code for the fade-in duration.
#[must_use]
pub(crate) const fn fade_in_source(self) -> &'static str {
return environment_source_code(self.fade_in_source);
}
/// Returns the safe provenance code for the minimum duration.
@@ -81,16 +116,16 @@ impl SplashSettings {
return environment_source_code(self.minimum_source);
}
/// Returns the safe provenance code for the fade duration.
/// Returns the safe provenance code for the fade-out duration.
#[must_use]
pub(crate) const fn fade_source(self) -> &'static str {
return environment_source_code(self.fade_source);
pub(crate) const fn fade_out_source(self) -> &'static str {
return environment_source_code(self.fade_out_source);
}
/// Returns the minimum backend lifecycle duration from readiness until main activation.
/// Returns the expected additive backend lifecycle from readiness through fade-out.
#[must_use]
pub(crate) fn expected_backend_lifecycle_ms(self) -> u64 {
return self.minimum_ms + u64::from(self.fade_ms);
return u64::from(self.fade_in_ms) + self.minimum_ms + u64::from(self.fade_out_ms);
}
}
@@ -103,6 +138,8 @@ pub(crate) struct SplashOrderDto {
pub(crate) action: String,
/// Optional status text.
pub(crate) message: std::option::Option<String>,
/// Optional message severity used by the bottom message feed.
pub(crate) status: std::option::Option<String>,
/// Optional animation duration in milliseconds.
pub(crate) duration_ms: std::option::Option<u32>,
}
@@ -110,8 +147,13 @@ pub(crate) struct SplashOrderDto {
impl SplashOrderDto {
/// Creates a new `SplashOrderDto` value.
#[must_use]
pub(crate) fn new(action: &str, message: std::option::Option<&str>, duration_ms: std::option::Option<u32>) -> Self {
return Self { action: action.to_owned(), message: message.map(std::string::ToString::to_string), duration_ms };
pub(crate) fn new(action: &str, message: std::option::Option<&str>, status: std::option::Option<&str>, duration_ms: std::option::Option<u32>) -> Self {
return Self {
action: action.to_owned(),
message: message.map(std::string::ToString::to_string),
status: status.map(std::string::ToString::to_string),
duration_ms,
};
}
}

View File

@@ -1,12 +1,12 @@
// file: crates/ksp-app-config-desk/src/tw_splash.rs
// version: 5
// version: 6
//! Tauri-window lifecycle for the Config Desk splash window.
use tauri::Emitter; // rust-rules: trait-import
use tauri::Manager; // rust-rules: trait-import
/// Crate-internal `WINDOW_LABEL_SPLASH` constant.
/// Crate-internal splash window label.
pub(crate) const WINDOW_LABEL_SPLASH: &str = "splash";
const SPLASH_EVENT_NAME: &str = "ksp-splash-order";
@@ -23,7 +23,7 @@ pub(crate) fn require_splash_window(manager: &impl Manager<tauri::Wry>) -> ksp_c
};
}
/// Executes the crate-internal splash frontend ready service operation for the owning module.
/// Starts the one-shot splash lifecycle after the splash frontend reports readiness.
pub(crate) async fn splash_frontend_ready_service(
app: tauri::AppHandle,
invoking_window: tauri::WebviewWindow,
@@ -36,12 +36,7 @@ pub(crate) async fn splash_frontend_ready_service(
);
}
if !state.begin_splash_sequence() {
ksp_logging_lib::trace!(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_WINDOWS,
window_label = WINDOW_LABEL_SPLASH,
"duplicate splash frontend readiness ignored"
);
ksp_logging_lib::trace!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WINDOWS, window_label = WINDOW_LABEL_SPLASH, "duplicate splash frontend readiness ignored");
return std::result::Result::Ok(());
}
let settings = state.splash_settings();
@@ -50,49 +45,60 @@ pub(crate) async fn splash_frontend_ready_service(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_WINDOWS,
window_label = WINDOW_LABEL_SPLASH,
fade_in_ms = settings.fade_in_ms(),
fade_in_source = settings.fade_in_source(),
minimum_ms = settings.minimum_ms(),
minimum_source = settings.minimum_source(),
fade_ms = settings.fade_ms(),
fade_source = settings.fade_source(),
fade_out_ms = settings.fade_out_ms(),
fade_out_source = settings.fade_out_source(),
expected_backend_lifecycle_ms = settings.expected_backend_lifecycle_ms(),
"splash frontend readiness accepted; starting splash to main lifecycle"
);
let fade_in = emit_order(
let emitted = emit_order(
&invoking_window,
crate::SplashOrderDto::new("fade_in", std::option::Option::Some("Initialisation de KSP Config Desk..."), std::option::Option::Some(settings.fade_ms())),
crate::SplashOrderDto::new("fade_in", std::option::Option::None, std::option::Option::None, std::option::Option::Some(settings.fade_in_ms())),
);
if let std::result::Result::Err(error) = fade_in {
if let std::result::Result::Err(error) = emitted {
return std::result::Result::Err(error);
}
let minimum_wait_started = std::time::Instant::now();
tokio::time::sleep(std::time::Duration::from_millis(settings.minimum_ms())).await;
ksp_logging_lib::debug!(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_WINDOWS,
window_label = WINDOW_LABEL_SPLASH,
configured_wait_ms = settings.minimum_ms(),
actual_wait_ms = minimum_wait_started.elapsed().as_secs_f64() * 1000.0,
lifecycle_elapsed_ms = lifecycle_started.elapsed().as_secs_f64() * 1000.0,
"splash minimum wait completed"
);
let fade_out = emit_order(
&invoking_window,
crate::SplashOrderDto::new("fade_out", std::option::Option::Some("Initialisation terminée."), std::option::Option::Some(settings.fade_ms())),
);
if let std::result::Result::Err(error) = fade_out {
let emitted = emit_message(&invoking_window, "Initialisation de Config Desk...", "info");
if let std::result::Result::Err(error) = emitted {
return std::result::Result::Err(error);
}
let fade_wait_started = std::time::Instant::now();
tokio::time::sleep(std::time::Duration::from_millis(u64::from(settings.fade_ms()))).await;
ksp_logging_lib::debug!(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_WINDOWS,
window_label = WINDOW_LABEL_SPLASH,
configured_wait_ms = settings.fade_ms(),
actual_wait_ms = fade_wait_started.elapsed().as_secs_f64() * 1000.0,
lifecycle_elapsed_ms = lifecycle_started.elapsed().as_secs_f64() * 1000.0,
"splash fade-out wait completed"
let emitted = emit_debug(&invoking_window, "Listener frontend prêt; fade-in piloté par Rust.");
if let std::result::Result::Err(error) = emitted {
return std::result::Result::Err(error);
}
sleep_and_log(&invoking_window, "fade-in", u64::from(settings.fade_in_ms()), lifecycle_started).await;
let emitted = emit_message(&invoking_window, "Configuration KSP chargée.", "success");
if let std::result::Result::Err(error) = emitted {
return std::result::Result::Err(error);
}
let emitted = emit_debug(&invoking_window, "Configuration et provenance des timings résolues via ksp-config-lib.");
if let std::result::Result::Err(error) = emitted {
return std::result::Result::Err(error);
}
let emitted = emit_message(&invoking_window, "Runtime Logging initialisé.", "success");
if let std::result::Result::Err(error) = emitted {
return std::result::Result::Err(error);
}
let emitted = emit_message(&invoking_window, "Gestion Config prête.", "success");
if let std::result::Result::Err(error) = emitted {
return std::result::Result::Err(error);
}
sleep_and_log(&invoking_window, "minimum", settings.minimum_ms(), lifecycle_started).await;
let emitted = emit_message(&invoking_window, "Initialisation terminée.", "success");
if let std::result::Result::Err(error) = emitted {
return std::result::Result::Err(error);
}
let emitted = emit_order(
&invoking_window,
crate::SplashOrderDto::new("fade_out", std::option::Option::None, std::option::Option::None, std::option::Option::Some(settings.fade_out_ms())),
);
if let std::result::Result::Err(error) = emitted {
return std::result::Result::Err(error);
}
sleep_and_log(&invoking_window, "fade-out", u64::from(settings.fade_out_ms()), lifecycle_started).await;
let show_main = crate::show_main_window(&app);
if let std::result::Result::Err(error) = show_main {
return std::result::Result::Err(error);
@@ -116,18 +122,45 @@ pub(crate) async fn splash_frontend_ready_service(
return std::result::Result::Ok(());
}
fn emit_message(window: &tauri::WebviewWindow, message: &str, status: &str) -> ksp_core_lib::Result<()> {
return emit_order(
window,
crate::SplashOrderDto::new("add_message", std::option::Option::Some(message), std::option::Option::Some(status), std::option::Option::None),
);
}
fn emit_debug(window: &tauri::WebviewWindow, message: &str) -> ksp_core_lib::Result<()> {
if !cfg!(debug_assertions) {
return std::result::Result::Ok(());
}
return emit_order(
window,
crate::SplashOrderDto::new("add_debug", std::option::Option::Some(message), std::option::Option::None, std::option::Option::None),
);
}
async fn sleep_and_log(window: &tauri::WebviewWindow, phase: &str, duration_ms: u64, lifecycle_started: std::time::Instant) {
let wait_started = std::time::Instant::now();
tokio::time::sleep(std::time::Duration::from_millis(duration_ms)).await;
ksp_logging_lib::debug!(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_WINDOWS,
window_label = window.label(),
phase,
configured_wait_ms = duration_ms,
actual_wait_ms = wait_started.elapsed().as_secs_f64() * 1000.0,
lifecycle_elapsed_ms = lifecycle_started.elapsed().as_secs_f64() * 1000.0,
"splash lifecycle wait completed"
);
return;
}
fn emit_order(window: &tauri::WebviewWindow, order: crate::SplashOrderDto) -> ksp_core_lib::Result<()> {
let action = order.action.clone();
let emitted = window.emit(SPLASH_EVENT_NAME, order);
return match emitted {
std::result::Result::Ok(()) => {
ksp_logging_lib::trace!(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_WINDOWS,
window_label = WINDOW_LABEL_SPLASH,
action = action.as_str(),
"splash order emitted"
);
ksp_logging_lib::trace!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WINDOWS, window_label = WINDOW_LABEL_SPLASH, action = action.as_str(), "splash order emitted");
std::result::Result::Ok(())
},
std::result::Result::Err(error) => std::result::Result::Err(