v0.1.4-pre.017

This commit is contained in:
2026-08-16 19:49:21 +02:00
parent fd200f5335
commit 4216b6b783
18 changed files with 858 additions and 32 deletions

View File

@@ -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"

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-app-config-desk/README.md -->
<!-- version: 19 -->
<!-- version: 20 -->
# `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

View File

@@ -1,12 +1,11 @@
<!-- file: crates/ksp-app-config-desk/TODO.md -->
<!-- version: 16 -->
<!-- version: 17 -->
# 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.

View File

@@ -1,11 +1,11 @@
<!-- file: crates/ksp-app-config-desk/USAGE.md -->
<!-- version: 19 -->
<!-- version: 20 -->
# 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`.

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-app-config-desk/frontend/main.html -->
<!-- version: 14 -->
<!-- version: 15 -->
<!DOCTYPE html>
<html lang="fr">
@@ -360,6 +360,66 @@
</div>
</section>
<section class="border rounded p-3 mb-4" aria-label="Test Logging">
<div class="d-flex flex-wrap gap-2 align-items-start justify-content-between mb-3">
<div>
<h3 class="h6 mb-1">Test Logging — routing contrôlé</h3>
<p class="text-body-secondary small mb-0">É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.</p>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-xl-6">
<label class="form-label" for="loggingTestMessage">Message</label>
<input id="loggingTestMessage" class="form-control" type="text" maxlength="500" autocomplete="off" value="Config Desk Logging routing test">
</div>
<div class="col-md-3 col-xl-2">
<label class="form-label" for="loggingTestLevel">Niveau</label>
<select id="loggingTestLevel" class="form-select">
<option value="trace">trace</option><option value="debug">debug</option><option value="info" selected>info</option><option value="warn">warn</option><option value="error">error</option><option value="all">tous</option>
</select>
</div>
<div class="col-md-5 col-xl-4">
<label class="form-label" for="loggingTestTarget">Target backend contrôlé</label>
<select id="loggingTestTarget" class="form-select font-monospace">
<option value="app">ksp-app-config-desk</option>
<option value="logging_test" selected>ksp-app-config-desk.logging-test</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label" for="loggingTestDomainMode">Domain</label>
<select id="loggingTestDomainMode" class="form-select">
<option value="absent">absent</option>
<option value="known" selected>connu</option>
<option value="custom">personnalisé</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label" for="loggingTestKnownDomain">Domain connu</label>
<select id="loggingTestKnownDomain" class="form-select font-monospace">
<option value="config.logging_test">config.logging_test</option>
<option value="config.logging_runtime">config.logging_runtime</option>
<option value="frontend">frontend</option>
</select>
</div>
<div class="col-md-5">
<label class="form-label" for="loggingTestCustomDomain">Domain personnalisé</label>
<input id="loggingTestCustomDomain" class="form-control font-monospace" type="text" maxlength="80" autocomplete="off" placeholder="custom.routing" disabled>
</div>
</div>
<div class="d-flex flex-wrap gap-2 mb-3">
<button id="emitLoggingTestBackend" class="btn btn-outline-primary" type="button">
<i class="fa-solid fa-terminal me-1" aria-hidden="true"></i>
Log backend
</button>
<button id="emitLoggingTestFrontend" class="btn btn-outline-secondary" type="button">
<i class="fa-solid fa-code me-1" aria-hidden="true"></i>
Log via bridge frontend
</button>
</div>
<div class="form-text mb-3">Le test backend respecte le target/domain choisis. Le bridge frontend conserve volontairement son contrat fixe <code>target=ksp-app-config-desk.frontend.main</code>, <code>domain=frontend</code> et réutilise uniquement le message/niveau.</div>
<div id="loggingTestStatus" class="alert alert-secondary mb-0" role="status" aria-live="polite">Aucun événement de test émis pendant ce lancement.</div>
</section>
<section class="border rounded p-3 mb-4 bg-body-tertiary" aria-label="Globals Logging">
<h3 class="h6 mb-3">Globals</h3>
<div class="row g-3">

View File

@@ -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<void> {
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),
);
};
}

View File

@@ -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<HTMLButtonElement>("#loggingDiscardDraftConfirm")?.addEventListener("click", confirmReload);
document.querySelector<HTMLButtonElement>("#addLoggingFile")?.addEventListener("click", addFile);
document.querySelector<HTMLButtonElement>("#addLoggingTargetFilter")?.addEventListener("click", addTargetFilter);
initializeLoggingTestPanel();
frontendTrace("main", "Logging editor mutation handlers installed");
void refreshLoggingDocument("startup");
}

View File

@@ -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<HTMLInputElement>(selector)?.value ?? "";
}
function selectedValue(selector: string): string {
return document.querySelector<HTMLSelectElement>(selector)?.value ?? "";
}
function setStatus(message: string, tone: "secondary" | "success" | "warning" | "danger" = "secondary"): void {
const status = document.querySelector<HTMLElement>("#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<HTMLSelectElement>("#loggingTestKnownDomain");
const custom = document.querySelector<HTMLInputElement>("#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<void> {
const request = buildRequest();
setStatus("Émission backend en cours...");
try {
const result = await invokeKsp<LoggingTestResultDto>("main", "emit_logging_test", { request });
const domain = result.domain ?? "<absent>";
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<void> {
await emitFrontendLog(level, "main", message);
}
async function emitFrontendBridgeTest(): Promise<void> {
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<HTMLSelectElement>("#loggingTestDomainMode")?.addEventListener("change", updateDomainControls);
document.querySelector<HTMLButtonElement>("#emitLoggingTestBackend")?.addEventListener("click", () => void emitBackendTest());
document.querySelector<HTMLButtonElement>("#emitLoggingTestFrontend")?.addEventListener("click", () => void emitFrontendBridgeTest());
updateDomainControls();
frontendTrace("main", "Logging test panel handlers installed");
}

View File

@@ -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<ViewId, { title: string; description: string }> = {
},
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.",
},
};

View File

@@ -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";

View File

@@ -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");

View File

@@ -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;

View File

@@ -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<String>,
/// 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<LoggingTestResultDto> {
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<LoggingTestLevel> {
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<LoggingTestTarget> {
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<std::option::Option<String>> {
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<std::option::Option<String>> {
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<std::option::Option<String>> {
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<T>(message: &str) -> ksp_core_lib::Result<T> {
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;

View File

@@ -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::Wry>) -> tauri::Builder<tau
get_logging_runtime_status,
apply_logging_profile,
save_logging_document,
emit_logging_test,
emit_frontend_log,
splash_frontend_ready
]);
@@ -216,6 +217,18 @@ fn save_logging_document(
};
}
#[tauri::command]
fn emit_logging_test(
request: crate::LoggingTestRequestDto,
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::LoggingTestResultDto, crate::CommandErrorDto> {
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);

View File

@@ -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());
}

91
deltas/0.1.4/pre.017.md Normal file
View File

@@ -0,0 +1,91 @@
<!-- file: deltas/0.1.4/pre.017.md -->
<!-- version: 1 -->
# 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.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md -->
<!-- version: 20 -->
<!-- version: 21 -->
# 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 lapplication é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é

BIN
logs_v0.1.4-pre.017.zip Normal file

Binary file not shown.