Souscription WebSocket Solana standard
diff --git a/kb-app-demo-desktop/frontend/sass/splash.scss b/kb-app-demo-desktop/frontend/sass/splash.scss
index aad12f5..7bdb429 100644
--- a/kb-app-demo-desktop/frontend/sass/splash.scss
+++ b/kb-app-demo-desktop/frontend/sass/splash.scss
@@ -1,5 +1,5 @@
// file: kb_app_demo/frontend/sass/splash.scss
-// version: 3
+// version: 4
@font-face {
font-family: 'Dos Amazigh';
@@ -66,6 +66,7 @@ body {
width: 50%;
max-height: 30%;
overflow-y: auto;
+ scrollbar-width: none;
color: white;
font-family: monospace;
font-size: 12px;
@@ -77,17 +78,25 @@ body {
#messages-container {
position: absolute;
+ font-family: Arial, sans-serif;
+ font-size: 14px;
bottom: 0;
left: 0;
width: 100%;
max-height: 30%;
overflow-y: auto;
+ scrollbar-width: none;
background-color: rgba(0, 0, 0, 0.1);
padding: 10px 10px 40px;
z-index: 2;
box-sizing: border-box;
}
+#debug-info::-webkit-scrollbar,
+#messages-container::-webkit-scrollbar {
+ display: none;
+}
+
.splash-message {
margin-bottom: 8px;
padding: 6px 10px;
diff --git a/kb-app-demo-desktop/frontend/ts/demo_backfill.ts b/kb-app-demo-desktop/frontend/ts/demo_backfill.ts
index feb9501..ba72e87 100644
--- a/kb-app-demo-desktop/frontend/ts/demo_backfill.ts
+++ b/kb-app-demo-desktop/frontend/ts/demo_backfill.ts
@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/frontend/ts/demo_backfill.ts
-// version: 7
+// version: 8
import * as bootstrap from "bootstrap";
import "simplebar";
@@ -210,6 +210,17 @@ async function cancelBackfill(): Promise {
async function loadOptions(): Promise {
const options = await invoke("demo_backfill_options");
+ const optionsWithPrograms = options as DemoBackfillOptionsPayload & {
+ programs: Array<{ code: string; programId: string }>;
+ };
+ const programOptions = element("#programAddressOptions");
+ programOptions.replaceChildren();
+ for (const program of optionsWithPrograms.programs) {
+ const option = document.createElement("option");
+ option.value = program.programId;
+ option.label = program.code;
+ programOptions.append(option);
+ }
const roleSelect = element("#backfillRoleSelect");
roleSelect.replaceChildren();
for (const role of options.roles) {
diff --git a/kb-app-demo-desktop/frontend/ts/splash.ts b/kb-app-demo-desktop/frontend/ts/splash.ts
index 103f35a..0a66617 100644
--- a/kb-app-demo-desktop/frontend/ts/splash.ts
+++ b/kb-app-demo-desktop/frontend/ts/splash.ts
@@ -1,6 +1,7 @@
// file: kb-app-demo-desktop/frontend/ts/splash.ts
-// version: 2
+// version: 3
+import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import type { SplashOrder } from "./bindings/kb_app_demo_desktop/splash/SplashOrder.ts";
@@ -105,6 +106,7 @@ async function initializeSplash(): Promise {
await listen("splash", event => {
void handleSplashOrder(event.payload);
});
+ await invoke("splash_frontend_ready");
}
document.addEventListener("DOMContentLoaded", () => {
diff --git a/kb-app-demo-desktop/src/app_state.rs b/kb-app-demo-desktop/src/app_state.rs
index 43026f1..16454e5 100644
--- a/kb-app-demo-desktop/src/app_state.rs
+++ b/kb-app-demo-desktop/src/app_state.rs
@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/app_state.rs
-// version: 9
+// version: 10
//! Shared Tauri application state and startup initialization.
@@ -23,6 +23,7 @@ pub(crate) struct AppState {
demo_decode_replay_campaign_id: std::sync::Mutex>,
demo_execution_solana_core_running: std::sync::atomic::AtomicBool,
demo_execution_solana_core_cancel_requested: std::sync::atomic::AtomicBool,
+ startup_sequence_started: std::sync::atomic::AtomicBool,
}
impl crate::AppState {
@@ -68,6 +69,7 @@ impl crate::AppState {
demo_decode_replay_campaign_id: std::sync::Mutex::new(std::option::Option::None),
demo_execution_solana_core_running: std::sync::atomic::AtomicBool::new(false),
demo_execution_solana_core_cancel_requested: std::sync::atomic::AtomicBool::new(false),
+ startup_sequence_started: std::sync::atomic::AtomicBool::new(false),
});
}
@@ -172,6 +174,19 @@ impl crate::AppState {
return &self.demo_execution_solana_core_cancel_requested;
}
+ /// Starts the splash startup sequence at most once.
+ pub(crate) fn begin_startup_sequence(&self) -> bool {
+ return self
+ .startup_sequence_started
+ .compare_exchange(
+ false,
+ true,
+ std::sync::atomic::Ordering::AcqRel,
+ std::sync::atomic::Ordering::Acquire,
+ )
+ .is_ok();
+ }
+
/// Returns the number of logging routes held by the logging guard.
pub(crate) fn logging_route_count(&self) -> usize {
let lock_result = self.logging_guard.lock();
diff --git a/kb-app-demo-desktop/src/demo_backfill.rs b/kb-app-demo-desktop/src/demo_backfill.rs
index 4851872..0076f7b 100644
--- a/kb-app-demo-desktop/src/demo_backfill.rs
+++ b/kb-app-demo-desktop/src/demo_backfill.rs
@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_backfill.rs
-// version: 11
+// version: 12
//! Tauri commands and UI payloads for bounded HTTP transaction backfills.
@@ -20,6 +20,20 @@ pub(crate) struct DemoBackfillRoleOption {
pub(crate) providers: std::vec::Vec,
}
+/// One known program suggested by the free-form Program ID field.
+#[derive(Clone, Debug, serde::Serialize, TS)]
+#[serde(rename_all = "camelCase")]
+#[ts(
+ export,
+ export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_backfill/DemoBackfillProgramOption.ts"
+)]
+pub(crate) struct DemoBackfillProgramOption {
+ /// Stable program code.
+ pub(crate) code: std::string::String,
+ /// Canonical program identifier.
+ pub(crate) program_id: std::string::String,
+}
+
/// Initial options shown by the backfill demo.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
@@ -30,6 +44,8 @@ pub(crate) struct DemoBackfillRoleOption {
pub(crate) struct DemoBackfillOptionsPayload {
/// Selectable endpoint roles.
pub(crate) roles: std::vec::Vec,
+ /// Known programs offered as non-blocking autocomplete suggestions.
+ pub(crate) programs: std::vec::Vec,
/// Preferred role when configured.
pub(crate) default_role: std::option::Option,
/// Default transaction commitment.
diff --git a/kb-app-demo-desktop/src/demo_decode_replay.rs b/kb-app-demo-desktop/src/demo_decode_replay.rs
index 7c30b4a..99ec7ff 100644
--- a/kb-app-demo-desktop/src/demo_decode_replay.rs
+++ b/kb-app-demo-desktop/src/demo_decode_replay.rs
@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_decode_replay.rs
-// version: 28
+// version: 29
//! Tauri commands and UI payloads for contextual instruction decode replay.
@@ -720,22 +720,22 @@ mod tests {
fn native_memo_token2022_elgamal_classic_token_and_ata_decoders_are_registered() {
let decoders = crate::available_decoders();
assert_eq!(decoders.len(), 7);
- let names = decoders
+ let mut names = decoders
.iter()
.map(|decoder| return decoder.identity().name)
.collect::>();
- assert_eq!(
- names,
- std::vec![
- "solana_native_classifier".to_string(),
- "spl_associated_token_account".to_string(),
- "spl_elgamal_registry".to_string(),
- "spl_memo".to_string(),
- "spl_token".to_string(),
- "spl_token2022".to_string(),
- "metadata_metaplex_token_metadata".to_string(),
- ]
- );
+ names.sort();
+ let mut expected_names = std::vec![
+ "metadata_metaplex_token_metadata".to_string(),
+ "solana_native_classifier".to_string(),
+ "spl_associated_token_account".to_string(),
+ "spl_elgamal_registry".to_string(),
+ "spl_memo".to_string(),
+ "spl_token".to_string(),
+ "spl_token2022".to_string(),
+ ];
+ expected_names.sort();
+ assert_eq!(names, expected_names);
}
#[test]
diff --git a/kb-app-demo-desktop/src/demo_sql_common.rs b/kb-app-demo-desktop/src/demo_sql_common.rs
index 2117ce2..dc3b6b1 100644
--- a/kb-app-demo-desktop/src/demo_sql_common.rs
+++ b/kb-app-demo-desktop/src/demo_sql_common.rs
@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_sql_common.rs
-// version: 8
+// version: 9
//! Shared SQL demo helpers and serializable payloads.
@@ -201,25 +201,23 @@ pub(crate) async fn initialize_postgres_schema_for_startup(
return;
},
};
- if !options.auto_initialize_schema {
- emit_sql_startup_splash(
- splash_window,
- "PostgreSQL schema auto-initialization disabled.",
- "info",
- false,
- );
- let masked_dsn = options.masked_dsn();
- tracing::debug!(target: crate::TRACING_TARGET, dsn = masked_dsn.as_str(), "PostgreSQL schema auto-initialization disabled");
- return;
- }
+ let initialize_schema = options.auto_initialize_schema;
let masked_dsn = options.masked_dsn();
options.auto_initialize_schema = false;
emit_sql_startup_splash(
splash_window,
- "Checking PostgreSQL raw/core/decode tables...",
+ "Connexion PostgreSQL et vérification des tables raw/core/decode...",
"info",
- true,
+ false,
);
+ if !initialize_schema {
+ emit_sql_startup_splash(
+ splash_window,
+ "Auto-initialisation désactivée : vérification sans création de table.",
+ "info",
+ true,
+ );
+ }
tracing::debug!(target: crate::TRACING_TARGET, dsn = masked_dsn.as_str(), "start PostgreSQL schema initialization");
let store_result = kb_store::PostgresStore::connect(options).await;
let store = match store_result {
@@ -241,12 +239,14 @@ pub(crate) async fn initialize_postgres_schema_for_startup(
return;
},
};
- let initialize_result = store.initialize_store_schema().await;
- if let std::result::Result::Err(error) = initialize_result {
- let error_message = error.to_string();
- emit_sql_startup_error(splash_window, error_message.as_str());
- tracing::error!(target: crate::TRACING_TARGET, error = error_message.as_str(), "PostgreSQL store schema initialization failed");
- return;
+ if initialize_schema {
+ let initialize_result = store.initialize_store_schema().await;
+ if let std::result::Result::Err(error) = initialize_result {
+ let error_message = error.to_string();
+ emit_sql_startup_error(splash_window, error_message.as_str());
+ tracing::error!(target: crate::TRACING_TARGET, error = error_message.as_str(), "PostgreSQL store schema initialization failed");
+ return;
+ }
}
let after_result = store.known_table_diagnostics().await;
let after_tables = match after_result {
diff --git a/kb-app-demo-desktop/src/demo_ws.rs b/kb-app-demo-desktop/src/demo_ws.rs
index 7ebea29..88929ef 100644
--- a/kb-app-demo-desktop/src/demo_ws.rs
+++ b/kb-app-demo-desktop/src/demo_ws.rs
@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_ws.rs
-// version: 14
+// version: 16
//! Standard Solana WebSocket demo commands backed by `kb_onchain_transport::WsSession`.
@@ -431,17 +431,26 @@ fn spawn_demo_ws_event_bridge(
};
match event {
kb_onchain_transport::WsSessionEvent::Notification { notification, .. } => {
+ let compact_payload = match serde_json::to_string(¬ification) {
+ std::result::Result::Ok(payload) => payload,
+ std::result::Result::Err(error) => {
+ format!("cannot serialize typed WebSocket notification: {error}")
+ },
+ };
+ tracing::debug!(
+ target: crate::TRACING_TARGET,
+ payload = %compact_payload,
+ "demo WebSocket notification received"
+ );
if rate_limiter.should_emit(&app_handle) {
- let payload = match serde_json::to_string_pretty(¬ification) {
+ let ui_payload = match serde_json::to_string_pretty(¬ification) {
std::result::Result::Ok(payload) => payload,
- std::result::Result::Err(error) => {
- format!("cannot serialize typed WebSocket notification: {error}")
- },
+ std::result::Result::Err(_) => compact_payload,
};
emit_demo_ws_message(
&app_handle,
"notification",
- truncate_payload(payload),
+ truncate_payload(ui_payload),
);
}
},
@@ -640,20 +649,27 @@ fn build_standard_ws_request(
std::result::Result::Ok(target) => target,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
- let config = match parse_optional_json_as::<
+ let mut config = match parse_optional_json_as::<
kb_onchain_transport::WsAccountSubscribeConfig,
>(config_json, "configJson")
{
- std::result::Result::Ok(config) => config,
+ std::result::Result::Ok(config) => config.unwrap_or_default(),
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
+ if config.encoding.is_none() {
+ config.encoding =
+ std::option::Option::Some(kb_onchain_transport::RpcAccountEncoding::Base64);
+ }
if let std::result::Result::Err(error) =
reject_present(filter_json, "filterJson", method)
{
return std::result::Result::Err(error);
}
std::result::Result::Ok(kb_onchain_transport::StandardWsRequest::Account(
- kb_onchain_transport::AccountSubscribeRequest { pubkey: target, config },
+ kb_onchain_transport::AccountSubscribeRequest {
+ pubkey: target,
+ config: std::option::Option::Some(config),
+ },
))
},
"blockSubscribe" => {
@@ -710,15 +726,22 @@ fn build_standard_ws_request(
{
return std::result::Result::Err(error);
}
- let config = match parse_optional_json_as::<
+ let mut config = match parse_optional_json_as::<
kb_onchain_transport::WsProgramSubscribeConfig,
>(config_json, "configJson")
{
- std::result::Result::Ok(config) => config,
+ std::result::Result::Ok(config) => config.unwrap_or_default(),
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
+ if config.encoding.is_none() {
+ config.encoding =
+ std::option::Option::Some(kb_onchain_transport::RpcAccountEncoding::Base64);
+ }
std::result::Result::Ok(kb_onchain_transport::StandardWsRequest::Program(
- kb_onchain_transport::ProgramSubscribeRequest { program_id: target, config },
+ kb_onchain_transport::ProgramSubscribeRequest {
+ program_id: target,
+ config: std::option::Option::Some(config),
+ },
))
},
"signatureSubscribe" => {
@@ -879,6 +902,42 @@ fn truncate_payload(payload: std::string::String) -> std::string::String {
mod tests {
use ts_rs::TS; // rust-rules: derive-import
+ #[test]
+ fn account_and_program_subscriptions_default_to_base64_without_overriding_explicit_encoding() {
+ let account = super::build_standard_ws_request(
+ "accountSubscribe",
+ std::option::Option::Some("11111111111111111111111111111111".to_string()),
+ std::option::Option::None,
+ std::option::Option::None,
+ );
+ let account = match account {
+ std::result::Result::Ok(kb_onchain_transport::StandardWsRequest::Account(value)) => {
+ value
+ },
+ _ => panic!("accountSubscribe request was not built"),
+ };
+ assert_eq!(
+ account.config.and_then(|value| return value.encoding),
+ std::option::Option::Some(kb_onchain_transport::RpcAccountEncoding::Base64)
+ );
+ let program = super::build_standard_ws_request(
+ "programSubscribe",
+ std::option::Option::Some(kb_program_ids::SPL_TOKEN2022_PROGRAM_ID.to_string()),
+ std::option::Option::None,
+ std::option::Option::Some(r#"{"encoding":"jsonParsed"}"#.to_string()),
+ );
+ let program = match program {
+ std::result::Result::Ok(kb_onchain_transport::StandardWsRequest::Program(value)) => {
+ value
+ },
+ _ => panic!("programSubscribe request was not built"),
+ };
+ assert_eq!(
+ program.config.and_then(|value| return value.encoding),
+ std::option::Option::Some(kb_onchain_transport::RpcAccountEncoding::JsonParsed)
+ );
+ }
+
#[test]
fn tauri_numeric_bindings_use_json_compatible_numbers() {
let config = ts_rs::Config::default();
diff --git a/kb-app-demo-desktop/src/lib.rs b/kb-app-demo-desktop/src/lib.rs
index d63480e..a5aa5a2 100644
--- a/kb-app-demo-desktop/src/lib.rs
+++ b/kb-app-demo-desktop/src/lib.rs
@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/lib.rs
-// version: 9
+// version: 10
//! Tauri desktop demo application for `khadhroony-bot3`.
@@ -39,6 +39,8 @@ pub(crate) use self::app_state::AppState;
pub(crate) use self::demo_backfill::DemoBackfillObserver;
/// Initial options shown by the backfill window.
pub(crate) use self::demo_backfill::DemoBackfillOptionsPayload;
+/// One known program suggested by the backfill Program ID autocomplete.
+pub(crate) use self::demo_backfill::DemoBackfillProgramOption;
/// One progress event emitted to the backfill window.
pub(crate) use self::demo_backfill::DemoBackfillProgressPayload;
/// UI request for one bounded backfill campaign.
@@ -327,6 +329,8 @@ pub(crate) use self::splash::SPLASH_MINIMUM_MS;
pub(crate) use self::splash::SplashOrder;
/// Emits one order to the splash frontend.
pub(crate) use self::splash::emit_splash_order;
+/// Runs the startup sequence after the splash frontend is ready.
+pub(crate) use self::splash::splash_frontend_ready_inner;
/// Waits for the minimum splash duration.
pub(crate) use self::splash::wait_until_minimum;
diff --git a/kb-app-demo-desktop/src/splash.rs b/kb-app-demo-desktop/src/splash.rs
index 8460f3f..a5426e9 100644
--- a/kb-app-demo-desktop/src/splash.rs
+++ b/kb-app-demo-desktop/src/splash.rs
@@ -1,17 +1,18 @@
// file: kb-app-demo-desktop/src/splash.rs
-// version: 3
+// version: 4
//! Splash-window payloads and startup sequencing helpers.
use tauri::Emitter; // rust-rules: trait-import
+use tauri::Manager; // rust-rules: trait-import
use ts_rs::TS; // rust-rules: derive-import
/// Minimum splash duration before the main window is shown.
-pub(crate) const SPLASH_MINIMUM_MS: u64 = 3100;
+pub(crate) const SPLASH_MINIMUM_MS: u64 = 12100;
/// Splash fade duration.
-pub(crate) const SPLASH_FADE_MS: u32 = 3000;
+pub(crate) const SPLASH_FADE_MS: u32 = 12000;
/// Delay after fade-out before destroying the splash window.
-pub(crate) const SPLASH_CLOSE_WAIT_MS: u64 = 3100;
+pub(crate) const SPLASH_CLOSE_WAIT_MS: u64 = 12100;
/// Command sent from Rust to the splash frontend.
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, TS)]
@@ -50,6 +51,96 @@ pub(crate) fn emit_splash_order(
}
}
+/// Runs the startup sequence after the splash frontend installed its listener.
+pub(crate) async fn splash_frontend_ready_inner(
+ app: tauri::AppHandle,
+ window: tauri::WebviewWindow,
+ state: &crate::AppState,
+) -> std::result::Result<(), std::string::String> {
+ if window.label() != "splash" {
+ return std::result::Result::Err(
+ "splash readiness may only originate from splash".to_string(),
+ );
+ }
+ if !state.begin_startup_sequence() {
+ return std::result::Result::Ok(());
+ }
+ let main_window = match app.get_webview_window("main") {
+ std::option::Option::Some(value) => value,
+ std::option::Option::None => {
+ return std::result::Result::Err("main window is missing".to_string());
+ },
+ };
+ let started_at = tokio::time::Instant::now();
+ crate::emit_splash_order(
+ &window,
+ "fadein",
+ std::option::Option::None,
+ std::option::Option::None,
+ std::option::Option::Some(crate::SPLASH_FADE_MS),
+ );
+ crate::emit_splash_order(
+ &window,
+ "add_msg",
+ std::option::Option::Some("Chargement de la configuration terminé."),
+ std::option::Option::Some("success"),
+ std::option::Option::None,
+ );
+ crate::emit_splash_order(
+ &window,
+ "add_log",
+ std::option::Option::Some(
+ format!("Profil actif : {}", state.active_profile().name).as_str(),
+ ),
+ std::option::Option::None,
+ std::option::Option::None,
+ );
+ crate::emit_splash_order(
+ &window,
+ "add_msg",
+ std::option::Option::Some(
+ format!("Logging initialisé : {} routes actives.", state.logging_route_count())
+ .as_str(),
+ ),
+ std::option::Option::Some("success"),
+ std::option::Option::None,
+ );
+ crate::emit_splash_order(
+ &window,
+ "add_msg",
+ std::option::Option::Some("Pool HTTP initialisé."),
+ std::option::Option::Some("success"),
+ std::option::Option::None,
+ );
+ crate::initialize_postgres_schema_for_startup(state, &window).await;
+ crate::emit_splash_order(
+ &window,
+ "add_msg",
+ std::option::Option::Some("Initialisation terminée."),
+ std::option::Option::Some("success"),
+ std::option::Option::None,
+ );
+ crate::wait_until_minimum(started_at, crate::SPLASH_MINIMUM_MS).await;
+ crate::emit_splash_order(
+ &window,
+ "fadeout",
+ std::option::Option::None,
+ std::option::Option::None,
+ std::option::Option::Some(crate::SPLASH_FADE_MS),
+ );
+ tokio::time::sleep(std::time::Duration::from_millis(crate::SPLASH_CLOSE_WAIT_MS)).await;
+ if let std::result::Result::Err(error) = main_window.show() {
+ return std::result::Result::Err(format!("cannot show main window: {error:?}"));
+ }
+ if let std::result::Result::Err(error) = main_window.set_focus() {
+ return std::result::Result::Err(format!("cannot focus main window: {error:?}"));
+ }
+ if let std::result::Result::Err(error) = window.destroy() {
+ return std::result::Result::Err(format!("cannot destroy splash window: {error:?}"));
+ }
+ return std::result::Result::Ok(());
+}
+
/// Waits until the configured minimum splash duration has elapsed.
pub(crate) async fn wait_until_minimum(start: tokio::time::Instant, minimum_ms: u64) {
let minimum = std::time::Duration::from_millis(minimum_ms);
diff --git a/kb-app-demo-desktop/src/tauri.rs b/kb-app-demo-desktop/src/tauri.rs
index 074bfe7..d4f31c0 100644
--- a/kb-app-demo-desktop/src/tauri.rs
+++ b/kb-app-demo-desktop/src/tauri.rs
@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/tauri.rs
-// version: 10
+// version: 12
//! Tauri runtime assembly and private command wrappers.
@@ -36,6 +36,7 @@ pub fn run() -> kb_core::Result<()> {
builder = builder.manage(app_state);
builder = builder.invoke_handler(tauri::generate_handler![
emit_frontend_log,
+ splash_frontend_ready,
load_project_readme,
open_demo_backfill_window,
demo_backfill_options,
@@ -104,99 +105,18 @@ pub fn run() -> kb_core::Result<()> {
});
builder = builder.plugin(tracing_builder.build::());
builder = builder.setup(|app| {
- let splash_window = match app.get_webview_window("splash") {
- std::option::Option::Some(window) => window,
- std::option::Option::None => {
- return std::result::Result::Err(std::boxed::Box::new(std::io::Error::new(
- std::io::ErrorKind::NotFound,
- "splash window is missing",
- )));
- },
- };
- let main_window = match app.get_webview_window("main") {
- std::option::Option::Some(window) => window,
- std::option::Option::None => {
- return std::result::Result::Err(std::boxed::Box::new(std::io::Error::new(
- std::io::ErrorKind::NotFound,
- "main window is missing",
- )));
- },
- };
- let app_handle = app.handle().clone();
- tauri::async_runtime::spawn(async move {
- let started_at = tokio::time::Instant::now();
- crate::emit_splash_order(
- &splash_window,
- "add_log",
- std::option::Option::Some("Configuration chargée"),
- std::option::Option::None,
- std::option::Option::None,
- );
- crate::emit_splash_order(
- &splash_window,
- "add_log",
- std::option::Option::Some("Sous-système de logs initialisé"),
- std::option::Option::None,
- std::option::Option::None,
- );
- crate::emit_splash_order(
- &splash_window,
- "add_log",
- std::option::Option::Some("Pool HTTP initialisé"),
- std::option::Option::None,
- std::option::Option::None,
- );
- let state = app_handle.state::();
- crate::initialize_postgres_schema_for_startup(&state, &splash_window).await;
- crate::emit_splash_order(
- &splash_window,
- "fadein",
- std::option::Option::None,
- std::option::Option::None,
- std::option::Option::Some(crate::SPLASH_FADE_MS),
- );
- crate::emit_splash_order(
- &splash_window,
- "add_msg",
- std::option::Option::Some("Initialisation..."),
- std::option::Option::Some("info"),
- std::option::Option::None,
- );
- crate::emit_splash_order(
- &splash_window,
- "add_msg",
- std::option::Option::Some("Loading complete..."),
- std::option::Option::Some("success"),
- std::option::Option::None,
- );
- crate::wait_until_minimum(started_at, crate::SPLASH_MINIMUM_MS).await;
- if cfg!(debug_assertions) {
- crate::emit_splash_order(
- &splash_window,
- "add_log",
- std::option::Option::Some("Start Fade-out"),
- std::option::Option::None,
- std::option::Option::None,
- );
- }
- crate::emit_splash_order(
- &splash_window,
- "fadeout",
- std::option::Option::None,
- std::option::Option::None,
- std::option::Option::Some(crate::SPLASH_FADE_MS),
- );
- tokio::time::sleep(std::time::Duration::from_millis(crate::SPLASH_CLOSE_WAIT_MS)).await;
- if let std::result::Result::Err(error) = splash_window.destroy() {
- tracing::error!(target: crate::TRACING_TARGET, "cannot destroy splash window: {error:?}");
- }
- if let std::result::Result::Err(error) = main_window.show() {
- tracing::error!(target: crate::TRACING_TARGET, "cannot show main window: {error:?}");
- }
- if let std::result::Result::Err(error) = main_window.set_focus() {
- tracing::error!(target: crate::TRACING_TARGET, "cannot focus main window: {error:?}");
- }
- });
+ if app.get_webview_window("splash").is_none() {
+ return std::result::Result::Err(std::boxed::Box::new(std::io::Error::new(
+ std::io::ErrorKind::NotFound,
+ "splash window is missing",
+ )));
+ }
+ if app.get_webview_window("main").is_none() {
+ return std::result::Result::Err(std::boxed::Box::new(std::io::Error::new(
+ std::io::ErrorKind::NotFound,
+ "main window is missing",
+ )));
+ }
return std::result::Result::Ok(());
});
let run_result = builder.run(tauri::generate_context!());
@@ -208,6 +128,15 @@ pub fn run() -> kb_core::Result<()> {
};
}
+#[tauri::command]
+async fn splash_frontend_ready(
+ app: tauri::AppHandle,
+ window: tauri::WebviewWindow,
+ state: tauri::State<'_, crate::AppState>,
+) -> std::result::Result<(), std::string::String> {
+ return crate::splash_frontend_ready_inner(app, window, &state).await;
+}
+
fn install_default_rustls_provider() -> kb_core::Result<()> {
if rustls::crypto::CryptoProvider::get_default().is_some() {
return std::result::Result::Ok(());
@@ -759,8 +688,18 @@ fn demo_backfill_options(
} else {
roles.first().map(|item| return item.role.clone())
};
+ let programs = kb_program_ids::registered_program_ids()
+ .iter()
+ .map(|entry| {
+ return crate::DemoBackfillProgramOption {
+ code: entry.code().to_string(),
+ program_id: entry.program_id().to_string(),
+ };
+ })
+ .collect();
return crate::DemoBackfillOptionsPayload {
roles,
+ programs,
default_role,
default_commitment: "confirmed".to_string(),
default_page_size: 100,
diff --git a/kb-config/src/settings.rs b/kb-config/src/settings.rs
index 86457e6..7dce544 100644
--- a/kb-config/src/settings.rs
+++ b/kb-config/src/settings.rs
@@ -1,5 +1,5 @@
// file: kb-config/src/settings.rs
-// version: 15
+// version: 16
//! Typed configuration models shared by applications and workers.
@@ -1349,13 +1349,18 @@ mod tests {
("info.log", "info", "human"),
("error.jsonl", "error", "json"),
] {
- let expected_path = format!("logs/{directory}/{crate_name}/{suffix}");
+ let route_directory = if crate_name == "kb-pipeline-demo-scenarios" {
+ "kb-pipeline"
+ } else {
+ crate_name.as_str()
+ };
+ let expected_path = format!("logs/{directory}/{route_directory}/{suffix}");
let route_exists = profile.logging.targets.iter().any(|target| {
return target.enabled
&& target.path == expected_path
&& target.level == level
&& target.format == format
- && target.targets == std::vec![crate_name.clone()];
+ && target.targets.iter().any(|value| return value == crate_name);
});
assert!(
route_exists,