v0.1.4-pre.007

This commit is contained in:
2026-08-16 12:21:28 +02:00
parent e11006e96a
commit ac8867d5a1
16 changed files with 677 additions and 62 deletions

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml
# version: 70
# version: 71
[workspace]
resolver = "3"
members = ["crates/ksp-app-config-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib"]
[workspace.package]
version = "0.1.4-pre.6.fix.1"
version = "0.1.4-pre.7"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-app-config-desk/README.md -->
<!-- version: 5 -->
<!-- version: 6 -->
# `ksp-app-config-desk`
@@ -28,9 +28,8 @@ Le gabarit desktop actif fournit maintenant :
- `tauri-plugin-tracing` côté Rust et `@fltsci/tauri-plugin-tracing` côté frontend ;
- les ports dédiés `1430` pour Vite HTTP et `1431` pour le WebSocket de développement ;
- la destination frontend externe commune à Tauri et Vite;
- le bootstrap Config/Logging réel, un `AppState` durable et le premier contrat Tauri/TS-RS `get_app_snapshot`.
Le bridge de logging frontend KSP à target explicite n'est pas encore introduit : il appartient à une tranche dédiée afin de passer par `ksp-logging-lib` et de ne pas figer le target vide du bridge JS générique comme contrat KSP.
- le bootstrap Config/Logging réel, un `AppState` durable et le premier contrat Tauri/TS-RS `get_app_snapshot`;
- le bridge de logging frontend KSP `frontend_log.ts`, routé par `emit_frontend_log` vers des targets statiques whitelistés de `ksp-logging-lib`.
## Layout frontend
@@ -46,6 +45,7 @@ frontend/
│ ├── main.scss
│ └── splash.scss
├── ts/
│ ├── frontend_log.ts
│ ├── main.ts
│ └── splash.ts
├── main.html
@@ -88,6 +88,20 @@ Les bibliothèques consommées par le bundle applicatif sont déclarées sous `d
Les outils de compilation/développement et déclarations de types appartiennent à `devDependencies`, notamment `@tauri-apps/cli`, `@types/bootstrap`, `@types/node`, `sass-embedded`, `typescript` et `vite`. Les commandes npm directes sont réservées à l'installation ou à la mise à jour de ces dépendances ; le cycle normal de développement et de build est lancé depuis la racine du workspace par Tauri avec la configuration explicite de l'application.
## Logging frontend KSP
Le frontend utilise `frontend/ts/frontend_log.ts`. Les helpers `frontendTrace`, `frontendDebug`, `frontendInfo`, `frontendWarn` et `frontendError`, ainsi que le bridge `console.*`, invoquent la commande Tauri `emit_frontend_log`.
Le payload ne transporte pas de target tracing libre. Il utilise uniquement les identifiants logiques :
```text
frontend -> ksp-app-config-desk.frontend
main -> ksp-app-config-desk.frontend.main
splash -> ksp-app-config-desk.frontend.splash
```
Rust valide le niveau et le `targetId`, choisit un callsite statique puis émet exclusivement via les macros de `ksp-logging-lib`. Le package applicatif n'importe pas directement `tracing`. Le panneau manuel de test Logging, ses targets `test.*` et ses domaines personnalisables restent une surface distincte prévue plus tard.
## Bootstrap Config et Logging
Au démarrage, l'application construit `ConfigBootstrapOptions`, `ConfigFileRegistry`, `ConfigDocumentEngine` et `ConfigManagement` à partir des arguments complets du processus. Elle tente ensuite de résoudre le profil Logging par défaut avec un snapshot `ConfigEnvironment` frais.

View File

@@ -1,13 +1,12 @@
<!-- file: crates/ksp-app-config-desk/TODO.md -->
<!-- version: 4 -->
<!-- version: 5 -->
# TODO `ksp-app-config-desk`
Le gabarit frontend, le bootstrap Config/Logging, `AppState`, le `LoggingGuard` durable et les premiers DTO TS-RS sont maintenant présents.
Le gabarit frontend, le bootstrap Config/Logging, `AppState`, le `LoggingGuard` durable, les premiers DTO TS-RS et le bridge frontend logging KSP sont maintenant présents.
Les prochaines tranches doivent encore :
- reprendre/refondre `frontend_log.ts` depuis bot3 avec targets KSP whitelistés et émission via `ksp-logging-lib` ;
- finaliser le lifecycle splash configurable sans temporisation environnementale codée en dur ;
- développer les panneaux Documents, Profils, Environnement/`.env`, Secrets et Logging ;
- introduire DataTables au premier tableau qui exige tri, filtrage, sélection ou checkbox ;

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-app-config-desk/USAGE.md -->
<!-- version: 5 -->
<!-- version: 6 -->
# Utilisation de `ksp-app-config-desk`
@@ -73,9 +73,19 @@ Cette destination est résolue depuis la racine de la crate dans `vite.config.ts
## Tracing desktop
`tauri-plugin-tracing` est enregistré côté Rust et la capability contient `tracing:default`. Le package frontend associé est installé avec le gabarit.
`tauri-plugin-tracing` est enregistré côté Rust et la capability contient `tracing:default`. Le package frontend associé reste disponible comme adaptateur Tauri, sans installer de subscriber concurrent.
Cette tranche n'utilise pas encore le bridge JavaScript pour émettre les logs KSP applicatifs : `frontend_log.ts`, les targets KSP whitelistés et la commande `emit_frontend_log` sont introduits dans la tranche dédiée après initialisation de `ksp-logging-lib`.
Les logs techniques du frontend passent maintenant par `frontend/ts/frontend_log.ts` puis la commande Tauri `emit_frontend_log`. Les identifiants de target autorisés sont :
```text
frontend
main
splash
```
Ils sont convertis côté Rust vers des targets KSP statiques `ksp-app-config-desk.frontend*` et émis uniquement via `ksp-logging-lib`. Un niveau différent de `trace`, `debug`, `info`, `warn` ou `error`, ou un `targetId` non whitelisté, est rejeté avec un `CommandErrorDto` sûr.
`main.ts` et `splash.ts` installent aussi le bridge `console.*`; l'échec éventuel d'un `invoke` est écrit uniquement sur la console WebView originale afin d'éviter une boucle de logging.
## Bindings TS-RS

