diff --git a/Cargo.toml b/Cargo.toml index 9f326c4..5116f35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ # file: Cargo.toml -# version: 88 +# version: 89 [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.16.fix.2" +version = "0.1.4-pre.17" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" diff --git a/crates/ksp-app-config-desk/README.md b/crates/ksp-app-config-desk/README.md index a43b564..a5a262a 100644 --- a/crates/ksp-app-config-desk/README.md +++ b/crates/ksp-app-config-desk/README.md @@ -1,5 +1,5 @@ - + # `ksp-app-config-desk` @@ -113,7 +113,13 @@ 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 pont actuel garantit donc le trajet WebView -> Rust tout en conservant l'affichage local des appels `console.*` dans la console WebKit. Il ne réémet pas encore les événements Rust arbitraires vers la console WebKit : cette direction nécessitera une intégration contrôlée de `tauri_plugin_tracing::WebviewLayer` dans le subscriber possédé par `ksp-logging-lib`, sans second subscriber ni boucle avec le bridge KSP. Le panneau manuel de test Logging, ses targets `test.*` et ses domaines personnalisables restent une surface distincte prévue plus tard. +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 pont actuel garantit donc le trajet WebView -> Rust tout en conservant l'affichage local des appels `console.*` dans la console WebKit. Il ne réémet pas encore les événements Rust arbitraires vers la console WebKit : cette direction nécessitera une intégration contrôlée de `tauri_plugin_tracing::WebviewLayer` dans le subscriber possédé par `ksp-logging-lib`, sans second subscriber ni boucle avec le bridge KSP. Le panneau **Test Logging** complète désormais ce bridge : il peut émettre des événements backend via `ksp-logging-lib` avec target KSP statique et domain contrôlé, ou réutiliser le bridge frontend existant avec son target/domain fixes. + +## Panneau Test Logging + +`pre.017` ajoute une surface de validation volontairement explicite. Le champ **Message** est le contenu qui sera réellement journalisé ; il ne doit donc jamais recevoir de Secret. **Log backend** appelle `emit_logging_test`, qui valide le niveau (`trace/debug/info/warn/error/tous`), choisit un target statique (`ksp-app-config-desk` ou `ksp-app-config-desk.logging-test`) et applique un domain absent, connu ou personnalisé borné avant d'émettre exclusivement avec les macros `ksp-logging-lib`. Le résultat retourne uniquement des métadonnées sûres : nombre d'événements, niveau demandé, target/domain effectifs et génération runtime. + +**Log via bridge frontend** réutilise le bridge déjà installé. Son contrat reste volontairement fixe : `target=ksp-app-config-desk.frontend.main`, `domain=frontend`. Cela permet de comparer dans le même panneau le routing backend et le chemin WebView -> Rust -> `ksp-logging-lib`, avant et après modification/hot reload des filtres. ## Bootstrap Config et Logging diff --git a/crates/ksp-app-config-desk/TODO.md b/crates/ksp-app-config-desk/TODO.md index 03fe2f1..711c096 100644 --- a/crates/ksp-app-config-desk/TODO.md +++ b/crates/ksp-app-config-desk/TODO.md @@ -1,12 +1,11 @@ - + # TODO `ksp-app-config-desk` -Le gabarit frontend, le bootstrap Config/Logging, `AppState`, le `LoggingGuard` durable, les premiers DTO TS-RS, le bridge frontend logging KSP, le lifecycle `splash -> main`, les panneaux Documents, Profils/provenance et Environnement avec management `.env`/reveal Secret privilégié, ainsi que l'édition typée/persistence atomique du document Logging, son hot reload transactionnel, la sélection explicite du profil runtime et les fichiers isolés par identité de lancement sont maintenant présents. +Le gabarit frontend, le bootstrap Config/Logging, `AppState`, le `LoggingGuard` durable, les premiers DTO TS-RS, le bridge frontend logging KSP, le lifecycle `splash -> main`, les panneaux Documents, Profils/provenance et Environnement avec management `.env`/reveal Secret privilégié, ainsi que l'édition typée/persistence atomique du document Logging, son hot reload transactionnel, la sélection explicite du profil runtime, les fichiers isolés par identité de lancement et le panneau Test Logging backend/frontend sont maintenant présents. Les prochaines tranches doivent encore : - ramener le target applicatif de `debug` à `info` ou `warn` avant clôture de la release ; -- démontrer le routing `trace/debug/info/warn/error`, targets et domains avant/après hot reload avec le panneau Test Logging ; - étudier puis intégrer le retour Rust -> console WebKit via `tauri_plugin_tracing::WebviewLayer` dans le subscriber possédé par `ksp-logging-lib`, afin de pouvoir utiliser `attachConsole()` sans second subscriber, double émission ni boucle avec le bridge KSP. diff --git a/crates/ksp-app-config-desk/USAGE.md b/crates/ksp-app-config-desk/USAGE.md index 32f707d..7da0364 100644 --- a/crates/ksp-app-config-desk/USAGE.md +++ b/crates/ksp-app-config-desk/USAGE.md @@ -1,11 +1,11 @@ - + # Utilisation de `ksp-app-config-desk` ## État actuel -Le gabarit Rust/Tauri et le frontend Vite/TypeScript/SCSS sont présents. Le backend initialise Config, le runtime Logging et `AppState`. Les panneaux Documents, Profils et Environnement/`.env` sont fonctionnels, y compris les mutations `.env` create/update/remove et les reveals Secrets privilégiés. Logging expose maintenant un brouillon typé éditable, une persistence atomique, un hot reload sans redémarrage, un profil runtime sélectionnable indépendamment du `default_profile` et une identité de lancement observable pour isoler les fichiers persistants. +Le gabarit Rust/Tauri et le frontend Vite/TypeScript/SCSS sont présents. Le backend initialise Config, le runtime Logging et `AppState`. Les panneaux Documents, Profils et Environnement/`.env` sont fonctionnels, y compris les mutations `.env` create/update/remove et les reveals Secrets privilégiés. Logging expose maintenant un brouillon typé éditable, une persistence atomique, un hot reload sans redémarrage, un profil runtime sélectionnable indépendamment du `default_profile`, une identité de lancement observable pour isoler les fichiers persistants et un panneau de test du routing backend/frontend. La fenêtre `splash` est visible au démarrage. Après readiness du frontend et temporisation résolue par Config, l'application effectue le fade-out, affiche/focalise `main` puis détruit `splash`. La fenêtre principale expose immédiatement la navigation monofenêtre de référence. @@ -214,6 +214,23 @@ Le sélecteur **Profil édité** travaille sur un brouillon local. `logs_directo +## Test Logging — routing backend et bridge frontend + +La section **Test Logging — routing contrôlé** utilise un message explicitement destiné aux logs. Ne jamais y copier une valeur `KSP_SECRET_*` / `KSPB_SECRET_*`. + +Pour le backend : + +1. choisir `trace`, `debug`, `info`, `warn`, `error` ou `tous` ; +2. choisir `ksp-app-config-desk` ou `ksp-app-config-desk.logging-test` ; +3. choisir un domain `absent`, `connu` (`config.logging_test`, `config.logging_runtime`, `frontend`) ou `personnalisé` ; +4. cliquer **Log backend**. + +La commande `emit_logging_test` renvoie le nombre d'événements émis et la génération runtime active, mais ne duplique pas le message dans son DTO de résultat. Le test `tous` émet exactement cinq événements, un par niveau. + +**Log via bridge frontend** réutilise le même message et le même niveau, mais garde le contrat du bridge : `target=ksp-app-config-desk.frontend.main`, `domain=frontend`. En mode `tous`, cinq événements sont envoyés. Cette voie permet de vérifier que les changements de console/file sinks/target/domain appliqués par hot reload affectent aussi les événements issus de la WebView. + +Scénario conseillé : émettre `tous`, modifier les filtres du profil actif, **Sauvegarder et appliquer**, réémettre exactement le même test et comparer les sinks réellement alimentés sans redémarrer l'application. + ## Runtime Logging — profil actif, génération et fichiers par lancement La section **Runtime actif** du panneau Logging ne décrit pas le brouillon : elle interroge l'état réellement installé dans `ksp-logging-lib`. diff --git a/crates/ksp-app-config-desk/frontend/main.html b/crates/ksp-app-config-desk/frontend/main.html index d4284f8..b1a0e7c 100644 --- a/crates/ksp-app-config-desk/frontend/main.html +++ b/crates/ksp-app-config-desk/frontend/main.html @@ -1,5 +1,5 @@ - + @@ -360,6 +360,66 @@ +
+
+
+