View File

@@ -0,0 +1,99 @@
// file: crates/ksp-app-config-desk/frontend/ts/frontend_log.ts
// version: 1
//! Frontend logging helpers routed through the KSP Logging facade.
import { invoke } from "@tauri-apps/api/core";
import type { FrontendLogPayloadDto } from "./bindings/ksp_app_config_desk/frontend_logging/FrontendLogPayloadDto.ts";
export type FrontendLogLevel = "trace" | "debug" | "info" | "warn" | "error";
export type FrontendLogTargetId = "frontend" | "main" | "splash";
type ConsoleMethod = (...items: unknown[]) => void;
const originalConsole = {
trace: console.trace.bind(console),
debug: console.debug.bind(console),
log: console.log.bind(console),
info: console.info.bind(console),
warn: console.warn.bind(console),
error: console.error.bind(console),
};
function stringifyItem(item: unknown): string {
if (item instanceof Error) {
return item.stack ?? item.message;
}
if (typeof item === "string") {
return item;
}
try {
const serialized = JSON.stringify(item);
return serialized ?? String(item);
} catch {
return String(item);
}
}
function formatMessage(items: unknown[]): string {
return items.map(item => stringifyItem(item)).join(" ");
}
async function sendFrontendLog(level: FrontendLogLevel, targetId: FrontendLogTargetId, message: string): Promise<void> {
const payload: FrontendLogPayloadDto = {
level,
targetId,
message,
};
try {
await invoke("emit_frontend_log", { payload });
} catch (caughtError) {
originalConsole.error("KSP frontend logging bridge failed", caughtError);
}
}
export function frontendTrace(targetId: FrontendLogTargetId, ...items: unknown[]): void {
const message = formatMessage(items);
originalConsole.trace(message);
void sendFrontendLog("trace", targetId, message);
}
export function frontendDebug(targetId: FrontendLogTargetId, ...items: unknown[]): void {
const message = formatMessage(items);
originalConsole.debug(message);
void sendFrontendLog("debug", targetId, message);
}
export function frontendInfo(targetId: FrontendLogTargetId, ...items: unknown[]): void {
const message = formatMessage(items);
originalConsole.info(message);
void sendFrontendLog("info", targetId, message);
}
export function frontendWarn(targetId: FrontendLogTargetId, ...items: unknown[]): void {
const message = formatMessage(items);
originalConsole.warn(message);
void sendFrontendLog("warn", targetId, message);
}
export function frontendError(targetId: FrontendLogTargetId, ...items: unknown[]): void {
const message = formatMessage(items);
originalConsole.error(message);
void sendFrontendLog("error", targetId, message);
}
function buildConsoleBridge(level: FrontendLogLevel, targetId: FrontendLogTargetId, original: ConsoleMethod): ConsoleMethod {
return (...items: unknown[]) => {
original(...items);
void sendFrontendLog(level, targetId, formatMessage(items));
};
}
export function installFrontendConsoleBridge(targetId: FrontendLogTargetId): void {
console.trace = buildConsoleBridge("trace", targetId, originalConsole.trace);
console.debug = buildConsoleBridge("debug", targetId, originalConsole.debug);
console.log = buildConsoleBridge("info", targetId, originalConsole.log);
console.info = buildConsoleBridge("info", targetId, originalConsole.info);
console.warn = buildConsoleBridge("warn", targetId, originalConsole.warn);
console.error = buildConsoleBridge("error", targetId, originalConsole.error);
}

View File

@@ -1,16 +1,20 @@
// file: crates/ksp-app-config-desk/frontend/ts/main.ts
// version: 1
// version: 2
import "bootstrap";
import ResizeObserver from "resize-observer-polyfill";
import "simplebar";
import { getCurrentWindow } from "@tauri-apps/api/window";
import { frontendInfo, installFrontendConsoleBridge } from "./frontend_log";
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
installFrontendConsoleBridge("main");
document.addEventListener("DOMContentLoaded", () => {
const windowLabel = getCurrentWindow().label;
const status = document.querySelector<HTMLElement>("#shellStatus");
if (status) {
status.textContent = `Frontend chargé dans la fenêtre '${getCurrentWindow().label}'.`;
status.textContent = `Frontend chargé dans la fenêtre '${windowLabel}'.`;
}
frontendInfo("main", "Config Desk main frontend loaded", { windowLabel });
});

View File

@@ -1,11 +1,16 @@
// file: crates/ksp-app-config-desk/frontend/ts/splash.ts
// version: 1
// version: 2
import { getCurrentWindow } from "@tauri-apps/api/window";
import { frontendInfo, installFrontendConsoleBridge } from "./frontend_log";
installFrontendConsoleBridge("splash");
document.addEventListener("DOMContentLoaded", () => {
const windowLabel = getCurrentWindow().label;
const status = document.querySelector<HTMLElement>("#splash-status");
if (status) {
status.textContent = `Frontend chargé dans la fenêtre '${getCurrentWindow().label}'. Lifecycle complet prévu en pre.008.`;
status.textContent = `Frontend chargé dans la fenêtre '${windowLabel}'. Lifecycle complet prévu en pre.008.`;
}
frontendInfo("splash", "Config Desk splash frontend loaded", { windowLabel });
});

View File

@@ -1,9 +1,17 @@
// file: crates/ksp-app-config-desk/src/constants.rs
// version: 1
// version: 2
//! Application-owned tracing targets and domains.
/// Owning target for backend events emitted by Config Desk.
pub(crate) const TRACING_TARGET: &str = "ksp-app-config-desk";
/// Owning target for generic frontend events emitted through the KSP bridge.
pub(crate) const TRACING_TARGET_FRONTEND: &str = "ksp-app-config-desk.frontend";
/// Owning target for main-window frontend events.
pub(crate) const TRACING_TARGET_FRONTEND_MAIN: &str = "ksp-app-config-desk.frontend.main";
/// Owning target for splash-window frontend events.
pub(crate) const TRACING_TARGET_FRONTEND_SPLASH: &str = "ksp-app-config-desk.frontend.splash";
/// Structured domain used while bootstrapping Config and Logging.
pub(crate) const TRACING_DOMAIN_BOOTSTRAP: &str = "config.bootstrap";
/// Structured domain used by technical frontend events.
pub(crate) const TRACING_DOMAIN_FRONTEND: &str = "frontend";

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-config-desk/src/errors.rs
// version: 2
// version: 3
//! Application-local error codes for the configuration desktop shell.
@@ -11,3 +11,7 @@ pub(crate) const ERROR_CODE_LOGGING_BOOTSTRAP_FAILED: ksp_core_lib::ErrorCode =
pub(crate) const ERROR_CODE_APP_STATE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "app_state_invalid");
/// Shared Config 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("config_desk", "app_state_lock_failed");
/// Frontend logging requested an unsupported level.
pub(crate) const ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "frontend_log_level_invalid");
/// Frontend logging requested a target outside the application whitelist.
pub(crate) const ERROR_CODE_FRONTEND_LOG_TARGET_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "frontend_log_target_invalid");

View File