Test Logging — routing contrôlé

+

Émet des événements volontairement journalisés pour vérifier niveaux, targets, domains, sinks et hot reload. Ne colle jamais de Secret dans le message de test.

+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
Le test backend respecte le target/domain choisis. Le bridge frontend conserve volontairement son contrat fixe target=ksp-app-config-desk.frontend.main, domain=frontend et réutilise uniquement le message/niveau.
+
Aucun événement de test émis pendant ce lancement.
+
+

Globals

diff --git a/crates/ksp-app-config-desk/frontend/ts/frontend_log.ts b/crates/ksp-app-config-desk/frontend/ts/frontend_log.ts index e63812c..8ca6624 100644 --- a/crates/ksp-app-config-desk/frontend/ts/frontend_log.ts +++ b/crates/ksp-app-config-desk/frontend/ts/frontend_log.ts @@ -1,5 +1,5 @@ // file: crates/ksp-app-config-desk/frontend/ts/frontend_log.ts -// version: 1 +// version: 2 //! Frontend logging helpers routed through the KSP Logging facade. @@ -45,47 +45,73 @@ async function sendFrontendLog(level: FrontendLogLevel, targetId: FrontendLogTar targetId, message, }; - try { - await invoke("emit_frontend_log", { payload }); - } catch (caughtError) { - originalConsole.error("KSP frontend logging bridge failed", caughtError); - } + await invoke("emit_frontend_log", { payload }); +} + +function writeOriginalConsole(level: FrontendLogLevel, message: string): void { + return level === "trace" + ? originalConsole.trace(message) + : level === "debug" + ? originalConsole.debug(message) + : level === "info" + ? originalConsole.info(message) + : level === "warn" + ? originalConsole.warn(message) + : originalConsole.error(message); +} + +/// Emits one frontend event and resolves only after the Rust bridge accepted it. +export async function emitFrontendLog(level: FrontendLogLevel, targetId: FrontendLogTargetId, message: string): Promise { + writeOriginalConsole(level, message); + await sendFrontendLog(level, targetId, message); } export function frontendTrace(targetId: FrontendLogTargetId, ...items: unknown[]): void { const message = formatMessage(items); originalConsole.trace(message); - void sendFrontendLog("trace", targetId, message); + void sendFrontendLog("trace", targetId, message).catch(caughtError => + originalConsole.error("KSP frontend logging bridge failed", caughtError), + ); } export function frontendDebug(targetId: FrontendLogTargetId, ...items: unknown[]): void { const message = formatMessage(items); originalConsole.debug(message); - void sendFrontendLog("debug", targetId, message); + void sendFrontendLog("debug", targetId, message).catch(caughtError => + originalConsole.error("KSP frontend logging bridge failed", caughtError), + ); } export function frontendInfo(targetId: FrontendLogTargetId, ...items: unknown[]): void { const message = formatMessage(items); originalConsole.info(message); - void sendFrontendLog("info", targetId, message); + void sendFrontendLog("info", targetId, message).catch(caughtError => + originalConsole.error("KSP frontend logging bridge failed", caughtError), + ); } export function frontendWarn(targetId: FrontendLogTargetId, ...items: unknown[]): void { const message = formatMessage(items); originalConsole.warn(message); - void sendFrontendLog("warn", targetId, message); + void sendFrontendLog("warn", targetId, message).catch(caughtError => + originalConsole.error("KSP frontend logging bridge failed", caughtError), + ); } export function frontendError(targetId: FrontendLogTargetId, ...items: unknown[]): void { const message = formatMessage(items); originalConsole.error(message); - void sendFrontendLog("error", targetId, message); + void sendFrontendLog("error", targetId, message).catch(caughtError => + originalConsole.error("KSP frontend logging bridge failed", caughtError), + ); } function buildConsoleBridge(level: FrontendLogLevel, targetId: FrontendLogTargetId, original: ConsoleMethod): ConsoleMethod { return (...items: unknown[]) => { original(...items); - void sendFrontendLog(level, targetId, formatMessage(items)); + void sendFrontendLog(level, targetId, formatMessage(items)).catch(caughtError => + originalConsole.error("KSP frontend logging bridge failed", caughtError), + ); }; } diff --git a/crates/ksp-app-config-desk/frontend/ts/logging.ts b/crates/ksp-app-config-desk/frontend/ts/logging.ts index 5f209b0..fb06718 100644 --- a/crates/ksp-app-config-desk/frontend/ts/logging.ts +++ b/crates/ksp-app-config-desk/frontend/ts/logging.ts @@ -1,5 +1,5 @@ // file: crates/ksp-app-config-desk/frontend/ts/logging.ts -// version: 5 +// version: 6 //! Typed Logging editor backed by ConfigManagement persistence and KSP-owned runtime hot reload. @@ -13,6 +13,7 @@ import type { LoggingTargetFilterDto } from "./bindings/ksp_app_config_desk/logg import type { LoggingRuntimeStatusDto } from "./bindings/ksp_app_config_desk/logging_runtime/LoggingRuntimeStatusDto"; import { frontendDebug, frontendTrace } from "./frontend_log"; import { invokeKsp } from "./invoke"; +import { initializeLoggingTestPanel } from "./logging_test"; const LEVELS = ["off", "error", "warn", "info", "debug", "trace"] as const; const ROTATIONS = ["never", "hourly", "daily"] as const; @@ -772,6 +773,7 @@ export function initializeLoggingPanel(): void { document.querySelector("#loggingDiscardDraftConfirm")?.addEventListener("click", confirmReload); document.querySelector("#addLoggingFile")?.addEventListener("click", addFile); document.querySelector("#addLoggingTargetFilter")?.addEventListener("click", addTargetFilter); + initializeLoggingTestPanel(); frontendTrace("main", "Logging editor mutation handlers installed"); void refreshLoggingDocument("startup"); } diff --git a/crates/ksp-app-config-desk/frontend/ts/logging_test.ts b/crates/ksp-app-config-desk/frontend/ts/logging_test.ts new file mode 100644 index 0000000..c92e498 --- /dev/null +++ b/crates/ksp-app-config-desk/frontend/ts/logging_test.ts @@ -0,0 +1,122 @@ +// file: crates/ksp-app-config-desk/frontend/ts/logging_test.ts +// version: 1 + +//! Controlled Logging test panel for proving backend routing and the frontend bridge. + +import type { LoggingTestRequestDto } from "./bindings/ksp_app_config_desk/logging_test/LoggingTestRequestDto"; +import type { LoggingTestResultDto } from "./bindings/ksp_app_config_desk/logging_test/LoggingTestResultDto"; +import { emitFrontendLog, frontendTrace, type FrontendLogLevel } from "./frontend_log"; +import { invokeKsp } from "./invoke"; + +const FRONTEND_BRIDGE_TARGET = "ksp-app-config-desk.frontend.main"; +const FRONTEND_BRIDGE_DOMAIN = "frontend"; + +function inputValue(selector: string): string { + return document.querySelector(selector)?.value ?? ""; +} + +function selectedValue(selector: string): string { + return document.querySelector(selector)?.value ?? ""; +} + +function setStatus(message: string, tone: "secondary" | "success" | "warning" | "danger" = "secondary"): void { + const status = document.querySelector("#loggingTestStatus"); + if (!status) { + return; + } + status.className = `alert alert-${tone} mb-0`; + status.textContent = message; +} + +function updateDomainControls(): void { + const mode = selectedValue("#loggingTestDomainMode"); + const known = document.querySelector("#loggingTestKnownDomain"); + const custom = document.querySelector("#loggingTestCustomDomain"); + if (known) { + known.disabled = mode !== "known"; + } + if (custom) { + custom.disabled = mode !== "custom"; + } +} + +function effectiveDomain(): string { + const mode = selectedValue("#loggingTestDomainMode"); + if (mode === "known") { + return selectedValue("#loggingTestKnownDomain"); + } + if (mode === "custom") { + return inputValue("#loggingTestCustomDomain"); + } + return ""; +} + +function buildRequest(): LoggingTestRequestDto { + return { + level: selectedValue("#loggingTestLevel"), + targetId: selectedValue("#loggingTestTarget"), + domainMode: selectedValue("#loggingTestDomainMode"), + domain: effectiveDomain(), + message: inputValue("#loggingTestMessage"), + }; +} + +async function emitBackendTest(): Promise { + const request = buildRequest(); + setStatus("Émission backend en cours..."); + try { + const result = await invokeKsp("main", "emit_logging_test", { request }); + const domain = result.domain ?? ""; + const summary = `${result.emittedEventCount} événement(s) backend émis · niveau=${result.requestedLevel} · target=${result.target}` + + ` · domain=${domain} · génération=${result.loggingGeneration}.`; + setStatus(summary, "success"); + } catch { + setStatus("Émission backend refusée. Vérifie le message, le target et le domain sélectionnés.", "danger"); + } +} + +async function emitFrontendLevel(level: FrontendLogLevel, message: string): Promise { + await emitFrontendLog(level, "main", message); +} + +async function emitFrontendBridgeTest(): Promise { + const message = inputValue("#loggingTestMessage").trim(); + if (message.length === 0 || message.length > 500) { + setStatus("Le message de test frontend doit contenir entre 1 et 500 caractères.", "danger"); + return; + } + const level = selectedValue("#loggingTestLevel"); + setStatus("Émission via bridge frontend en cours..."); + try { + if (level === "all") { + for (const item of ["trace", "debug", "info", "warn", "error"] as const) { + await emitFrontendLevel(item, `[logging-test frontend/${item}] ${message}`); + } + setStatus( + `5 événements acceptés par le bridge frontend · target=${FRONTEND_BRIDGE_TARGET} · domain=${FRONTEND_BRIDGE_DOMAIN}.`, + "success", + ); + return; + } + if (level === "trace" || level === "debug" || level === "info" || level === "warn" || level === "error") { + await emitFrontendLevel(level, `[logging-test frontend/${level}] ${message}`); + setStatus( + `1 événement accepté par le bridge frontend · target=${FRONTEND_BRIDGE_TARGET} · domain=${FRONTEND_BRIDGE_DOMAIN}.`, + "success", + ); + return; + } + setStatus("Le niveau de test frontend sélectionné n'est pas supporté.", "danger"); + } catch { + setStatus("Le bridge frontend n'a pas accepté l'événement de test.", "danger"); + } +} + +/// Initializes the controlled Logging test surface. +export function initializeLoggingTestPanel(): void { + document.querySelector("#loggingTestDomainMode")?.addEventListener("change", updateDomainControls); + document.querySelector("#emitLoggingTestBackend")?.addEventListener("click", () => void emitBackendTest()); + document.querySelector("#emitLoggingTestFrontend")?.addEventListener("click", () => void emitFrontendBridgeTest()); + updateDomainControls(); + frontendTrace("main", "Logging test panel handlers installed"); +} diff --git a/crates/ksp-app-config-desk/frontend/ts/main.ts b/crates/ksp-app-config-desk/frontend/ts/main.ts index 5ffe28d..1b138fa 100644 --- a/crates/ksp-app-config-desk/frontend/ts/main.ts +++ b/crates/ksp-app-config-desk/frontend/ts/main.ts @@ -1,5 +1,5 @@ // file: crates/ksp-app-config-desk/frontend/ts/main.ts -// version: 11 +// version: 12 import "bootstrap"; import ResizeObserver from "resize-observer-polyfill"; @@ -38,7 +38,7 @@ const viewCopy: Record = { }, logging: { title: "Logging", - description: "Édition typée et persistence atomique des profils, sinks et filtres Logging ; le hot reload runtime reste dans la tranche suivante.", + description: "Édition typée, hot reload runtime, profils actifs et panneau de test du routing Logging KSP.", }, }; diff --git a/crates/ksp-app-config-desk/src/constants.rs b/crates/ksp-app-config-desk/src/constants.rs index 111d2b1..60f8cd1 100644 --- a/crates/ksp-app-config-desk/src/constants.rs +++ b/crates/ksp-app-config-desk/src/constants.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-config-desk/src/constants.rs -// version: 8 +// version: 9 //! Application-owned tracing targets and domains. @@ -11,6 +11,8 @@ pub(crate) const TRACING_TARGET_FRONTEND: &str = "ksp-app-config-desk.frontend"; 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"; +/// Dedicated target used by the controlled Logging test panel. +pub(crate) const TRACING_TARGET_LOGGING_TEST: &str = "ksp-app-config-desk.logging-test"; /// Structured domain used while bootstrapping Config and Logging. pub(crate) const TRACING_DOMAIN_BOOTSTRAP: &str = "config.bootstrap"; /// Structured domain used by technical frontend events. @@ -29,3 +31,5 @@ pub(crate) const TRACING_DOMAIN_SECRETS: &str = "config.secrets"; pub(crate) const TRACING_DOMAIN_LOGGING_EDITOR: &str = "config.logging_editor"; /// Structured domain for active Logging runtime metadata and explicit profile application. pub(crate) const TRACING_DOMAIN_LOGGING_RUNTIME: &str = "config.logging_runtime"; +/// Structured domain used by controlled Logging test events. +pub(crate) const TRACING_DOMAIN_LOGGING_TEST: &str = "config.logging_test"; diff --git a/crates/ksp-app-config-desk/src/errors.rs b/crates/ksp-app-config-desk/src/errors.rs index 0cb342b..4f9f87f 100644 --- a/crates/ksp-app-config-desk/src/errors.rs +++ b/crates/ksp-app-config-desk/src/errors.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-config-desk/src/errors.rs -// version: 8 +// version: 9 //! Application-local error codes for the configuration desktop shell. @@ -18,6 +18,8 @@ pub(crate) const ERROR_CODE_APP_STATE_LOCK_FAILED: ksp_core_lib::ErrorCode = ksp 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"); +/// Controlled Logging test request contains an unsupported or invalid selector. +pub(crate) const ERROR_CODE_LOGGING_TEST_REQUEST_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "logging_test_request_invalid"); /// A validated Config document does not expose the standard profile contract expected by the Profiles panel. pub(crate) const ERROR_CODE_PROFILE_CONTRACT_MISSING: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "profile_contract_missing"); diff --git a/crates/ksp-app-config-desk/src/lib.rs b/crates/ksp-app-config-desk/src/lib.rs index df6aa88..076a5ff 100644 --- a/crates/ksp-app-config-desk/src/lib.rs +++ b/crates/ksp-app-config-desk/src/lib.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-config-desk/src/lib.rs -// version: 13 +// version: 14 //! Tauri desktop application for managing and validating KSP configuration. @@ -17,6 +17,7 @@ mod errors; mod frontend_logging; mod logging_editor; mod logging_runtime; +mod logging_test; mod profiles; mod secrets; mod splash; @@ -36,6 +37,7 @@ pub(crate) use self::constants::TRACING_DOMAIN_ENVIRONMENT; pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND; pub(crate) use self::constants::TRACING_DOMAIN_LOGGING_EDITOR; pub(crate) use self::constants::TRACING_DOMAIN_LOGGING_RUNTIME; +pub(crate) use self::constants::TRACING_DOMAIN_LOGGING_TEST; pub(crate) use self::constants::TRACING_DOMAIN_PROFILES; pub(crate) use self::constants::TRACING_DOMAIN_SECRETS; pub(crate) use self::constants::TRACING_DOMAIN_WINDOWS; @@ -43,6 +45,7 @@ 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::constants::TRACING_TARGET_LOGGING_TEST; pub(crate) use self::documents::ConfigDocumentDetailDto; pub(crate) use self::documents::ConfigDocumentErrorDto; pub(crate) use self::documents::ConfigDocumentSaveResultDto; @@ -58,6 +61,7 @@ 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_LOGGING_SOURCE_ROLLBACK_FAILED; +pub(crate) use self::errors::ERROR_CODE_LOGGING_TEST_REQUEST_INVALID; pub(crate) use self::errors::ERROR_CODE_PROFILE_CONTRACT_MISSING; pub(crate) use self::errors::ERROR_CODE_PROFILE_PROJECTION_FAILED; pub(crate) use self::errors::ERROR_CODE_SECRET_REVEAL_REQUIRES_SECRET; @@ -73,6 +77,7 @@ pub(crate) use self::logging_editor::LoggingDocumentCandidateDto; pub(crate) use self::logging_editor::LoggingDocumentDto; pub(crate) use self::logging_editor::LoggingDocumentSaveResultDto; pub(crate) use self::logging_runtime::{LoggingRuntimeFileDto, LoggingRuntimeStatusDto}; +pub(crate) use self::logging_test::{LoggingTestRequestDto, LoggingTestResultDto}; pub(crate) use self::profiles::ConfigProfileDetailDto; pub(crate) use self::profiles::ConfigProfileDocumentDto; pub(crate) use self::secrets::SecretRevealRequestDto; diff --git a/crates/ksp-app-config-desk/src/logging_test.rs b/crates/ksp-app-config-desk/src/logging_test.rs new file mode 100644 index 0000000..4d4b41a --- /dev/null +++ b/crates/ksp-app-config-desk/src/logging_test.rs @@ -0,0 +1,428 @@ +// file: crates/ksp-app-config-desk/src/logging_test.rs +// version: 1 + +//! Controlled Logging test events for validating KSP runtime routing from Config Desk. + +use ts_rs::TS; // rust-rules: derive-import + +/// Controlled request emitted through the backend KSP Logging facade. +#[derive(Clone, Debug, serde::Deserialize, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_config_desk/logging_test/LoggingTestRequestDto.ts")] +pub(crate) struct LoggingTestRequestDto { + /// Requested level: trace/debug/info/warn/error/all. + pub(crate) level: String, + /// Controlled backend target identifier. + pub(crate) target_id: String, + /// Domain selection mode: absent/known/custom. + pub(crate) domain_mode: String, + /// Selected known domain or custom domain token. + pub(crate) domain: String, + /// Human test message emitted as the event body. + pub(crate) message: String, +} + +/// Safe result metadata for one controlled backend Logging test request. +#[derive(Clone, Debug, serde::Serialize, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_config_desk/logging_test/LoggingTestResultDto.ts")] +pub(crate) struct LoggingTestResultDto { + /// Number of Logging events emitted by the request. + pub(crate) emitted_event_count: u32, + /// Requested level selector. + pub(crate) requested_level: String, + /// Effective static KSP target used by the emitted events. + pub(crate) target: String, + /// Effective domain, or `None` when domain routing is intentionally absent. + pub(crate) domain: std::option::Option, + /// Logging runtime generation active while the events were emitted. + pub(crate) logging_generation: u32, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum LoggingTestLevel { + Trace, + Debug, + Info, + Warn, + Error, + All, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum LoggingTestTarget { + App, + Dedicated, +} + +/// Emits one controlled backend test request through `ksp-logging-lib` only. +pub(crate) fn emit(state: &crate::AppState, request: LoggingTestRequestDto) -> ksp_core_lib::Result { + let level = parse_level(request.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(request.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), + }; + let domain = resolve_domain(request.domain_mode.as_str(), request.domain.as_str()); + let domain = match domain { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let message = validate_message(request.message.as_str()); + let message = match message { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let runtime_status = state.logging_runtime_status(); + let runtime_status = match runtime_status { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let emitted_event_count = emit_levels(level, target, domain.as_deref(), message); + return std::result::Result::Ok(LoggingTestResultDto { + emitted_event_count, + requested_level: request.level.trim().to_ascii_lowercase(), + target: target_value(target).to_owned(), + domain, + logging_generation: runtime_status.generation, + }); +} + +fn parse_level(level: &str) -> ksp_core_lib::Result { + return match level.trim().to_ascii_lowercase().as_str() { + "trace" => std::result::Result::Ok(LoggingTestLevel::Trace), + "debug" => std::result::Result::Ok(LoggingTestLevel::Debug), + "info" => std::result::Result::Ok(LoggingTestLevel::Info), + "warn" => std::result::Result::Ok(LoggingTestLevel::Warn), + "error" => std::result::Result::Ok(LoggingTestLevel::Error), + "all" => std::result::Result::Ok(LoggingTestLevel::All), + _ => invalid_request("Logging test level is not supported"), + }; +} + +fn parse_target(target_id: &str) -> ksp_core_lib::Result { + return match target_id.trim().to_ascii_lowercase().as_str() { + "app" => std::result::Result::Ok(LoggingTestTarget::App), + "logging_test" => std::result::Result::Ok(LoggingTestTarget::Dedicated), + _ => invalid_request("Logging test target is not supported"), + }; +} + +fn resolve_domain(domain_mode: &str, domain: &str) -> ksp_core_lib::Result> { + return match domain_mode.trim().to_ascii_lowercase().as_str() { + "absent" => std::result::Result::Ok(std::option::Option::None), + "known" => resolve_known_domain(domain), + "custom" => resolve_custom_domain(domain), + _ => invalid_request("Logging test domain mode is not supported"), + }; +} + +fn resolve_known_domain(domain: &str) -> ksp_core_lib::Result> { + return match domain.trim() { + crate::TRACING_DOMAIN_LOGGING_TEST | crate::TRACING_DOMAIN_LOGGING_RUNTIME | crate::TRACING_DOMAIN_FRONTEND => { + std::result::Result::Ok(std::option::Option::Some(domain.trim().to_owned())) + }, + _ => invalid_request("Logging test known domain is not supported"), + }; +} + +fn resolve_custom_domain(domain: &str) -> ksp_core_lib::Result> { + let domain = domain.trim(); + if domain.is_empty() || domain.len() > 80 || !domain.chars().all(is_domain_character) { + return invalid_request("Logging test custom domain is invalid"); + } + return std::result::Result::Ok(std::option::Option::Some(domain.to_owned())); +} + +fn is_domain_character(character: char) -> bool { + return character.is_ascii_alphanumeric() || character == '.' || character == '_' || character == '-'; +} + +fn validate_message(message: &str) -> ksp_core_lib::Result<&str> { + let message = message.trim(); + if message.is_empty() || message.len() > 500 { + return invalid_request("Logging test message must contain between 1 and 500 bytes"); + } + return std::result::Result::Ok(message); +} + +fn invalid_request(message: &str) -> ksp_core_lib::Result { + return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_LOGGING_TEST_REQUEST_INVALID, message)); +} + +const fn target_value(target: LoggingTestTarget) -> &'static str { + return match target { + LoggingTestTarget::App => crate::TRACING_TARGET, + LoggingTestTarget::Dedicated => crate::TRACING_TARGET_LOGGING_TEST, + }; +} + +fn emit_levels(level: LoggingTestLevel, target: LoggingTestTarget, domain: std::option::Option<&str>, message: &str) -> u32 { + return match level { + LoggingTestLevel::Trace => { + emit_one(LoggingTestLevel::Trace, target, domain, message); + 1 + }, + LoggingTestLevel::Debug => { + emit_one(LoggingTestLevel::Debug, target, domain, message); + 1 + }, + LoggingTestLevel::Info => { + emit_one(LoggingTestLevel::Info, target, domain, message); + 1 + }, + LoggingTestLevel::Warn => { + emit_one(LoggingTestLevel::Warn, target, domain, message); + 1 + }, + LoggingTestLevel::Error => { + emit_one(LoggingTestLevel::Error, target, domain, message); + 1 + }, + LoggingTestLevel::All => { + emit_one(LoggingTestLevel::Trace, target, domain, message); + emit_one(LoggingTestLevel::Debug, target, domain, message); + emit_one(LoggingTestLevel::Info, target, domain, message); + emit_one(LoggingTestLevel::Warn, target, domain, message); + emit_one(LoggingTestLevel::Error, target, domain, message); + 5 + }, + }; +} + +fn emit_one(level: LoggingTestLevel, target: LoggingTestTarget, domain: std::option::Option<&str>, message: &str) { + return match target { + LoggingTestTarget::App => emit_app_target(level, domain, message), + LoggingTestTarget::Dedicated => emit_dedicated_target(level, domain, message), + }; +} + +fn emit_app_target(level: LoggingTestLevel, domain: std::option::Option<&str>, message: &str) { + return match level { + LoggingTestLevel::Trace => emit_app_trace(domain, message), + LoggingTestLevel::Debug => emit_app_debug(domain, message), + LoggingTestLevel::Info => emit_app_info(domain, message), + LoggingTestLevel::Warn => emit_app_warn(domain, message), + LoggingTestLevel::Error => emit_app_error(domain, message), + LoggingTestLevel::All => (), + }; +} + +fn emit_dedicated_target(level: LoggingTestLevel, domain: std::option::Option<&str>, message: &str) { + return match level { + LoggingTestLevel::Trace => emit_dedicated_trace(domain, message), + LoggingTestLevel::Debug => emit_dedicated_debug(domain, message), + LoggingTestLevel::Info => emit_dedicated_info(domain, message), + LoggingTestLevel::Warn => emit_dedicated_warn(domain, message), + LoggingTestLevel::Error => emit_dedicated_error(domain, message), + LoggingTestLevel::All => (), + }; +} + +fn emit_app_trace(domain: std::option::Option<&str>, message: &str) { + return match domain { + std::option::Option::Some(domain) => ksp_logging_lib::trace!( + target: crate::TRACING_TARGET, + domain = domain, + action = "logging_test", + test_source = "backend", + test_level = "trace", + "{message}" + ), + std::option::Option::None => ksp_logging_lib::trace!( + target: crate::TRACING_TARGET, + action = "logging_test", + test_source = "backend", + test_level = "trace", + "{message}" + ), + }; +} + +fn emit_app_debug(domain: std::option::Option<&str>, message: &str) { + return match domain { + std::option::Option::Some(domain) => ksp_logging_lib::debug!( + target: crate::TRACING_TARGET, + domain = domain, + action = "logging_test", + test_source = "backend", + test_level = "debug", + "{message}" + ), + std::option::Option::None => ksp_logging_lib::debug!( + target: crate::TRACING_TARGET, + action = "logging_test", + test_source = "backend", + test_level = "debug", + "{message}" + ), + }; +} + +fn emit_app_info(domain: std::option::Option<&str>, message: &str) { + return match domain { + std::option::Option::Some(domain) => ksp_logging_lib::info!( + target: crate::TRACING_TARGET, + domain = domain, + action = "logging_test", + test_source = "backend", + test_level = "info", + "{message}" + ), + std::option::Option::None => ksp_logging_lib::info!( + target: crate::TRACING_TARGET, + action = "logging_test", + test_source = "backend", + test_level = "info", + "{message}" + ), + }; +} + +fn emit_app_warn(domain: std::option::Option<&str>, message: &str) { + return match domain { + std::option::Option::Some(domain) => ksp_logging_lib::warn!( + target: crate::TRACING_TARGET, + domain = domain, + action = "logging_test", + test_source = "backend", + test_level = "warn", + "{message}" + ), + std::option::Option::None => ksp_logging_lib::warn!( + target: crate::TRACING_TARGET, + action = "logging_test", + test_source = "backend", + test_level = "warn", + "{message}" + ), + }; +} + +fn emit_app_error(domain: std::option::Option<&str>, message: &str) { + return match domain { + std::option::Option::Some(domain) => ksp_logging_lib::error!( + target: crate::TRACING_TARGET, + domain = domain, + action = "logging_test", + test_source = "backend", + test_level = "error", + "{message}" + ), + std::option::Option::None => ksp_logging_lib::error!( + target: crate::TRACING_TARGET, + action = "logging_test", + test_source = "backend", + test_level = "error", + "{message}" + ), + }; +} + +fn emit_dedicated_trace(domain: std::option::Option<&str>, message: &str) { + return match domain { + std::option::Option::Some(domain) => ksp_logging_lib::trace!( + target: crate::TRACING_TARGET_LOGGING_TEST, + domain = domain, + action = "logging_test", + test_source = "backend", + test_level = "trace", + "{message}" + ), + std::option::Option::None => ksp_logging_lib::trace!( + target: crate::TRACING_TARGET_LOGGING_TEST, + action = "logging_test", + test_source = "backend", + test_level = "trace", + "{message}" + ), + }; +} + +fn emit_dedicated_debug(domain: std::option::Option<&str>, message: &str) { + return match domain { + std::option::Option::Some(domain) => ksp_logging_lib::debug!( + target: crate::TRACING_TARGET_LOGGING_TEST, + domain = domain, + action = "logging_test", + test_source = "backend", + test_level = "debug", + "{message}" + ), + std::option::Option::None => ksp_logging_lib::debug!( + target: crate::TRACING_TARGET_LOGGING_TEST, + action = "logging_test", + test_source = "backend", + test_level = "debug", + "{message}" + ), + }; +} + +fn emit_dedicated_info(domain: std::option::Option<&str>, message: &str) { + return match domain { + std::option::Option::Some(domain) => ksp_logging_lib::info!( + target: crate::TRACING_TARGET_LOGGING_TEST, + domain = domain, + action = "logging_test", + test_source = "backend", + test_level = "info", + "{message}" + ), + std::option::Option::None => ksp_logging_lib::info!( + target: crate::TRACING_TARGET_LOGGING_TEST, + action = "logging_test", + test_source = "backend", + test_level = "info", + "{message}" + ), + }; +} + +fn emit_dedicated_warn(domain: std::option::Option<&str>, message: &str) { + return match domain { + std::option::Option::Some(domain) => ksp_logging_lib::warn!( + target: crate::TRACING_TARGET_LOGGING_TEST, + domain = domain, + action = "logging_test", + test_source = "backend", + test_level = "warn", + "{message}" + ), + std::option::Option::None => ksp_logging_lib::warn!( + target: crate::TRACING_TARGET_LOGGING_TEST, + action = "logging_test", + test_source = "backend", + test_level = "warn", + "{message}" + ), + }; +} + +fn emit_dedicated_error(domain: std::option::Option<&str>, message: &str) { + return match domain { + std::option::Option::Some(domain) => ksp_logging_lib::error!( + target: crate::TRACING_TARGET_LOGGING_TEST, + domain = domain, + action = "logging_test", + test_source = "backend", + test_level = "error", + "{message}" + ), + std::option::Option::None => ksp_logging_lib::error!( + target: crate::TRACING_TARGET_LOGGING_TEST, + action = "logging_test", + test_source = "backend", + test_level = "error", + "{message}" + ), + }; +} + +#[cfg(test)] +#[path = "../unit_tests/logging_test.rs"] +mod tests; diff --git a/crates/ksp-app-config-desk/src/tauri.rs b/crates/ksp-app-config-desk/src/tauri.rs index 35fef0e..ff99d5e 100644 --- a/crates/ksp-app-config-desk/src/tauri.rs +++ b/crates/ksp-app-config-desk/src/tauri.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-config-desk/src/tauri.rs -// version: 14 +// version: 15 //! Tauri runtime assembly for the KSP configuration desktop application. @@ -52,6 +52,7 @@ fn configure_commands(builder: tauri::Builder) -> tauri::Builder, +) -> std::result::Result { + let result = crate::logging_test::emit(&state, request); + return match result { + std::result::Result::Ok(value) => std::result::Result::Ok(value), + 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); diff --git a/crates/ksp-app-config-desk/unit_tests/logging_test.rs b/crates/ksp-app-config-desk/unit_tests/logging_test.rs new file mode 100644 index 0000000..2647a1a --- /dev/null +++ b/crates/ksp-app-config-desk/unit_tests/logging_test.rs @@ -0,0 +1,51 @@ +// file: crates/ksp-app-config-desk/unit_tests/logging_test.rs +// version: 1 + +use super::{LoggingTestLevel, LoggingTestTarget}; + +#[test] +fn controlled_logging_test_levels_include_all_five_levels_and_batch_mode() { + assert!(matches!(super::parse_level("trace"), std::result::Result::Ok(LoggingTestLevel::Trace))); + assert!(matches!(super::parse_level("debug"), std::result::Result::Ok(LoggingTestLevel::Debug))); + assert!(matches!(super::parse_level("info"), std::result::Result::Ok(LoggingTestLevel::Info))); + assert!(matches!(super::parse_level("warn"), std::result::Result::Ok(LoggingTestLevel::Warn))); + assert!(matches!(super::parse_level("error"), std::result::Result::Ok(LoggingTestLevel::Error))); + assert!(matches!(super::parse_level("all"), std::result::Result::Ok(LoggingTestLevel::All))); + assert!(super::parse_level("fatal").is_err()); +} + +#[test] +fn controlled_logging_test_targets_are_static_ksp_targets() { + assert!(matches!(super::parse_target("app"), std::result::Result::Ok(LoggingTestTarget::App))); + assert!(matches!(super::parse_target("logging_test"), std::result::Result::Ok(LoggingTestTarget::Dedicated))); + assert_eq!(super::target_value(LoggingTestTarget::App), crate::TRACING_TARGET); + assert_eq!(super::target_value(LoggingTestTarget::Dedicated), crate::TRACING_TARGET_LOGGING_TEST); + assert!(super::parse_target("external").is_err()); +} + +#[test] +fn logging_test_domain_modes_cover_absent_known_and_bounded_custom_values() { + let absent = super::resolve_domain("absent", "ignored"); + assert!(matches!(absent, std::result::Result::Ok(std::option::Option::None))); + let known = super::resolve_domain("known", crate::TRACING_DOMAIN_LOGGING_TEST); + assert!(matches!( + known.as_ref(), + std::result::Result::Ok(std::option::Option::Some(value)) if value == crate::TRACING_DOMAIN_LOGGING_TEST + )); + let custom = super::resolve_domain("custom", "custom.route_1-test"); + assert!(matches!( + custom.as_ref(), + std::result::Result::Ok(std::option::Option::Some(value)) if value == "custom.route_1-test" + )); + assert!(super::resolve_domain("custom", "custom route").is_err()); + assert!(super::resolve_domain("custom", "").is_err()); + assert!(super::resolve_domain("known", "unknown.domain").is_err()); +} + +#[test] +fn logging_test_message_is_required_and_bounded() { + let message = super::validate_message(" test "); + assert!(matches!(message, std::result::Result::Ok("test"))); + assert!(super::validate_message(" ").is_err()); + assert!(super::validate_message("x".repeat(501).as_str()).is_err()); +} diff --git a/deltas/0.1.4/pre.017.md b/deltas/0.1.4/pre.017.md new file mode 100644 index 0000000..2939135 --- /dev/null +++ b/deltas/0.1.4/pre.017.md @@ -0,0 +1,91 @@ + + + +# Delta `0.1.4-pre.017` — panneau Test Logging + +## Base + +`0.1.4-pre.016-fix.002` est validée : `cargo fmt --all`, `cargo check --workspace` et `cargo clippy --workspace --all-targets` sont propres ; 56 tests `ksp-app-config-desk`, 88 tests `ksp-config-lib`, 37 tests unitaires `ksp-logging-lib`, les audits ownership, APIs publiques et tests runtime/intégration passent. + +## Objectif + +Ajouter une surface manuelle et contrôlée pour démontrer le routing réel du runtime Logging avant/après hot reload, sans contourner `ksp-logging-lib` et sans introduire de target dynamique arbitraire. + +## Changements + +### Backend contrôlé + +Config Desk ajoute `emit_logging_test` et deux DTO TS-RS : + +- `LoggingTestRequestDto` ; +- `LoggingTestResultDto`. + +Le service accepte : + +- niveau `trace`, `debug`, `info`, `warn`, `error` ou `all` ; +- target logique `app` -> `ksp-app-config-desk` ; +- target logique `logging_test` -> `ksp-app-config-desk.logging-test` ; +- domain `absent`, `known` ou `custom`. + +Les domains connus sont limités à `config.logging_test`, `config.logging_runtime` et `frontend`. Un domain personnalisé est borné à 80 caractères ASCII alphanumériques plus `.`, `_` et `-`. Le message doit contenir de 1 à 500 octets. + +Les targets restent des callsites statiques. L'application n'importe pas `tracing` et émet exclusivement via les macros `ksp-logging-lib`. + +Le mode `all` émet exactement cinq événements, un par niveau. Le résultat ne réexpose pas le message ; il contient uniquement le nombre d'événements, le niveau demandé, target/domain effectifs et la génération runtime active. + +### Frontend + +La vue Logging ajoute **Test Logging — routing contrôlé** avec : + +- message ; +- niveau ; +- target backend contrôlé ; +- domain absent/connu/personnalisé ; +- **Log backend** ; +- **Log via bridge frontend**. + +La voie frontend réutilise le bridge existant et conserve son contrat fixe `target=ksp-app-config-desk.frontend.main`, `domain=frontend`. Elle permet de comparer le routing d'un événement backend et celui d'un événement issu de la WebView dans les mêmes sinks. + +Le message du panneau est volontairement journalisé : l'UI rappelle explicitement de ne jamais y placer de Secret. + +### Tests + +Les tests ajoutés couvrent : + +- les cinq niveaux et le mode batch `all` ; +- les deux targets statiques ; +- les trois modes de domain ; +- le rejet des domains personnalisés non sûrs ; +- la borne du message ; +- les exports TS-RS des DTO. + +## Version technique + +```text +workspace.package.version = 0.1.4-pre.17 +``` + +## Validation attendue + +```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 test -p ksp-logging-lib +cargo tauri dev -c crates/ksp-app-config-desk/tauri.conf.json +``` + +Scénario fonctionnel : + +1. avec le profil actif actuel, lancer `tous` sur `ksp-app-config-desk.logging-test` + `config.logging_test` ; +2. noter quels niveaux apparaissent en console et dans les file sinks ; +3. modifier niveaux/targets/domains, **Sauvegarder et appliquer** ; +4. relancer exactement le même test sans redémarrage ; +5. vérifier que le routing suit immédiatement le nouveau runtime ; +6. répéter avec **Log via bridge frontend** et vérifier le target `ksp-app-config-desk.frontend.main` / domain `frontend`. + +## Suite + +`0.1.4-pre.018` couvrira robustesse/extensibilité/tests desktop : source invalide au démarrage, audits, tests frontend/Tauri/build et ajout futur d'un `file_id`/editor sans refonte du shell. diff --git a/docs/plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md b/docs/plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md index 0ecb4bd..1b83d0e 100644 --- a/docs/plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md +++ b/docs/plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md @@ -1,5 +1,5 @@ - + # Plan `0.1.4` — `ksp-app-config-desk` @@ -1247,13 +1247,13 @@ pre.015 Logging editor — mutations/persistence [réalisé] - Recharger le document = resynchronisation du brouillon uniquement - rollback source + ancien runtime conservé si l’application échoue -pre.016 Logging runtime — compléments [en cours] +pre.016 Logging runtime — compléments [réalisé] - fichiers applicatifs uniques par lancement avec timestamp de démarrage - sélection explicite du profil à appliquer indépendamment du default_profile - observabilité metadata/génération du runtime - consolidation des scénarios de rollback/runtime -pre.017 panneau Test Logging +pre.017 panneau Test Logging [en cours] - champ message - niveau trace/debug/info/warn/error/tous - select target KSP contrôlé diff --git a/logs_v0.1.4-pre.017.zip b/logs_v0.1.4-pre.017.zip new file mode 100644 index 0000000..d520acc Binary files /dev/null and b/logs_v0.1.4-pre.017.zip differ