@@ -0,0 +1,205 @@
// file: crates/ksp-app-config-desk/src/frontend_logging.rs
// version: 1
//! KSP-owned bridge for technical log events emitted by Config Desk frontend scripts.
use ts_rs::TS; // rust-rules: derive-import
/// Log payload sent by Config Desk frontend scripts.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_config_desk/frontend_logging/FrontendLogPayloadDto.ts")]
pub(crate) struct FrontendLogPayloadDto {
/// Lowercase KSP log level.
pub(crate) level: String,
/// Whitelisted logical frontend target identifier.
pub(crate) target_id: String,
/// Rendered technical frontend message.
pub(crate) message: String,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum FrontendLogLevel {
Trace,
Debug,
Info,
Warn,
Error,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum FrontendLogTarget {
Frontend,
Main,
Splash,
}
/// Emits one validated frontend event through the KSP Logging facade.
pub(crate) fn emit_frontend_log_event(payload: FrontendLogPayloadDto) -> ksp_core_lib::Result<()> {
let level = parse_level(payload.level.as_str());
let level = match level {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let target = parse_target(payload.target_id.as_str());
let target = match target {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
emit_validated_frontend_log(level, target, payload.message.as_str());
return std::result::Result::Ok(());
}
fn parse_level(level: &str) -> ksp_core_lib::Result<FrontendLogLevel> {
return match level.trim().to_ascii_lowercase().as_str() {
"trace" => std::result::Result::Ok(FrontendLogLevel::Trace),
"debug" => std::result::Result::Ok(FrontendLogLevel::Debug),
"info" => std::result::Result::Ok(FrontendLogLevel::Info),
"warn" => std::result::Result::Ok(FrontendLogLevel::Warn),
"error" => std::result::Result::Ok(FrontendLogLevel::Error),
_ => std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID, "Frontend log level is not supported")),
};
}
fn parse_target(target_id: &str) -> ksp_core_lib::Result<FrontendLogTarget> {
return match target_id.trim().to_ascii_lowercase().as_str() {
"frontend" => std::result::Result::Ok(FrontendLogTarget::Frontend),
"main" => std::result::Result::Ok(FrontendLogTarget::Main),
"splash" => std::result::Result::Ok(FrontendLogTarget::Splash),
_ => {
std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_FRONTEND_LOG_TARGET_INVALID, "Frontend log target identifier is not supported"))
},
};
}
fn emit_validated_frontend_log(level: FrontendLogLevel, target: FrontendLogTarget, message: &str) {
return match target {
FrontendLogTarget::Frontend => emit_frontend_target(level, message),
FrontendLogTarget::Main => emit_main_target(level, message),
FrontendLogTarget::Splash => emit_splash_target(level, message),
};
}
fn emit_frontend_target(level: FrontendLogLevel, message: &str) {
return match level {
FrontendLogLevel::Trace => ksp_logging_lib::trace!(
target: crate::TRACING_TARGET_FRONTEND,
domain = crate::TRACING_DOMAIN_FRONTEND,
action = "frontend_log",
target_id = "frontend",
"{message}"
),
FrontendLogLevel::Debug => ksp_logging_lib::debug!(
target: crate::TRACING_TARGET_FRONTEND,
domain = crate::TRACING_DOMAIN_FRONTEND,
action = "frontend_log",
target_id = "frontend",
"{message}"
),
FrontendLogLevel::Info => ksp_logging_lib::info!(
target: crate::TRACING_TARGET_FRONTEND,
domain = crate::TRACING_DOMAIN_FRONTEND,
action = "frontend_log",
target_id = "frontend",
"{message}"
),
FrontendLogLevel::Warn => ksp_logging_lib::warn!(
target: crate::TRACING_TARGET_FRONTEND,
domain = crate::TRACING_DOMAIN_FRONTEND,
action = "frontend_log",
target_id = "frontend",
"{message}"
),
FrontendLogLevel::Error => ksp_logging_lib::error!(
target: crate::TRACING_TARGET_FRONTEND,
domain = crate::TRACING_DOMAIN_FRONTEND,
action = "frontend_log",
target_id = "frontend",
"{message}"
),
};
}
fn emit_main_target(level: FrontendLogLevel, message: &str) {
return match level {
FrontendLogLevel::Trace => ksp_logging_lib::trace!(
target: crate::TRACING_TARGET_FRONTEND_MAIN,
domain = crate::TRACING_DOMAIN_FRONTEND,
action = "frontend_log",
target_id = "main",
"{message}"
),
FrontendLogLevel::Debug => ksp_logging_lib::debug!(
target: crate::TRACING_TARGET_FRONTEND_MAIN,
domain = crate::TRACING_DOMAIN_FRONTEND,
action = "frontend_log",
target_id = "main",
"{message}"
),
FrontendLogLevel::Info => ksp_logging_lib::info!(
target: crate::TRACING_TARGET_FRONTEND_MAIN,
domain = crate::TRACING_DOMAIN_FRONTEND,
action = "frontend_log",
target_id = "main",
"{message}"
),
FrontendLogLevel::Warn => ksp_logging_lib::warn!(
target: crate::TRACING_TARGET_FRONTEND_MAIN,
domain = crate::TRACING_DOMAIN_FRONTEND,
action = "frontend_log",
target_id = "main",
"{message}"
),
FrontendLogLevel::Error => ksp_logging_lib::error!(
target: crate::TRACING_TARGET_FRONTEND_MAIN,
domain = crate::TRACING_DOMAIN_FRONTEND,
action = "frontend_log",
target_id = "main",
"{message}"
),
};
}
fn emit_splash_target(level: FrontendLogLevel, message: &str) {
return match level {
FrontendLogLevel::Trace => ksp_logging_lib::trace!(
target: crate::TRACING_TARGET_FRONTEND_SPLASH,
domain = crate::TRACING_DOMAIN_FRONTEND,
action = "frontend_log",
target_id = "splash",
"{message}"
),
FrontendLogLevel::Debug => ksp_logging_lib::debug!(
target: crate::TRACING_TARGET_FRONTEND_SPLASH,
domain = crate::TRACING_DOMAIN_FRONTEND,
action = "frontend_log",
target_id = "splash",
"{message}"
),
FrontendLogLevel::Info => ksp_logging_lib::info!(
target: crate::TRACING_TARGET_FRONTEND_SPLASH,
domain = crate::TRACING_DOMAIN_FRONTEND,
action = "frontend_log",
target_id = "splash",
"{message}"
),
FrontendLogLevel::Warn => ksp_logging_lib::warn!(
target: crate::TRACING_TARGET_FRONTEND_SPLASH,
domain = crate::TRACING_DOMAIN_FRONTEND,
action = "frontend_log",
target_id = "splash",
"{message}"
),
FrontendLogLevel::Error => ksp_logging_lib::error!(
target: crate::TRACING_TARGET_FRONTEND_SPLASH,
domain = crate::TRACING_DOMAIN_FRONTEND,
action = "frontend_log",
target_id = "splash",
"{message}"
),
};
}
#[cfg(test)]
#[path = "../unit_tests/frontend_logging.rs"]
mod tests;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-config-desk/src/lib.rs
// version: 2
// version: 3
//! Tauri desktop application for managing and validating KSP configuration.
@@ -12,6 +12,7 @@ mod bootstrap;
mod constants;
mod dto_common;
mod errors;
mod frontend_logging;
mod tauri;
/// Runs the KSP configuration desktop application.
@@ -21,10 +22,18 @@ pub(crate) use self::app_state::AppState;
pub(crate) use self::bootstrap::config_management;
pub(crate) use self::bootstrap::initialize_logging;
pub(crate) use self::constants::TRACING_DOMAIN_BOOTSTRAP;
pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND;
pub(crate) use self::constants::TRACING_TARGET;
pub(crate) use self::constants::TRACING_TARGET_FRONTEND;
pub(crate) use self::constants::TRACING_TARGET_FRONTEND_MAIN;
pub(crate) use self::constants::TRACING_TARGET_FRONTEND_SPLASH;
pub(crate) use self::dto_common::AppSnapshotDto;
pub(crate) use self::dto_common::CommandErrorDto;
pub(crate) use self::errors::ERROR_CODE_APP_STATE_INVALID;
pub(crate) use self::errors::ERROR_CODE_APP_STATE_LOCK_FAILED;
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID;
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_TARGET_INVALID;
pub(crate) use self::errors::ERROR_CODE_LOGGING_BOOTSTRAP_FAILED;
pub(crate) use self::errors::ERROR_CODE_TAURI_RUNTIME_FAILED;
pub(crate) use self::frontend_logging::FrontendLogPayloadDto;
pub(crate) use self::frontend_logging::emit_frontend_log_event;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-config-desk/src/tauri.rs
// version: 3
// version: 4
//! Tauri runtime assembly for the KSP configuration desktop application.
@@ -38,7 +38,7 @@ fn configure_plugins(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<taur
}
fn configure_commands(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
return builder.invoke_handler(tauri::generate_handler![get_app_snapshot]);
return builder.invoke_handler(tauri::generate_handler![get_app_snapshot, emit_frontend_log]);
}
fn configure_setup(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
@@ -61,3 +61,12 @@ fn get_app_snapshot(state: tauri::State<'_, crate::AppState>) -> std::result::Re
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
};
}
#[tauri::command]
fn emit_frontend_log(payload: crate::FrontendLogPayloadDto) -> std::result::Result<(), crate::CommandErrorDto> {
let result = crate::emit_frontend_log_event(payload);
return match result {
std::result::Result::Ok(()) => std::result::Result::Ok(()),
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
};
}

View File

@@ -0,0 +1,40 @@
// file: crates/ksp-app-config-desk/unit_tests/frontend_logging.rs
// version: 1
#[test]
fn frontend_log_levels_are_explicit_and_case_insensitive() {
assert!(matches!(super::parse_level(" TRACE "), std::result::Result::Ok(super::FrontendLogLevel::Trace)));
assert!(matches!(super::parse_level("debug"), std::result::Result::Ok(super::FrontendLogLevel::Debug)));
assert!(matches!(super::parse_level("Info"), std::result::Result::Ok(super::FrontendLogLevel::Info)));
assert!(matches!(super::parse_level("warn"), std::result::Result::Ok(super::FrontendLogLevel::Warn)));
assert!(matches!(super::parse_level("ERROR"), std::result::Result::Ok(super::FrontendLogLevel::Error)));
}
#[test]
fn unsupported_frontend_log_level_is_rejected() {
let result = super::parse_level("notice");
assert!(result.is_err());
let error = match result {
std::result::Result::Ok(_) => return,
std::result::Result::Err(error) => error,
};
assert_eq!(error.code(), crate::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID);
}
#[test]
fn frontend_log_targets_are_whitelisted() {
assert!(matches!(super::parse_target("frontend"), std::result::Result::Ok(super::FrontendLogTarget::Frontend)));
assert!(matches!(super::parse_target(" MAIN "), std::result::Result::Ok(super::FrontendLogTarget::Main)));
assert!(matches!(super::parse_target("Splash"), std::result::Result::Ok(super::FrontendLogTarget::Splash)));
}
#[test]
fn arbitrary_frontend_log_target_is_rejected() {
let result = super::parse_target("ksp-app-config-desk.test.logging");
assert!(result.is_err());
let error = match result {
std::result::Result::Ok(_) => return,
std::result::Result::Err(error) => error,
};
assert_eq!(error.code(), crate::ERROR_CODE_FRONTEND_LOG_TARGET_INVALID);
}

View File

@@ -97,16 +97,16 @@ Aucun retour vers une API Sass legacy n'est introduit.
## 7. Fichiers modifiés
| Fichier | Action |
| ---------------------------------------------------------------- | ------: |
| `Cargo.toml` | modifié |
| `crates/ksp-app-config-desk/frontend/sass/splash.scss` | modifié |
| `crates/ksp-app-config-desk/vite.config.ts` | modifié |
| `crates/ksp-app-config-desk/frontend/fonts/README.md` | modifié |
| `crates/ksp-app-config-desk/README.md` | modifié |
| `crates/ksp-app-config-desk/USAGE.md` | modifié |
| `crates/ksp-app-config-desk/TODO.md` | modifié |
| `deltas/0.1.4/pre.006-fix.001.md` | ajouté |
| Fichier | Action |
|--------------------------------------------------------|--------:|
| `Cargo.toml` | modifié |
| `crates/ksp-app-config-desk/frontend/sass/splash.scss` | modifié |
| `crates/ksp-app-config-desk/vite.config.ts` | modifié |
| `crates/ksp-app-config-desk/frontend/fonts/README.md` | modifié |
| `crates/ksp-app-config-desk/README.md` | modifié |
| `crates/ksp-app-config-desk/USAGE.md` | modifié |
| `crates/ksp-app-config-desk/TODO.md` | modifié |
| `deltas/0.1.4/pre.006-fix.001.md` | ajouté |
## 8. Validations demandées

209
deltas/0.1.4/pre.007.md Normal file
View File

@@ -0,0 +1,209 @@
<!-- file: deltas/0.1.4/pre.007.md -->
<!-- version: 1 -->
# `0.1.4-pre.007` — Bridge frontend logging KSP
## 1. Base validée
La base `0.1.4-pre.6.fix.1` a été validée localement avec :
```bash
cargo fmt --all
cargo check --workspace
cargo test -p ksp-app-config-desk
cargo test -p ksp-config-lib
cargo tauri dev -c crates/ksp-app-config-desk/tauri.conf.json
```
Les tests de l'application et de Config passent, Vite démarre sur `1430`, le binaire Tauri s'exécute et le fallback Logging transitoire reste observable lorsque `std.logging.json` n'est pas disponible depuis les paths actifs.
## 2. Objectif
Cette tranche reprend/refond le mécanisme `frontend_log.ts` éprouvé dans khadhroony-bot3 afin que les événements techniques des WebViews passent par la façade Logging KSP.
Elle introduit :
- `FrontendLogPayloadDto` avec binding TS-RS ;
- `frontend/ts/frontend_log.ts` ;
- les helpers `frontendTrace` / `frontendDebug` / `frontendInfo` / `frontendWarn` / `frontendError` ;
- un bridge `console.*` pour `trace`, `debug`, `log`, `info`, `warn` et `error` ;
- la commande Tauri centralisée `emit_frontend_log` ;
- une whitelist d'identifiants de targets frontend ;
- des callsites tracing KSP statiques et filtrables ;
- des tests unitaires de validation level/target.
Le panneau manuel `Logging Test`, ses targets de test et ses domaines personnalisables restent hors scope de `pre.007`.
## 3. Version technique
La tranche modifie le runtime Rust et TypeScript :
```text
workspace.package.version = "0.1.4-pre.7"
```
Les versions frontend/Tauri applicatives restent `0.1.4`.
## 4. Différence volontaire avec bot3
Bot3 acceptait un target texte frontend puis réémettait tous les événements sous un seul callsite Rust fixe, le target frontend demandé étant conservé comme champ structuré.
Config Desk doit pouvoir valider les filtres **réels** par target de `ksp-logging-lib`. `pre.007` ne transporte donc plus un target tracing libre. Le DTO expose un `targetId` logique limité à :
```text
frontend
main
splash
```
Rust mappe ces identifiants vers les callsites statiques :
```text
frontend -> ksp-app-config-desk.frontend
main -> ksp-app-config-desk.frontend.main
splash -> ksp-app-config-desk.frontend.splash
```
Un identifiant arbitraire est rejeté. Le frontend ne peut donc ni usurper un target d'une autre crate ni créer dynamiquement une taxonomie de targets.
## 5. Niveau et domaine
Les seuls niveaux acceptés sont :
```text
trace
debug
info
warn
error
```
La casse et les espaces périphériques sont normalisés côté Rust. Tout autre niveau produit `config_desk.frontend_log_level_invalid`.
Les événements techniques du bridge utilisent le domaine structuré fixe :
```text
frontend
```
Le domaine reste distinct du target. Les domaines personnalisables appartiendront au panneau manuel Logging Test et ne sont pas ajoutés à ce DTO technique.
## 6. Ownership Logging
Le chemin est :
```text
frontend helper / console bridge
-> invoke("emit_frontend_log")
-> wrapper #[tauri::command] dans tauri.rs
-> frontend_logging service
-> ksp-logging-lib::{trace,debug,info,warn,error}!
-> subscriber KSP déjà installé
```
`ksp-app-config-desk` n'importe pas directement `tracing` et `tauri-plugin-tracing` n'installe toujours aucun subscriber par défaut.
Le package `@fltsci/tauri-plugin-tracing` reste l'adaptateur frontend correspondant au plugin Tauri, mais le contrat applicatif KSP à target filtrable passe par la commande dédiée ci-dessus.
## 7. DTO et binding TS-RS
Le nouveau DTO est :
```text
FrontendLogPayloadDto
├── level
├── targetId
└── message
```
Le binding est généré sous :
```text
frontend/ts/bindings/ksp_app_config_desk/frontend_logging/FrontendLogPayloadDto.ts
```
Le message est une donnée technique fournie par le frontend. Cette surface ne doit pas être utilisée pour recopier des secrets Config ou des résultats de reveal.
## 8. Bridge console
`installFrontendConsoleBridge(targetId)` conserve les méthodes console originales puis route :
```text
console.trace -> trace
console.debug -> debug
console.log -> info
console.info -> info
console.warn -> warn
console.error -> error
```
L'affichage WebView original est conservé. Si `invoke("emit_frontend_log")` échoue, le diagnostic est écrit avec `originalConsole.error`, et non avec le `console.error` remplacé, afin d'éviter une récursion du bridge.
`main.ts` installe le bridge avec `main`; `splash.ts` avec `splash`. Les deux émettent également un événement `info` lors de leur `DOMContentLoaded`.
## 9. Erreurs applicatives
Deux codes stables sont ajoutés :
```text
config_desk.frontend_log_level_invalid
config_desk.frontend_log_target_invalid
```
Le wrapper Tauri transforme ces erreurs avec le `CommandErrorDto` borné déjà introduit en `pre.006`.
## 10. Tests
Les tests unitaires hors `src/` vérifient :
- les cinq niveaux reconnus et leur normalisation ;
- le rejet d'un niveau inconnu ;
- les trois `targetId` whitelistés ;
- le rejet d'un target arbitraire.
Le derive TS-RS ajoute le test d'export de `FrontendLogPayloadDto`.
## 11. Fichiers
| Fichier | Action |
| ------------------------------------------------------------- | ------: |
| `Cargo.toml` | modifié |
| `crates/ksp-app-config-desk/src/constants.rs` | modifié |
| `crates/ksp-app-config-desk/src/errors.rs` | modifié |
| `crates/ksp-app-config-desk/src/frontend_logging.rs` | ajouté |
| `crates/ksp-app-config-desk/src/lib.rs` | modifié |
| `crates/ksp-app-config-desk/src/tauri.rs` | modifié |
| `crates/ksp-app-config-desk/unit_tests/frontend_logging.rs` | ajouté |
| `crates/ksp-app-config-desk/frontend/ts/frontend_log.ts` | ajouté |
| `crates/ksp-app-config-desk/frontend/ts/main.ts` | modifié |
| `crates/ksp-app-config-desk/frontend/ts/splash.ts` | modifié |
| `crates/ksp-app-config-desk/README.md` | modifié |
| `crates/ksp-app-config-desk/USAGE.md` | modifié |
| `crates/ksp-app-config-desk/TODO.md` | modifié |
| `deltas/0.1.4/pre.007.md` | ajouté |
## 12. Validations à exécuter
Depuis la racine du workspace :
```bash
cargo fmt --all
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-app-config-desk
cargo test -p ksp-config-lib
cargo tree -p ksp-app-config-desk
cargo tauri dev -c crates/ksp-app-config-desk/tauri.conf.json
```
Après les tests Rust, vérifier la présence du binding :
```text
crates/ksp-app-config-desk/frontend/ts/bindings/ksp_app_config_desk/frontend_logging/FrontendLogPayloadDto.ts
```
Au lancement avec le fallback Logging `Info`, les événements `Config Desk splash frontend loaded` doivent être observables sous le target `ksp-app-config-desk.frontend.splash`. Le target `main` deviendra observable dans le lifecycle réel du shell lorsque la fenêtre principale sera affichée.
## 13. Suite
Après validation, `pre.008` traite le shell et le lifecycle splash configurable : readiness frontend, fermeture/masquage du splash, affichage de `main`, et temporisation issue de Config/env plutôt qu'une constante environnementale codée en dur.

View File

@@ -238,8 +238,8 @@ La gestion npm distingue le runtime frontend du tooling. Les bibliothèques cons
Chaque application Tauri desk KSP reçoit un couple de ports Vite/HMR propre. La première application réserve :
| Application | Port Vite HTTP | Port HMR |
| --------------------- | -------------: | -------: |
| `ksp-app-config-desk` | `1430` | `1431` |
|-----------------------|---------------:|---------:|
| `ksp-app-config-desk` | `1430` | `1431` |
Les applications suivantes incrémentent le couple de deux ports (`1432/1433`, puis `1434/1435`, etc.). Vite doit utiliser un port strict afin qu'une collision soit signalée au lieu de provoquer un basculement silencieux vers un autre port. Cette allocation permet de faire fonctionner simultanément plusieurs applications desk en mode développement.
@@ -249,20 +249,20 @@ Aucune dépendance n'est ajoutée par `pre.001`. Les versions suivantes ont ét
| Dépendance | Version actuelle auditée | Usage envisagé |
| ------------------------------- | -----------------------: | -------------------------------------------- |
| `tauri` | `2.11.5` | runtime desktop |
| `tauri-build` | `2.6.3` | build Tauri |
| `tauri-plugin-tracing` | `0.3.4` | intégration tracing à la frontière Tauri |
| `ts-rs` | `12.0.1` | bindings DTO applicatifs |
| `fs2` | `0.4.3` | verrou single-instance |
| `@fltsci/tauri-plugin-tracing` | `0.3.4` | package frontend compagnon du plugin tracing |
| `@tauri-apps/api` | `2.11.1` | frontend Tauri |
| `@tauri-apps/cli` | `2.11.4` | dev/build desktop |
| `@types/bootstrap` | `5.2.11` | typings Bootstrap |
| `vite` | `8.2.1` | build frontend |
| `typescript` | `7.0.2` | frontend TypeScript |
| `sass-embedded` | `1.102.0` | compilation SCSS |
| `bootstrap` | `5.3.8` | composants/layout |
| `@fortawesome/fontawesome-free` | `7.3.1` | iconographie |
| `tauri` | `2.11.5` | runtime desktop |
| `tauri-build` | `2.6.3` | build Tauri |
| `tauri-plugin-tracing` | `0.3.4` | intégration tracing à la frontière Tauri |
| `ts-rs` | `12.0.1` | bindings DTO applicatifs |
| `fs2` | `0.4.3` | verrou single-instance |
| `@fltsci/tauri-plugin-tracing` | `0.3.4` | package frontend compagnon du plugin tracing |
| `@tauri-apps/api` | `2.11.1` | frontend Tauri |
| `@tauri-apps/cli` | `2.11.4` | dev/build desktop |
| `@types/bootstrap` | `5.2.11` | typings Bootstrap |
| `vite` | `8.2.1` | build frontend |
| `typescript` | `7.0.2` | frontend TypeScript |
| `sass-embedded` | `1.102.0` | compilation SCSS |
| `bootstrap` | `5.3.8` | composants/layout |
| `@fortawesome/fontawesome-free` | `7.3.1` | iconographie |
Sources d'audit :
@@ -691,23 +691,23 @@ En parallèle, le `frontend_log.ts` commun reprend/refond le mécanisme bot3 pou
Les noms ci-dessous sont les noms fonctionnels cibles ; ils pourront être normalisés avant implémentation, mais leurs responsabilités sont fixées.
| Commande Tauri | Service interne | API KSP principale | Secret réel ? |
| Commande Tauri | Service interne | API KSP principale | Secret réel ? |
| ------------------------------ | ------------------------------------ | --------------------------------------------------------------------------- | -------------------------------------------: |
| `get_app_snapshot` | `config_service` + `logging_service` | registry/management + runtime state | non |
| `list_config_documents` | `config_service` | future vue publique du `ConfigFileRegistry` | non |
| `inspect_config_document` | `config_service` | `load_validated_document` + `read_source` si erreur | non |
| `save_config_source_candidate` | `config_service` | future API Config de réparation validée | non |
| `inspect_profile` | `config_service` | `load_resolved_profile` + résolution détaillée | non |
| `get_environment_report` | `environment_service` | `ConfigManagement::environment_report` | non |
| `get_app_snapshot` | `config_service` + `logging_service` | registry/management + runtime state | non |
| `list_config_documents` | `config_service` | future vue publique du `ConfigFileRegistry` | non |
| `inspect_config_document` | `config_service` | `load_validated_document` + `read_source` si erreur | non |
| `save_config_source_candidate` | `config_service` | future API Config de réparation validée | non |
| `inspect_profile` | `config_service` | `load_resolved_profile` + résolution détaillée | non |
| `get_environment_report` | `environment_service` | `ConfigManagement::environment_report` | non |
| `set_dotenv_value` | `environment_service` | `ConfigManagement::set_dotenv_value` + rapport frais | entrée potentiellement Secret, jamais loggée |
| `remove_dotenv_value` | `environment_service` | `ConfigManagement::remove_dotenv_value` + rapport frais | non |
| `reveal_environment_value` | `secret_service` | `reveal_effective_environment_value` ou `reveal_dotenv_value` | **oui** |
| `get_logging_editor` | `logging_service` | `load_logging_document` | non |
| `save_logging_document` | `logging_service` | `save_logging_document` | non par design Logging |
| `apply_logging_profile` | `logging_service` | `ConfigEnvironment::load` + `load_resolved_logging_config` + `reinitialize` | valeurs resolved possibles, jamais DTO/log |
| `emit_frontend_log` | `frontend_logging_service` | façade `ksp-logging-lib` | non, message technique frontend seulement |
| `emit_logging_test` | `logging_service` | façade/macros `ksp-logging-lib` | non, message explicitement saisi pour test |
| `splash_frontend_ready` | `splash` | lifecycle Tauri + settings splash déjà résolus | non |
| `remove_dotenv_value` | `environment_service` | `ConfigManagement::remove_dotenv_value` + rapport frais | non |
| `reveal_environment_value` | `secret_service` | `reveal_effective_environment_value` ou `reveal_dotenv_value` | **oui** |
| `get_logging_editor` | `logging_service` | `load_logging_document` | non |
| `save_logging_document` | `logging_service` | `save_logging_document` | non par design Logging |
| `apply_logging_profile` | `logging_service` | `ConfigEnvironment::load` + `load_resolved_logging_config` + `reinitialize` | valeurs resolved possibles, jamais DTO/log |
| `emit_frontend_log` | `frontend_logging_service` | façade `ksp-logging-lib` | non, message technique frontend seulement |
| `emit_logging_test` | `logging_service` | façade/macros `ksp-logging-lib` | non, message explicitement saisi pour test |
| `splash_frontend_ready` | `splash` | lifecycle Tauri + settings splash déjà résolus | non |
Tous les wrappers dans `tauri.rs` :