v0.1.4-pre.008
This commit is contained in:
@@ -1,6 +1,12 @@
|
|||||||
# file: .env.example
|
# file: .env.example
|
||||||
# version: 1
|
# version: 2
|
||||||
|
|
||||||
# KSP Logging root directory. Used by config/std.logging.json for relative log output paths.
|
# KSP Logging root directory. Used by config/std.logging.json for relative log output paths.
|
||||||
# The current Config document fallback is "logs" when neither the process environment nor .env defines this variable.
|
# The current Config document fallback is "logs" when neither the process environment nor .env defines this variable.
|
||||||
KSP_LOGS_DIRECTORY=logs
|
KSP_LOGS_DIRECTORY=logs
|
||||||
|
|
||||||
|
# Minimum time in milliseconds that a KSP desk splash remains visible after its frontend is ready.
|
||||||
|
KSP_DESK_SPLASH_MINIMUM_MS=1200
|
||||||
|
|
||||||
|
# Fade-in/fade-out duration in milliseconds used by the common KSP desk splash lifecycle.
|
||||||
|
KSP_DESK_SPLASH_FADE_MS=300
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 71
|
# version: 72
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = ["crates/ksp-app-config-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib"]
|
members = ["crates/ksp-app-config-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.1.4-pre.7"
|
version = "0.1.4-pre.8"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||||
@@ -22,7 +22,7 @@ solana-pubkey = { version = "^4.3", default-features = false }
|
|||||||
tracing = { version = "^0.1", default-features = false, features = ["std"] }
|
tracing = { version = "^0.1", default-features = false, features = ["std"] }
|
||||||
tracing-subscriber = { version = "^0.3", default-features = false, features = ["fmt", "json", "ansi"] }
|
tracing-subscriber = { version = "^0.3", default-features = false, features = ["fmt", "json", "ansi"] }
|
||||||
tracing-appender = { version = "^0.2", default-features = false }
|
tracing-appender = { version = "^0.2", default-features = false }
|
||||||
tokio = { version = "^1.53", default-features = false, features = ["rt", "rt-multi-thread", "macros"] }
|
tokio = { version = "^1.53", default-features = false, features = ["rt", "rt-multi-thread", "macros", "time"] }
|
||||||
tauri = { version = "^2.11" }
|
tauri = { version = "^2.11" }
|
||||||
tauri-build = { version = "^2.6" }
|
tauri-build = { version = "^2.6" }
|
||||||
tauri-plugin-tracing = { version = "^0.3" }
|
tauri-plugin-tracing = { version = "^0.3" }
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# file: crates/ksp-app-config-desk/Cargo.toml
|
# file: crates/ksp-app-config-desk/Cargo.toml
|
||||||
# version: 3
|
# version: 4
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "ksp-app-config-desk"
|
name = "ksp-app-config-desk"
|
||||||
@@ -29,6 +29,7 @@ ksp-logging-lib = { path = "../ksp-logging-lib" }
|
|||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
tauri.workspace = true
|
tauri.workspace = true
|
||||||
tauri-plugin-tracing.workspace = true
|
tauri-plugin-tracing.workspace = true
|
||||||
|
tokio.workspace = true
|
||||||
ts-rs.workspace = true
|
ts-rs.workspace = true
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: crates/ksp-app-config-desk/README.md -->
|
<!-- file: crates/ksp-app-config-desk/README.md -->
|
||||||
<!-- version: 6 -->
|
<!-- version: 7 -->
|
||||||
|
|
||||||
# `ksp-app-config-desk`
|
# `ksp-app-config-desk`
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ Le gabarit desktop actif fournit maintenant :
|
|||||||
|
|
||||||
- le verrou single-instance ;
|
- le verrou single-instance ;
|
||||||
- l'assemblage Tauri centralisé dans `src/tauri.rs` ;
|
- l'assemblage Tauri centralisé dans `src/tauri.rs` ;
|
||||||
- les fenêtres `splash` et `main` ;
|
- les fenêtres `splash` et `main`, orchestrées par `tw_splash.rs` et `tw_main.rs` ;
|
||||||
- le frontend Vanilla TypeScript + Vite sous `frontend/` ;
|
- le frontend Vanilla TypeScript + Vite sous `frontend/` ;
|
||||||
- SCSS, Bootstrap et Font Awesome ;
|
- SCSS, Bootstrap et Font Awesome ;
|
||||||
- SimpleBar + `resize-observer-polyfill` pour le scrolling du shell ;
|
- SimpleBar + `resize-observer-polyfill` pour le scrolling du shell ;
|
||||||
@@ -29,7 +29,8 @@ Le gabarit desktop actif fournit maintenant :
|
|||||||
- les ports dédiés `1430` pour Vite HTTP et `1431` pour le WebSocket de développement ;
|
- les ports dédiés `1430` pour Vite HTTP et `1431` pour le WebSocket de développement ;
|
||||||
- la destination frontend externe commune à Tauri et Vite;
|
- la destination frontend externe commune à Tauri et Vite;
|
||||||
- le bootstrap Config/Logging réel, un `AppState` durable et le premier contrat Tauri/TS-RS `get_app_snapshot`;
|
- le bootstrap Config/Logging réel, un `AppState` durable et le premier contrat Tauri/TS-RS `get_app_snapshot`;
|
||||||
- le bridge de logging frontend KSP `frontend_log.ts`, routé par `emit_frontend_log` vers des targets statiques whitelistés de `ksp-logging-lib`.
|
- le bridge de logging frontend KSP `frontend_log.ts`, routé par `emit_frontend_log` vers des targets statiques whitelistés de `ksp-logging-lib` ;
|
||||||
|
- le lifecycle configurable `splash -> main` avec navigation monofenêtre et instrumentation `debug`/`trace` des interactions frontend.
|
||||||
|
|
||||||
## Layout frontend
|
## Layout frontend
|
||||||
|
|
||||||
@@ -46,6 +47,7 @@ frontend/
|
|||||||
│ └── splash.scss
|
│ └── splash.scss
|
||||||
├── ts/
|
├── ts/
|
||||||
│ ├── frontend_log.ts
|
│ ├── frontend_log.ts
|
||||||
|
│ ├── invoke.ts
|
||||||
│ ├── main.ts
|
│ ├── main.ts
|
||||||
│ └── splash.ts
|
│ └── splash.ts
|
||||||
├── main.html
|
├── main.html
|
||||||
@@ -110,6 +112,23 @@ Si le document Logging, sa résolution environnementale ou l'initialisation du r
|
|||||||
|
|
||||||
Le `LoggingGuard` est conservé dans `AppState` pour préparer le hot reload ultérieur.
|
Le `LoggingGuard` est conservé dans `AppState` pour préparer le hot reload ultérieur.
|
||||||
|
|
||||||
|
## Lifecycle des fenêtres
|
||||||
|
|
||||||
|
`tw_splash.rs` valide l'origine de `splash_frontend_ready`, ignore les readiness dupliquées et orchestre le passage à `main`. Les timings sont résolus par `ksp-config-lib` depuis :
|
||||||
|
|
||||||
|
```text
|
||||||
|
KSP_DESK_SPLASH_MINIMUM_MS=1200
|
||||||
|
KSP_DESK_SPLASH_FADE_MS=300
|
||||||
|
```
|
||||||
|
|
||||||
|
Après la durée minimale, le splash reçoit son fade-out, `tw_main.rs` affiche/focalise `main`, puis le splash est détruit. Une valeur splash invalide ne bloque pas le manager : des timings de fallback sûrs restent en mémoire afin que la future surface `.env` puisse permettre la réparation.
|
||||||
|
|
||||||
|
Le shell principal expose les cinq routes de référence `Vue d'ensemble`, `Documents`, `Profils`, `Environnement / .env` et `Logging`. Les routes non encore fonctionnelles affichent un placeholder sans réimplémenter les services prévus par les prereleases suivantes.
|
||||||
|
|
||||||
|
## Traçabilité frontend
|
||||||
|
|
||||||
|
Les actions utilisateur significatives et changements d'état sont journalisés en `debug`; les opérations plus fines (rendu/remplacement DOM, étapes IPC, animations et événements fréquents) en `trace`. Le wrapper `invoke.ts` trace début/fin des commandes sans journaliser leurs arguments, afin de ne pas créer ultérieurement de fuite de valeurs sensibles.
|
||||||
|
|
||||||
## Police du splash
|
## Police du splash
|
||||||
|
|
||||||
Le splash utilise `DOS_Amazigh.ttf` (`font-family: Dos Amazigh`) pour son titre, via le `@font-face` déclaré dans `frontend/sass/splash.scss`. Le répertoire `frontend/fonts/` documente la provenance et l'empreinte de l'asset de référence. Le lifecycle final du splash reste traité dans la tranche dédiée `pre.008`.
|
Le splash utilise `DOS_Amazigh.ttf` (`font-family: Dos Amazigh`) pour son titre, via le `@font-face` déclaré dans `frontend/sass/splash.scss`. Le répertoire `frontend/fonts/` documente la provenance et l'empreinte de l'asset de référence. Le lifecycle du splash est désormais actif et utilise cette police pendant la transition vers la fenêtre principale.
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
<!-- file: crates/ksp-app-config-desk/TODO.md -->
|
<!-- file: crates/ksp-app-config-desk/TODO.md -->
|
||||||
<!-- version: 5 -->
|
<!-- version: 6 -->
|
||||||
|
|
||||||
# TODO `ksp-app-config-desk`
|
# TODO `ksp-app-config-desk`
|
||||||
|
|
||||||
Le gabarit frontend, le bootstrap Config/Logging, `AppState`, le `LoggingGuard` durable, les premiers DTO TS-RS et le bridge frontend logging KSP sont maintenant présents.
|
Le gabarit frontend, le bootstrap Config/Logging, `AppState`, le `LoggingGuard` durable, les premiers DTO TS-RS, le bridge frontend logging KSP et le lifecycle `splash -> main` sont maintenant présents.
|
||||||
|
|
||||||
Les prochaines tranches doivent encore :
|
Les prochaines tranches doivent encore :
|
||||||
|
|
||||||
- finaliser le lifecycle splash configurable sans temporisation environnementale codée en dur ;
|
|
||||||
- développer les panneaux Documents, Profils, Environnement/`.env`, Secrets et Logging ;
|
- développer les panneaux Documents, Profils, Environnement/`.env`, Secrets et Logging ;
|
||||||
- introduire DataTables au premier tableau qui exige tri, filtrage, sélection ou checkbox ;
|
- introduire DataTables au premier tableau qui exige tri, filtrage, sélection ou checkbox ;
|
||||||
- démontrer le hot reload Logging et le panneau de test de routage.
|
- démontrer le hot reload Logging et le panneau de test de routage.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: crates/ksp-app-config-desk/USAGE.md -->
|
<!-- file: crates/ksp-app-config-desk/USAGE.md -->
|
||||||
<!-- version: 6 -->
|
<!-- version: 7 -->
|
||||||
|
|
||||||
# Utilisation de `ksp-app-config-desk`
|
# Utilisation de `ksp-app-config-desk`
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
Le gabarit Rust/Tauri et le frontend Vite/TypeScript/SCSS sont présents. Le backend initialise désormais Config, le runtime Logging et `AppState`; les panneaux métier Config restent volontairement absents à ce stade.
|
Le gabarit Rust/Tauri et le frontend Vite/TypeScript/SCSS sont présents. Le backend initialise désormais Config, le runtime Logging et `AppState`; les panneaux métier Config restent volontairement absents à ce stade.
|
||||||
|
|
||||||
La fenêtre `splash` est la fenêtre visible au démarrage. La fenêtre `main` est déjà construite et bundlée, mais son lifecycle d'affichage est finalisé dans la tranche splash/shell dédiée afin de ne pas introduire une temporisation codée en dur.
|
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.
|
||||||
|
|
||||||
## Première installation des dépendances frontend
|
## Première installation des dépendances frontend
|
||||||
|
|
||||||
@@ -106,6 +106,33 @@ Si cette configuration ne peut pas être utilisée, l'application doit rester d
|
|||||||
|
|
||||||
La commande Tauri `get_app_snapshot` expose un état sûr du bootstrap. Ses types TypeScript sont générés par `cargo test -p ksp-app-config-desk` sous `frontend/ts/bindings/`.
|
La commande Tauri `get_app_snapshot` expose un état sûr du bootstrap. Ses types TypeScript sont générés par `cargo test -p ksp-app-config-desk` sous `frontend/ts/bindings/`.
|
||||||
|
|
||||||
|
## Lifecycle et timings du splash
|
||||||
|
|
||||||
|
Les variables communes sont :
|
||||||
|
|
||||||
|
```text
|
||||||
|
KSP_DESK_SPLASH_MINIMUM_MS=1200
|
||||||
|
KSP_DESK_SPLASH_FADE_MS=300
|
||||||
|
```
|
||||||
|
|
||||||
|
La priorité est celle de Config : process > `.env` > fallback. Les valeurs sont lues uniquement via `ksp-config-lib`. Une valeur non numérique ou hors borne déclenche un fallback runtime en mémoire plutôt qu'un refus de démarrer Config Desk.
|
||||||
|
|
||||||
|
Le frontend `splash.ts` installe son listener puis appelle `splash_frontend_ready`. Le backend vérifie que l'appel provient réellement de la WebView `splash`; une readiness dupliquée (par exemple après reload Vite) est ignorée.
|
||||||
|
|
||||||
|
## Navigation principale
|
||||||
|
|
||||||
|
La barre principale contient :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Vue d'ensemble
|
||||||
|
Documents
|
||||||
|
Profils
|
||||||
|
Environnement / .env
|
||||||
|
Logging
|
||||||
|
```
|
||||||
|
|
||||||
|
Seule la vue d'ensemble consomme déjà `get_app_snapshot`; les autres routes restent des placeholders jusqu'à leurs tranches fonctionnelles. Chaque clic de navigation est tracé en `debug` et chaque remplacement/rendu de section en `trace`. Les appels Tauri partagés utilisent `frontend/ts/invoke.ts`, qui journalise le début et la fin d'une commande sans journaliser ses arguments.
|
||||||
|
|
||||||
## Asset font du splash
|
## Asset font du splash
|
||||||
|
|
||||||
La police bot3 de référence est `DOS_Amazigh.ttf`. Vérifier l'asset local contre l'empreinte documentée dans `frontend/fonts/README.md` ; `frontend/sass/splash.scss` l'applique déjà au titre du splash. La tranche `pre.008` reste consacrée au lifecycle et à la finition visuelle.
|
La police bot3 de référence est `DOS_Amazigh.ttf`. Vérifier l'asset local contre l'empreinte documentée dans `frontend/fonts/README.md` ; `frontend/sass/splash.scss` l'applique au titre du splash.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: crates/ksp-app-config-desk/frontend/main.html -->
|
<!-- file: crates/ksp-app-config-desk/frontend/main.html -->
|
||||||
<!-- version: 1 -->
|
<!-- version: 2 -->
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
|
|
||||||
@@ -13,12 +13,19 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
<header class="app-header">
|
<header class="app-header">
|
||||||
<nav class="navbar navbar-expand-lg h-100 py-0 bg-light text-dark">
|
<nav class="navbar h-100 py-0 bg-light text-dark">
|
||||||
<div class="container-fluid px-4">
|
<div class="container-fluid px-4 flex-nowrap">
|
||||||
<div class="navbar-brand d-flex align-items-center">
|
<div class="navbar-brand d-flex align-items-center me-4">
|
||||||
<img alt="KSP" src="imgs/logo.png" class="app-logo">
|
<img alt="KSP" src="imgs/logo.png" class="app-logo">
|
||||||
<span class="ps-2 fs-4 fw-bold text-primary">KSP — <span class="badge text-bg-primary">Config Desk</span></span>
|
<span class="ps-2 fs-4 fw-bold text-primary">KSP — <span class="badge text-bg-primary">Config Desk</span></span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="app-nav nav nav-pills flex-nowrap gap-1" aria-label="Navigation Config Desk">
|
||||||
|
<button class="nav-link app-nav-button active" type="button" data-view="overview">Vue d'ensemble</button>
|
||||||
|
<button class="nav-link app-nav-button" type="button" data-view="documents">Documents</button>
|
||||||
|
<button class="nav-link app-nav-button" type="button" data-view="profiles">Profils</button>
|
||||||
|
<button class="nav-link app-nav-button" type="button" data-view="environment">Environnement / .env</button>
|
||||||
|
<button class="nav-link app-nav-button" type="button" data-view="logging">Logging</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
@@ -28,10 +35,30 @@
|
|||||||
<div class="container-fluid px-4 py-4">
|
<div class="container-fluid px-4 py-4">
|
||||||
<div class="card app-shell-card mx-auto shadow-sm border-0">
|
<div class="card app-shell-card mx-auto shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<h1 class="h3 mb-3">Configuration KSP</h1>
|
<h1 id="viewTitle" class="h3 mb-2">Vue d'ensemble</h1>
|
||||||
<p class="text-body-secondary mb-4">
|
<p id="viewDescription" class="text-body-secondary mb-4">État sûr du bootstrap Config Desk et du runtime Logging actuellement actif.</p>
|
||||||
Le shell desktop est prêt. Les panneaux Documents, Profils, Environnement et Logging seront ajoutés dans les prereleases suivantes.
|
|
||||||
</p>
|
<section id="overviewPanel" aria-label="Vue d'ensemble">
|
||||||
|
<dl class="row mb-4 app-overview-list">
|
||||||
|
<dt class="col-sm-5">Version application</dt>
|
||||||
|
<dd id="overviewVersion" class="col-sm-7 text-sm-end">—</dd>
|
||||||
|
<dt class="col-sm-5">Descripteurs Config enregistrés</dt>
|
||||||
|
<dd id="overviewDocuments" class="col-sm-7 text-sm-end">—</dd>
|
||||||
|
<dt class="col-sm-5">Profil Logging actif</dt>
|
||||||
|
<dd id="overviewLoggingProfile" class="col-sm-7 text-sm-end">—</dd>
|
||||||
|
<dt class="col-sm-5">Génération Logging</dt>
|
||||||
|
<dd id="overviewLoggingGeneration" class="col-sm-7 text-sm-end">—</dd>
|
||||||
|
<dt class="col-sm-5 mb-0">Fallback Logging actif</dt>
|
||||||
|
<dd id="overviewLoggingFallback" class="col-sm-7 text-sm-end mb-0">—</dd>
|
||||||
|
</dl>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="placeholderPanel" hidden aria-live="polite">
|
||||||
|
<div class="alert alert-secondary mb-4" role="status">
|
||||||
|
Cette route du shell est prête ; son contenu fonctionnel sera ajouté dans la prerelease prévue par le plan 0.1.4.
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div class="alert alert-primary mb-0" role="status">
|
<div class="alert alert-primary mb-0" role="status">
|
||||||
<i class="fa-solid fa-desktop me-2" aria-hidden="true"></i>
|
<i class="fa-solid fa-desktop me-2" aria-hidden="true"></i>
|
||||||
<span id="shellStatus" class="app-shell-status">Initialisation du frontend...</span>
|
<span id="shellStatus" class="app-shell-status">Initialisation du frontend...</span>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/frontend/sass/_app.scss
|
// file: crates/ksp-app-config-desk/frontend/sass/_app.scss
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
$app-header-height: 72px;
|
$app-header-height: 72px;
|
||||||
$app-footer-height: 42px;
|
$app-footer-height: 42px;
|
||||||
@@ -58,3 +58,17 @@ body {
|
|||||||
.app-shell-status {
|
.app-shell-status {
|
||||||
font-family: var(--bs-font-monospace);
|
font-family: var(--bs-font-monospace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-nav {
|
||||||
|
min-width: 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-nav-button {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-overview-list dd {
|
||||||
|
font-family: var(--bs-font-monospace);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/frontend/sass/splash.scss
|
// file: crates/ksp-app-config-desk/frontend/sass/splash.scss
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Dos Amazigh";
|
font-family: "Dos Amazigh";
|
||||||
@@ -26,7 +26,7 @@ body {
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: 960px;
|
width: 960px;
|
||||||
height: 637px;
|
height: 637px;
|
||||||
opacity: 1;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#splash-image {
|
#splash-image {
|
||||||
@@ -62,4 +62,4 @@ body {
|
|||||||
background: rgba(0, 0, 0, 0.35);
|
background: rgba(0, 0, 0, 0.35);
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 0.82rem;
|
font-size: 0.82rem;
|
||||||
}
|
}
|
||||||
|
|||||||
19
crates/ksp-app-config-desk/frontend/ts/invoke.ts
Normal file
19
crates/ksp-app-config-desk/frontend/ts/invoke.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// file: crates/ksp-app-config-desk/frontend/ts/invoke.ts
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
//! Shared Tauri invoke wrapper with bounded frontend lifecycle tracing.
|
||||||
|
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import { frontendDebug, frontendError, frontendTrace, type FrontendLogTargetId } from "./frontend_log";
|
||||||
|
|
||||||
|
export async function invokeKsp<T>(targetId: FrontendLogTargetId, command: string, args?: Record<string, unknown>): Promise<T> {
|
||||||
|
frontendDebug(targetId, "Frontend IPC command requested", { command });
|
||||||
|
try {
|
||||||
|
const result = await invoke<T>(command, args);
|
||||||
|
frontendTrace(targetId, "Frontend IPC command completed", { command });
|
||||||
|
return result;
|
||||||
|
} catch (caughtError) {
|
||||||
|
frontendError(targetId, "Frontend IPC command failed", { command });
|
||||||
|
throw caughtError;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,140 @@
|
|||||||
// file: crates/ksp-app-config-desk/frontend/ts/main.ts
|
// file: crates/ksp-app-config-desk/frontend/ts/main.ts
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
import "bootstrap";
|
import "bootstrap";
|
||||||
import ResizeObserver from "resize-observer-polyfill";
|
import ResizeObserver from "resize-observer-polyfill";
|
||||||
import "simplebar";
|
import "simplebar";
|
||||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
import { frontendInfo, installFrontendConsoleBridge } from "./frontend_log";
|
import type { AppSnapshotDto } from "./bindings/ksp_app_config_desk/dto_common/AppSnapshotDto.ts";
|
||||||
|
import { frontendDebug, frontendInfo, frontendTrace, installFrontendConsoleBridge } from "./frontend_log";
|
||||||
|
import { invokeKsp } from "./invoke";
|
||||||
|
|
||||||
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
|
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
|
||||||
installFrontendConsoleBridge("main");
|
installFrontendConsoleBridge("main");
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
type ViewId = "overview" | "documents" | "profiles" | "environment" | "logging";
|
||||||
const windowLabel = getCurrentWindow().label;
|
|
||||||
const status = document.querySelector<HTMLElement>("#shellStatus");
|
const viewCopy: Record<ViewId, { title: string; description: string }> = {
|
||||||
if (status) {
|
overview: {
|
||||||
status.textContent = `Frontend chargé dans la fenêtre '${windowLabel}'.`;
|
title: "Vue d'ensemble",
|
||||||
|
description: "État sûr du bootstrap Config Desk et du runtime Logging actuellement actif.",
|
||||||
|
},
|
||||||
|
documents: {
|
||||||
|
title: "Documents",
|
||||||
|
description: "Inventaire, diagnostics et réparation des documents Config — surface fonctionnelle prévue en pre.009.",
|
||||||
|
},
|
||||||
|
profiles: {
|
||||||
|
title: "Profils",
|
||||||
|
description: "Sélection et provenance des profils Config — surface fonctionnelle prévue en pre.010.",
|
||||||
|
},
|
||||||
|
environment: {
|
||||||
|
title: "Environnement / .env",
|
||||||
|
description: "Rapports et management des variables KSP/KSPB — surfaces fonctionnelles prévues en pre.011/pre.012.",
|
||||||
|
},
|
||||||
|
logging: {
|
||||||
|
title: "Logging",
|
||||||
|
description: "Édition des profils et hot reload du runtime Logging — surfaces fonctionnelles prévues en pre.014 à pre.017.",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function isViewId(value: string): value is ViewId {
|
||||||
|
return Object.prototype.hasOwnProperty.call(viewCopy, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function activateView(viewId: ViewId, source: "startup" | "user"): void {
|
||||||
|
if (source === "user") {
|
||||||
|
frontendDebug("main", "Main navigation activated", { viewId });
|
||||||
}
|
}
|
||||||
|
const copy = viewCopy[viewId];
|
||||||
|
const title = document.querySelector<HTMLElement>("#viewTitle");
|
||||||
|
const description = document.querySelector<HTMLElement>("#viewDescription");
|
||||||
|
if (title) {
|
||||||
|
title.textContent = copy.title;
|
||||||
|
}
|
||||||
|
if (description) {
|
||||||
|
description.textContent = copy.description;
|
||||||
|
}
|
||||||
|
document.querySelectorAll<HTMLButtonElement>("[data-view]").forEach(button => {
|
||||||
|
const active = button.dataset.view === viewId;
|
||||||
|
button.classList.toggle("active", active);
|
||||||
|
button.setAttribute("aria-current", active ? "page" : "false");
|
||||||
|
});
|
||||||
|
const overview = document.querySelector<HTMLElement>("#overviewPanel");
|
||||||
|
if (overview) {
|
||||||
|
overview.hidden = viewId !== "overview";
|
||||||
|
}
|
||||||
|
const placeholder = document.querySelector<HTMLElement>("#placeholderPanel");
|
||||||
|
if (placeholder) {
|
||||||
|
placeholder.hidden = viewId === "overview";
|
||||||
|
}
|
||||||
|
frontendTrace("main", "Main view DOM updated", { viewId, source });
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindNavigation(): void {
|
||||||
|
document.querySelectorAll<HTMLButtonElement>("[data-view]").forEach(button => {
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
const requestedView = button.dataset.view;
|
||||||
|
if (requestedView && isViewId(requestedView)) {
|
||||||
|
activateView(requestedView, "user");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
frontendTrace("main", "Main navigation handlers installed");
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSnapshot(snapshot: AppSnapshotDto): void {
|
||||||
|
const version = document.querySelector<HTMLElement>("#overviewVersion");
|
||||||
|
const documents = document.querySelector<HTMLElement>("#overviewDocuments");
|
||||||
|
const profile = document.querySelector<HTMLElement>("#overviewLoggingProfile");
|
||||||
|
const generation = document.querySelector<HTMLElement>("#overviewLoggingGeneration");
|
||||||
|
const fallback = document.querySelector<HTMLElement>("#overviewLoggingFallback");
|
||||||
|
if (version) {
|
||||||
|
version.textContent = snapshot.applicationVersion;
|
||||||
|
}
|
||||||
|
if (documents) {
|
||||||
|
documents.textContent = snapshot.configDocumentCount.toString();
|
||||||
|
}
|
||||||
|
if (profile) {
|
||||||
|
profile.textContent = snapshot.activeLoggingProfile ?? "fallback transitoire";
|
||||||
|
}
|
||||||
|
if (generation) {
|
||||||
|
generation.textContent = snapshot.loggingGeneration.toString();
|
||||||
|
}
|
||||||
|
if (fallback) {
|
||||||
|
fallback.textContent = snapshot.fallbackLoggingActive ? "oui" : "non";
|
||||||
|
}
|
||||||
|
frontendTrace("main", "Application snapshot rendered", {
|
||||||
|
loggingGeneration: snapshot.loggingGeneration,
|
||||||
|
fallbackLoggingActive: snapshot.fallbackLoggingActive,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadSnapshot(): Promise<void> {
|
||||||
|
const status = document.querySelector<HTMLElement>("#shellStatus");
|
||||||
|
const snapshot = await invokeKsp<AppSnapshotDto>("main", "get_app_snapshot");
|
||||||
|
renderSnapshot(snapshot);
|
||||||
|
if (status) {
|
||||||
|
status.textContent = "Shell principal prêt.";
|
||||||
|
}
|
||||||
|
frontendTrace("main", "Main shell status replaced", { status: "ready" });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initializeMain(): Promise<void> {
|
||||||
|
const windowLabel = getCurrentWindow().label;
|
||||||
frontendInfo("main", "Config Desk main frontend loaded", { windowLabel });
|
frontendInfo("main", "Config Desk main frontend loaded", { windowLabel });
|
||||||
|
bindNavigation();
|
||||||
|
activateView("overview", "startup");
|
||||||
|
try {
|
||||||
|
await loadSnapshot();
|
||||||
|
} catch {
|
||||||
|
const status = document.querySelector<HTMLElement>("#shellStatus");
|
||||||
|
if (status) {
|
||||||
|
status.textContent = "Le snapshot initial n'a pas pu être chargé.";
|
||||||
|
}
|
||||||
|
frontendTrace("main", "Main shell status replaced", { status: "snapshot_error" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
void initializeMain();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,16 +1,113 @@
|
|||||||
// file: crates/ksp-app-config-desk/frontend/ts/splash.ts
|
// file: crates/ksp-app-config-desk/frontend/ts/splash.ts
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
import { frontendInfo, installFrontendConsoleBridge } from "./frontend_log";
|
import type { SplashOrderDto } from "./bindings/ksp_app_config_desk/splash/SplashOrderDto.ts";
|
||||||
|
import { frontendDebug, frontendError, frontendInfo, frontendTrace, installFrontendConsoleBridge } from "./frontend_log";
|
||||||
|
import { invokeKsp } from "./invoke";
|
||||||
|
|
||||||
installFrontendConsoleBridge("splash");
|
installFrontendConsoleBridge("splash");
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
const defaultFadeDurationMs = 300;
|
||||||
const windowLabel = getCurrentWindow().label;
|
let activeOpacityFrame: number | null = null;
|
||||||
|
|
||||||
|
function normalizeDurationMs(value: number | null | undefined): number {
|
||||||
|
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
||||||
|
return defaultFadeDurationMs;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function easeInOut(progress: number): number {
|
||||||
|
if (progress < 0.5) {
|
||||||
|
return 2 * progress * progress;
|
||||||
|
}
|
||||||
|
return 1 - Math.pow(-2 * progress + 2, 2) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function animateOpacity(element: HTMLElement, fromOpacity: number, toOpacity: number, durationMs: number): Promise<void> {
|
||||||
|
frontendTrace("splash", "Splash opacity animation started", { fromOpacity, toOpacity, durationMs });
|
||||||
|
if (activeOpacityFrame !== null) {
|
||||||
|
cancelAnimationFrame(activeOpacityFrame);
|
||||||
|
activeOpacityFrame = null;
|
||||||
|
}
|
||||||
|
element.style.opacity = fromOpacity.toString();
|
||||||
|
element.style.willChange = "opacity";
|
||||||
|
await new Promise<void>(resolve => requestAnimationFrame(() => resolve()));
|
||||||
|
await new Promise<void>(resolve => {
|
||||||
|
const startedAt = performance.now();
|
||||||
|
const opacityDelta = toOpacity - fromOpacity;
|
||||||
|
const updateOpacity = (currentTime: number): void => {
|
||||||
|
const elapsedMs = currentTime - startedAt;
|
||||||
|
const rawProgress = Math.min(elapsedMs / durationMs, 1);
|
||||||
|
element.style.opacity = (fromOpacity + opacityDelta * easeInOut(rawProgress)).toString();
|
||||||
|
if (rawProgress >= 1) {
|
||||||
|
element.style.opacity = toOpacity.toString();
|
||||||
|
element.style.willChange = "auto";
|
||||||
|
activeOpacityFrame = null;
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
activeOpacityFrame = requestAnimationFrame(updateOpacity);
|
||||||
|
};
|
||||||
|
activeOpacityFrame = requestAnimationFrame(updateOpacity);
|
||||||
|
});
|
||||||
|
frontendTrace("splash", "Splash opacity animation completed", { toOpacity, durationMs });
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceStatus(message: string | null): void {
|
||||||
|
if (!message) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const status = document.querySelector<HTMLElement>("#splash-status");
|
const status = document.querySelector<HTMLElement>("#splash-status");
|
||||||
if (status) {
|
if (status) {
|
||||||
status.textContent = `Frontend chargé dans la fenêtre '${windowLabel}'. Lifecycle complet prévu en pre.008.`;
|
status.textContent = message;
|
||||||
|
frontendTrace("splash", "Splash status replaced", { message });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSplashOrder(order: SplashOrderDto): Promise<void> {
|
||||||
|
frontendTrace("splash", "Splash order received", { action: order.action });
|
||||||
|
const container = document.querySelector<HTMLElement>("#splash-container");
|
||||||
|
replaceStatus(order.message);
|
||||||
|
if (!container) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (order.action === "fade_in") {
|
||||||
|
await animateOpacity(container, 0, 1, normalizeDurationMs(order.durationMs));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (order.action === "fade_out") {
|
||||||
|
await animateOpacity(container, 1, 0, normalizeDurationMs(order.durationMs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initializeSplash(): Promise<void> {
|
||||||
|
const windowLabel = getCurrentWindow().label;
|
||||||
frontendInfo("splash", "Config Desk splash frontend loaded", { windowLabel });
|
frontendInfo("splash", "Config Desk splash frontend loaded", { windowLabel });
|
||||||
|
const container = document.querySelector<HTMLElement>("#splash-container");
|
||||||
|
if (container) {
|
||||||
|
container.style.opacity = "0";
|
||||||
|
container.style.willChange = "opacity";
|
||||||
|
frontendTrace("splash", "Splash container prepared for managed fade-in");
|
||||||
|
}
|
||||||
|
await listen<SplashOrderDto>("ksp-splash-order", event => {
|
||||||
|
void handleSplashOrder(event.payload);
|
||||||
|
});
|
||||||
|
frontendDebug("splash", "Splash lifecycle listener installed");
|
||||||
|
try {
|
||||||
|
await invokeKsp<void>("splash", "splash_frontend_ready");
|
||||||
|
} catch {
|
||||||
|
replaceStatus("Le lifecycle du splash n'a pas pu démarrer.");
|
||||||
|
if (container) {
|
||||||
|
container.style.opacity = "1";
|
||||||
|
container.style.willChange = "auto";
|
||||||
|
}
|
||||||
|
frontendError("splash", "Splash readiness command failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
void initializeSplash();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/app_state.rs
|
// file: crates/ksp-app-config-desk/src/app_state.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
//! Shared backend state owned by the Tauri application.
|
//! Shared backend state owned by the Tauri application.
|
||||||
|
|
||||||
@@ -15,6 +15,8 @@ struct LoggingRuntimeState {
|
|||||||
pub(crate) struct AppState {
|
pub(crate) struct AppState {
|
||||||
config_management: ksp_config_lib::ConfigManagement,
|
config_management: ksp_config_lib::ConfigManagement,
|
||||||
logging_runtime: std::sync::Mutex<LoggingRuntimeState>,
|
logging_runtime: std::sync::Mutex<LoggingRuntimeState>,
|
||||||
|
splash_settings: crate::SplashSettings,
|
||||||
|
splash_sequence_started: std::sync::atomic::AtomicBool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
@@ -30,6 +32,27 @@ impl AppState {
|
|||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
|
let splash_settings = crate::SplashSettings::load();
|
||||||
|
let splash_settings = match splash_settings {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => {
|
||||||
|
ksp_logging_lib::warn!(
|
||||||
|
target: crate::TRACING_TARGET,
|
||||||
|
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||||
|
error_domain = error.code().domain(),
|
||||||
|
error_code = error.code().code(),
|
||||||
|
"managed splash timings are invalid; using transient in-memory defaults"
|
||||||
|
);
|
||||||
|
crate::SplashSettings::fallback()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
ksp_logging_lib::debug!(
|
||||||
|
target: crate::TRACING_TARGET,
|
||||||
|
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||||
|
minimum_ms = splash_settings.minimum_ms(),
|
||||||
|
fade_ms = splash_settings.fade_ms(),
|
||||||
|
"resolved Config Desk splash timings"
|
||||||
|
);
|
||||||
return std::result::Result::Ok(Self {
|
return std::result::Result::Ok(Self {
|
||||||
config_management,
|
config_management,
|
||||||
logging_runtime: std::sync::Mutex::new(LoggingRuntimeState {
|
logging_runtime: std::sync::Mutex::new(LoggingRuntimeState {
|
||||||
@@ -39,6 +62,8 @@ impl AppState {
|
|||||||
fallback_active: logging_startup.fallback_active,
|
fallback_active: logging_startup.fallback_active,
|
||||||
startup_diagnostic: logging_startup.startup_diagnostic,
|
startup_diagnostic: logging_startup.startup_diagnostic,
|
||||||
}),
|
}),
|
||||||
|
splash_settings,
|
||||||
|
splash_sequence_started: std::sync::atomic::AtomicBool::new(false),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,4 +100,18 @@ impl AppState {
|
|||||||
startup_diagnostic: runtime.startup_diagnostic.clone(),
|
startup_diagnostic: runtime.startup_diagnostic.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the resolved splash timings captured during application bootstrap.
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) const fn splash_settings(&self) -> crate::SplashSettings {
|
||||||
|
return self.splash_settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marks the one-shot splash lifecycle as started and reports whether this caller won the transition.
|
||||||
|
pub(crate) fn begin_splash_sequence(&self) -> bool {
|
||||||
|
return self
|
||||||
|
.splash_sequence_started
|
||||||
|
.compare_exchange(false, true, std::sync::atomic::Ordering::AcqRel, std::sync::atomic::Ordering::Acquire)
|
||||||
|
.is_ok();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/constants.rs
|
// file: crates/ksp-app-config-desk/src/constants.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
//! Application-owned tracing targets and domains.
|
//! Application-owned tracing targets and domains.
|
||||||
|
|
||||||
@@ -15,3 +15,5 @@ pub(crate) const TRACING_TARGET_FRONTEND_SPLASH: &str = "ksp-app-config-desk.fro
|
|||||||
pub(crate) const TRACING_DOMAIN_BOOTSTRAP: &str = "config.bootstrap";
|
pub(crate) const TRACING_DOMAIN_BOOTSTRAP: &str = "config.bootstrap";
|
||||||
/// Structured domain used by technical frontend events.
|
/// Structured domain used by technical frontend events.
|
||||||
pub(crate) const TRACING_DOMAIN_FRONTEND: &str = "frontend";
|
pub(crate) const TRACING_DOMAIN_FRONTEND: &str = "frontend";
|
||||||
|
/// Structured domain used by Tauri window lifecycle operations.
|
||||||
|
pub(crate) const TRACING_DOMAIN_WINDOWS: &str = "desktop.window";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/errors.rs
|
// file: crates/ksp-app-config-desk/src/errors.rs
|
||||||
// version: 3
|
// version: 4
|
||||||
|
|
||||||
//! Application-local error codes for the configuration desktop shell.
|
//! Application-local error codes for the configuration desktop shell.
|
||||||
|
|
||||||
@@ -15,3 +15,12 @@ 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");
|
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.
|
/// 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");
|
pub(crate) const ERROR_CODE_FRONTEND_LOG_TARGET_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "frontend_log_target_invalid");
|
||||||
|
/// A KSP desk splash environment duration is malformed or exceeds its safety bound.
|
||||||
|
pub(crate) const ERROR_CODE_SPLASH_SETTING_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "splash_setting_invalid");
|
||||||
|
/// Splash readiness was invoked from a window other than the splash window.
|
||||||
|
pub(crate) const ERROR_CODE_SPLASH_ORIGIN_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "splash_origin_invalid");
|
||||||
|
/// A required Tauri window is missing from the configured application runtime.
|
||||||
|
pub(crate) const ERROR_CODE_TAURI_WINDOW_MISSING: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "tauri_window_missing");
|
||||||
|
/// A Tauri window show/focus/destroy/event operation failed.
|
||||||
|
pub(crate) const ERROR_CODE_TAURI_WINDOW_OPERATION_FAILED: ksp_core_lib::ErrorCode =
|
||||||
|
ksp_core_lib::ErrorCode::new("config_desk", "tauri_window_operation_failed");
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/lib.rs
|
// file: crates/ksp-app-config-desk/src/lib.rs
|
||||||
// version: 3
|
// version: 4
|
||||||
|
|
||||||
//! Tauri desktop application for managing and validating KSP configuration.
|
//! Tauri desktop application for managing and validating KSP configuration.
|
||||||
|
|
||||||
@@ -13,7 +13,10 @@ mod constants;
|
|||||||
mod dto_common;
|
mod dto_common;
|
||||||
mod errors;
|
mod errors;
|
||||||
mod frontend_logging;
|
mod frontend_logging;
|
||||||
|
mod splash;
|
||||||
mod tauri;
|
mod tauri;
|
||||||
|
mod tw_main;
|
||||||
|
mod tw_splash;
|
||||||
|
|
||||||
/// Runs the KSP configuration desktop application.
|
/// Runs the KSP configuration desktop application.
|
||||||
pub use self::tauri::run;
|
pub use self::tauri::run;
|
||||||
@@ -23,6 +26,7 @@ pub(crate) use self::bootstrap::config_management;
|
|||||||
pub(crate) use self::bootstrap::initialize_logging;
|
pub(crate) use self::bootstrap::initialize_logging;
|
||||||
pub(crate) use self::constants::TRACING_DOMAIN_BOOTSTRAP;
|
pub(crate) use self::constants::TRACING_DOMAIN_BOOTSTRAP;
|
||||||
pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND;
|
pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND;
|
||||||
|
pub(crate) use self::constants::TRACING_DOMAIN_WINDOWS;
|
||||||
pub(crate) use self::constants::TRACING_TARGET;
|
pub(crate) use self::constants::TRACING_TARGET;
|
||||||
pub(crate) use self::constants::TRACING_TARGET_FRONTEND;
|
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_MAIN;
|
||||||
@@ -34,6 +38,16 @@ pub(crate) use self::errors::ERROR_CODE_APP_STATE_LOCK_FAILED;
|
|||||||
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID;
|
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID;
|
||||||
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_TARGET_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_BOOTSTRAP_FAILED;
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_SPLASH_ORIGIN_INVALID;
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_SPLASH_SETTING_INVALID;
|
||||||
pub(crate) use self::errors::ERROR_CODE_TAURI_RUNTIME_FAILED;
|
pub(crate) use self::errors::ERROR_CODE_TAURI_RUNTIME_FAILED;
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_TAURI_WINDOW_MISSING;
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_TAURI_WINDOW_OPERATION_FAILED;
|
||||||
pub(crate) use self::frontend_logging::FrontendLogPayloadDto;
|
pub(crate) use self::frontend_logging::FrontendLogPayloadDto;
|
||||||
pub(crate) use self::frontend_logging::emit_frontend_log_event;
|
pub(crate) use self::frontend_logging::emit_frontend_log_event;
|
||||||
|
pub(crate) use self::splash::SplashOrderDto;
|
||||||
|
pub(crate) use self::splash::SplashSettings;
|
||||||
|
pub(crate) use self::tw_main::WINDOW_LABEL_MAIN;
|
||||||
|
pub(crate) use self::tw_main::show_and_focus as show_main_window;
|
||||||
|
pub(crate) use self::tw_splash::WINDOW_LABEL_SPLASH;
|
||||||
|
pub(crate) use self::tw_splash::frontend_ready as splash_frontend_ready_service;
|
||||||
|
|||||||
138
crates/ksp-app-config-desk/src/splash.rs
Normal file
138
crates/ksp-app-config-desk/src/splash.rs
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
// file: crates/ksp-app-config-desk/src/splash.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
//! Common splash settings and frontend event contracts for Config Desk.
|
||||||
|
|
||||||
|
use ts_rs::TS; // rust-rules: derive-import
|
||||||
|
|
||||||
|
const ENV_SPLASH_MINIMUM_MS: &str = "KSP_DESK_SPLASH_MINIMUM_MS";
|
||||||
|
const ENV_SPLASH_FADE_MS: &str = "KSP_DESK_SPLASH_FADE_MS";
|
||||||
|
const DEFAULT_SPLASH_MINIMUM_MS: u64 = 1200;
|
||||||
|
const DEFAULT_SPLASH_FADE_MS: u32 = 300;
|
||||||
|
const MAX_SPLASH_MINIMUM_MS: u64 = 60_000;
|
||||||
|
const MAX_SPLASH_FADE_MS: u32 = 10_000;
|
||||||
|
|
||||||
|
/// Runtime timings used by the common desk splash lifecycle.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) struct SplashSettings {
|
||||||
|
minimum_ms: u64,
|
||||||
|
fade_ms: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SplashSettings {
|
||||||
|
/// Resolves splash timings through the Config-owned environment snapshot.
|
||||||
|
pub(crate) fn load() -> ksp_core_lib::Result<Self> {
|
||||||
|
let environment = ksp_config_lib::ConfigEnvironment::load();
|
||||||
|
let environment = match environment {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let minimum = environment.resolve_variable(ENV_SPLASH_MINIMUM_MS, std::option::Option::Some("1200"));
|
||||||
|
let minimum = match minimum {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let fade = environment.resolve_variable(ENV_SPLASH_FADE_MS, std::option::Option::Some("300"));
|
||||||
|
let fade = match fade {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let minimum_ms = parse_u64_setting(ENV_SPLASH_MINIMUM_MS, minimum.value(), MAX_SPLASH_MINIMUM_MS);
|
||||||
|
let minimum_ms = match minimum_ms {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let fade_ms = parse_u32_setting(ENV_SPLASH_FADE_MS, fade.value(), MAX_SPLASH_FADE_MS);
|
||||||
|
let fade_ms = match fade_ms {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
return std::result::Result::Ok(Self { minimum_ms, fade_ms });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns safe in-memory timings used when the managed environment cannot be resolved.
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) const fn fallback() -> Self {
|
||||||
|
return Self { minimum_ms: DEFAULT_SPLASH_MINIMUM_MS, fade_ms: DEFAULT_SPLASH_FADE_MS };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the minimum visible duration after splash frontend readiness.
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) const fn minimum_ms(self) -> u64 {
|
||||||
|
return self.minimum_ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the fade duration used for both fade-in and fade-out.
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) const fn fade_ms(self) -> u32 {
|
||||||
|
return self.fade_ms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Command emitted by Rust to the splash frontend.
|
||||||
|
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_config_desk/splash/SplashOrderDto.ts")]
|
||||||
|
pub(crate) struct SplashOrderDto {
|
||||||
|
/// Splash action name.
|
||||||
|
pub(crate) action: String,
|
||||||
|
/// Optional status text.
|
||||||
|
pub(crate) message: std::option::Option<String>,
|
||||||
|
/// Optional animation duration in milliseconds.
|
||||||
|
pub(crate) duration_ms: std::option::Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SplashOrderDto {
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) fn new(action: &str, message: std::option::Option<&str>, duration_ms: std::option::Option<u32>) -> Self {
|
||||||
|
return Self { action: action.to_owned(), message: message.map(std::string::ToString::to_string), duration_ms };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_u64_setting(variable_name: &str, value: &str, maximum: u64) -> ksp_core_lib::Result<u64> {
|
||||||
|
let parsed = value.parse::<u64>();
|
||||||
|
let parsed = match parsed {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_SPLASH_SETTING_INVALID, "KSP desk splash duration is not a valid unsigned integer")
|
||||||
|
.with_context("variable_name", variable_name)
|
||||||
|
.with_source(error),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if parsed > maximum {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_SPLASH_SETTING_INVALID, "KSP desk splash duration exceeds the allowed bound")
|
||||||
|
.with_context("variable_name", variable_name)
|
||||||
|
.with_context("maximum_ms", maximum),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_u32_setting(variable_name: &str, value: &str, maximum: u32) -> ksp_core_lib::Result<u32> {
|
||||||
|
let parsed = value.parse::<u32>();
|
||||||
|
let parsed = match parsed {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_SPLASH_SETTING_INVALID, "KSP desk splash duration is not a valid unsigned integer")
|
||||||
|
.with_context("variable_name", variable_name)
|
||||||
|
.with_source(error),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if parsed > maximum {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_SPLASH_SETTING_INVALID, "KSP desk splash duration exceeds the allowed bound")
|
||||||
|
.with_context("variable_name", variable_name)
|
||||||
|
.with_context("maximum_ms", maximum),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "../unit_tests/splash.rs"]
|
||||||
|
mod tests;
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/tauri.rs
|
// file: crates/ksp-app-config-desk/src/tauri.rs
|
||||||
// version: 4
|
// version: 5
|
||||||
|
|
||||||
//! Tauri runtime assembly for the KSP configuration desktop application.
|
//! Tauri runtime assembly for the KSP configuration desktop application.
|
||||||
|
|
||||||
use tauri::Manager; // rust-rules: trait-import
|
|
||||||
|
|
||||||
/// Runs the configuration desktop application.
|
/// Runs the configuration desktop application.
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<()> {
|
pub fn run(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<()> {
|
||||||
@@ -38,16 +36,18 @@ fn configure_plugins(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<taur
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn configure_commands(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
|
fn configure_commands(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
|
||||||
return builder.invoke_handler(tauri::generate_handler![get_app_snapshot, emit_frontend_log]);
|
return builder.invoke_handler(tauri::generate_handler![get_app_snapshot, emit_frontend_log, splash_frontend_ready]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn configure_setup(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
|
fn configure_setup(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
|
||||||
return builder.setup(|app| {
|
return builder.setup(|app| {
|
||||||
if app.get_webview_window("splash").is_none() {
|
let splash = crate::tw_splash::require_window(app);
|
||||||
return std::result::Result::Err(std::boxed::Box::new(std::io::Error::new(std::io::ErrorKind::NotFound, "splash window is missing")));
|
if let std::result::Result::Err(error) = splash {
|
||||||
|
return std::result::Result::Err(std::boxed::Box::new(error));
|
||||||
}
|
}
|
||||||
if app.get_webview_window("main").is_none() {
|
let main = crate::tw_main::require_window(app);
|
||||||
return std::result::Result::Err(std::boxed::Box::new(std::io::Error::new(std::io::ErrorKind::NotFound, "main window is missing")));
|
if let std::result::Result::Err(error) = main {
|
||||||
|
return std::result::Result::Err(std::boxed::Box::new(error));
|
||||||
}
|
}
|
||||||
return std::result::Result::Ok(());
|
return std::result::Result::Ok(());
|
||||||
});
|
});
|
||||||
@@ -70,3 +70,16 @@ fn emit_frontend_log(payload: crate::FrontendLogPayloadDto) -> std::result::Resu
|
|||||||
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn splash_frontend_ready(
|
||||||
|
app: tauri::AppHandle,
|
||||||
|
webview_window: tauri::WebviewWindow,
|
||||||
|
state: tauri::State<'_, crate::AppState>,
|
||||||
|
) -> std::result::Result<(), crate::CommandErrorDto> {
|
||||||
|
let result = crate::splash_frontend_ready_service(app, webview_window, &state).await;
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
50
crates/ksp-app-config-desk/src/tw_main.rs
Normal file
50
crates/ksp-app-config-desk/src/tw_main.rs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// file: crates/ksp-app-config-desk/src/tw_main.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
//! Tauri-window helpers for the Config Desk main window.
|
||||||
|
|
||||||
|
use tauri::Manager; // rust-rules: trait-import
|
||||||
|
|
||||||
|
pub(crate) const WINDOW_LABEL_MAIN: &str = "main";
|
||||||
|
|
||||||
|
pub(crate) fn require_window(manager: &impl Manager<tauri::Wry>) -> ksp_core_lib::Result<tauri::WebviewWindow> {
|
||||||
|
let window = manager.get_webview_window(WINDOW_LABEL_MAIN);
|
||||||
|
return match window {
|
||||||
|
std::option::Option::Some(value) => std::result::Result::Ok(value),
|
||||||
|
std::option::Option::None => std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_WINDOW_MISSING, "Config Desk main window is missing")
|
||||||
|
.with_context("window_label", WINDOW_LABEL_MAIN),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn show_and_focus(app: &tauri::AppHandle) -> ksp_core_lib::Result<()> {
|
||||||
|
let window = require_window(app);
|
||||||
|
let window = match window {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let shown = window.show();
|
||||||
|
if let std::result::Result::Err(error) = shown {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_WINDOW_OPERATION_FAILED, "Cannot show Config Desk main window")
|
||||||
|
.with_context("window_label", WINDOW_LABEL_MAIN)
|
||||||
|
.with_source(error),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let focused = window.set_focus();
|
||||||
|
if let std::result::Result::Err(error) = focused {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_WINDOW_OPERATION_FAILED, "Cannot focus Config Desk main window")
|
||||||
|
.with_context("window_label", WINDOW_LABEL_MAIN)
|
||||||
|
.with_source(error),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ksp_logging_lib::debug!(
|
||||||
|
target: crate::TRACING_TARGET,
|
||||||
|
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||||
|
window_label = WINDOW_LABEL_MAIN,
|
||||||
|
"main window shown and focused"
|
||||||
|
);
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
105
crates/ksp-app-config-desk/src/tw_splash.rs
Normal file
105
crates/ksp-app-config-desk/src/tw_splash.rs
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
// file: crates/ksp-app-config-desk/src/tw_splash.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
//! Tauri-window lifecycle for the Config Desk splash window.
|
||||||
|
|
||||||
|
use tauri::Emitter; // rust-rules: trait-import
|
||||||
|
use tauri::Manager; // rust-rules: trait-import
|
||||||
|
|
||||||
|
pub(crate) const WINDOW_LABEL_SPLASH: &str = "splash";
|
||||||
|
const SPLASH_EVENT_NAME: &str = "ksp-splash-order";
|
||||||
|
|
||||||
|
pub(crate) fn require_window(manager: &impl Manager<tauri::Wry>) -> ksp_core_lib::Result<tauri::WebviewWindow> {
|
||||||
|
let window = manager.get_webview_window(WINDOW_LABEL_SPLASH);
|
||||||
|
return match window {
|
||||||
|
std::option::Option::Some(value) => std::result::Result::Ok(value),
|
||||||
|
std::option::Option::None => std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_WINDOW_MISSING, "Config Desk splash window is missing")
|
||||||
|
.with_context("window_label", WINDOW_LABEL_SPLASH),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn frontend_ready(app: tauri::AppHandle, invoking_window: tauri::WebviewWindow, state: &crate::AppState) -> ksp_core_lib::Result<()> {
|
||||||
|
if invoking_window.label() != WINDOW_LABEL_SPLASH {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_SPLASH_ORIGIN_INVALID, "Splash readiness may only originate from the splash window")
|
||||||
|
.with_context("window_label", invoking_window.label()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if !state.begin_splash_sequence() {
|
||||||
|
ksp_logging_lib::trace!(
|
||||||
|
target: crate::TRACING_TARGET,
|
||||||
|
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||||
|
window_label = WINDOW_LABEL_SPLASH,
|
||||||
|
"duplicate splash frontend readiness ignored"
|
||||||
|
);
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
let settings = state.splash_settings();
|
||||||
|
ksp_logging_lib::debug!(
|
||||||
|
target: crate::TRACING_TARGET,
|
||||||
|
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||||
|
minimum_ms = settings.minimum_ms(),
|
||||||
|
fade_ms = settings.fade_ms(),
|
||||||
|
"starting splash to main lifecycle"
|
||||||
|
);
|
||||||
|
let fade_in = emit_order(
|
||||||
|
&invoking_window,
|
||||||
|
crate::SplashOrderDto::new("fade_in", std::option::Option::Some("Initialisation de KSP Config Desk..."), std::option::Option::Some(settings.fade_ms())),
|
||||||
|
);
|
||||||
|
if let std::result::Result::Err(error) = fade_in {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(settings.minimum_ms())).await;
|
||||||
|
let fade_out = emit_order(
|
||||||
|
&invoking_window,
|
||||||
|
crate::SplashOrderDto::new("fade_out", std::option::Option::Some("Initialisation terminée."), std::option::Option::Some(settings.fade_ms())),
|
||||||
|
);
|
||||||
|
if let std::result::Result::Err(error) = fade_out {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(u64::from(settings.fade_ms()))).await;
|
||||||
|
let show_main = crate::show_main_window(&app);
|
||||||
|
if let std::result::Result::Err(error) = show_main {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
let destroyed = invoking_window.destroy();
|
||||||
|
if let std::result::Result::Err(error) = destroyed {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_WINDOW_OPERATION_FAILED, "Cannot destroy Config Desk splash window")
|
||||||
|
.with_context("window_label", WINDOW_LABEL_SPLASH)
|
||||||
|
.with_source(error),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ksp_logging_lib::debug!(
|
||||||
|
target: crate::TRACING_TARGET,
|
||||||
|
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||||
|
window_label = WINDOW_LABEL_SPLASH,
|
||||||
|
"splash window destroyed after main activation"
|
||||||
|
);
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_order(window: &tauri::WebviewWindow, order: crate::SplashOrderDto) -> ksp_core_lib::Result<()> {
|
||||||
|
let action = order.action.clone();
|
||||||
|
let emitted = window.emit(SPLASH_EVENT_NAME, order);
|
||||||
|
return match emitted {
|
||||||
|
std::result::Result::Ok(()) => {
|
||||||
|
ksp_logging_lib::trace!(
|
||||||
|
target: crate::TRACING_TARGET,
|
||||||
|
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||||
|
window_label = WINDOW_LABEL_SPLASH,
|
||||||
|
action = action.as_str(),
|
||||||
|
"splash order emitted"
|
||||||
|
);
|
||||||
|
std::result::Result::Ok(())
|
||||||
|
},
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_WINDOW_OPERATION_FAILED, "Cannot emit Config Desk splash order")
|
||||||
|
.with_context("window_label", WINDOW_LABEL_SPLASH)
|
||||||
|
.with_context("action", action)
|
||||||
|
.with_source(error),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
26
crates/ksp-app-config-desk/unit_tests/splash.rs
Normal file
26
crates/ksp-app-config-desk/unit_tests/splash.rs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// file: crates/ksp-app-config-desk/unit_tests/splash.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fallback_splash_settings_are_short_and_ordered() {
|
||||||
|
let settings = crate::SplashSettings::fallback();
|
||||||
|
assert_eq!(settings.minimum_ms(), 1200);
|
||||||
|
assert_eq!(settings.fade_ms(), 300);
|
||||||
|
assert!(settings.minimum_ms() >= u64::from(settings.fade_ms()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn splash_order_contract_keeps_action_message_and_duration() {
|
||||||
|
let order = crate::SplashOrderDto::new("fade_out", std::option::Option::Some("done"), std::option::Option::Some(300));
|
||||||
|
assert_eq!(order.action, "fade_out");
|
||||||
|
assert_eq!(order.message.as_deref(), std::option::Option::Some("done"));
|
||||||
|
assert_eq!(order.duration_ms, std::option::Option::Some(300));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn splash_duration_parser_rejects_invalid_or_unbounded_values() {
|
||||||
|
let malformed = super::parse_u64_setting("KSP_DESK_SPLASH_MINIMUM_MS", "invalid", 60_000);
|
||||||
|
let oversized = super::parse_u32_setting("KSP_DESK_SPLASH_FADE_MS", "10001", 10_000);
|
||||||
|
assert!(malformed.is_err());
|
||||||
|
assert!(oversized.is_err());
|
||||||
|
}
|
||||||
308
deltas/0.1.4/pre.008.md
Normal file
308
deltas/0.1.4/pre.008.md
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
<!-- file: deltas/0.1.4/pre.008.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# `0.1.4-pre.008` — Shell principal et lifecycle splash de référence
|
||||||
|
|
||||||
|
## 1. Base validée
|
||||||
|
|
||||||
|
La base `0.1.4-pre.7` a été validée localement avec :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-app-config-desk
|
||||||
|
cargo test -p ksp-config-lib
|
||||||
|
cargo tree -p ksp-app-config-desk
|
||||||
|
cargo tauri dev -c crates/ksp-app-config-desk/tauri.conf.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Le bridge frontend Logging est observable sous les targets `ksp-app-config-desk.frontend.splash` et `ksp-app-config-desk.frontend.main`. La fenêtre `main` est chargée mais reste volontairement invisible dans cette base, car la transition réelle appartient à `pre.008`.
|
||||||
|
|
||||||
|
## 2. Objectif
|
||||||
|
|
||||||
|
Cette tranche transforme le squelette à deux WebViews en lifecycle desktop réel et pose le shell de navigation monofenêtre de référence.
|
||||||
|
|
||||||
|
Elle introduit :
|
||||||
|
|
||||||
|
- `tw_splash.rs` et `tw_main.rs` conformément à `KSP-APP-014` ;
|
||||||
|
- un module `splash.rs` pour les timings et le DTO d'ordres frontend ;
|
||||||
|
- `splash_frontend_ready` comme commande Tauri centralisée ;
|
||||||
|
- une transition one-shot `splash -> main` ;
|
||||||
|
- affichage + focus de `main`, puis destruction de `splash` ;
|
||||||
|
- les variables Config/.env communes du splash ;
|
||||||
|
- le premier shell de navigation `Vue d'ensemble / Documents / Profils / Environnement / Logging` ;
|
||||||
|
- le wrapper frontend `invoke.ts` ;
|
||||||
|
- la règle normative de traçabilité frontend `debug`/`trace`.
|
||||||
|
|
||||||
|
Les panneaux Config fonctionnels restent hors scope jusqu'à leurs prereleases dédiées.
|
||||||
|
|
||||||
|
## 3. Version technique
|
||||||
|
|
||||||
|
La tranche modifie Rust, TypeScript, SCSS, HTML, environnement et dépendances/features :
|
||||||
|
|
||||||
|
```text
|
||||||
|
workspace.package.version = "0.1.4-pre.8"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Modules Tauri window
|
||||||
|
|
||||||
|
Les responsabilités sont désormais séparées :
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/tw_splash.rs
|
||||||
|
- résolution/validation de la fenêtre splash
|
||||||
|
- readiness frontend
|
||||||
|
- émission des ordres frontend
|
||||||
|
- séquence de transition
|
||||||
|
- destruction du splash
|
||||||
|
|
||||||
|
src/tw_main.rs
|
||||||
|
- résolution/validation de la fenêtre main
|
||||||
|
- show + focus
|
||||||
|
|
||||||
|
src/splash.rs
|
||||||
|
- SplashSettings
|
||||||
|
- SplashOrderDto
|
||||||
|
- validation des timings
|
||||||
|
```
|
||||||
|
|
||||||
|
`tauri.rs` conserve seulement les wrappers `#[tauri::command]`, l'assemblage du Builder et le setup commun.
|
||||||
|
|
||||||
|
## 5. Origine réelle de la readiness
|
||||||
|
|
||||||
|
Le wrapper `splash_frontend_ready` reçoit directement la `tauri::WebviewWindow` injectée par Tauri. Le backend vérifie :
|
||||||
|
|
||||||
|
```text
|
||||||
|
webview_window.label() == "splash"
|
||||||
|
```
|
||||||
|
|
||||||
|
La commande ne fait donc pas confiance à un label fourni par le payload JavaScript.
|
||||||
|
|
||||||
|
Un `AtomicBool` dans `AppState` rend la séquence one-shot. Une readiness supplémentaire — notamment lors d'un reload Vite — est ignorée et journalisée au niveau `trace`.
|
||||||
|
|
||||||
|
## 6. Timings Config/.env
|
||||||
|
|
||||||
|
Deux variables communes au gabarit desk sont figées :
|
||||||
|
|
||||||
|
```text
|
||||||
|
KSP_DESK_SPLASH_MINIMUM_MS=1200
|
||||||
|
KSP_DESK_SPLASH_FADE_MS=300
|
||||||
|
```
|
||||||
|
|
||||||
|
Elles sont ajoutées à `.env.example` dans cette tranche et lues exclusivement via `ksp_config_lib::ConfigEnvironment`.
|
||||||
|
|
||||||
|
Priorité :
|
||||||
|
|
||||||
|
```text
|
||||||
|
process > .env > fallback
|
||||||
|
```
|
||||||
|
|
||||||
|
Bornes applicatives :
|
||||||
|
|
||||||
|
```text
|
||||||
|
minimum : 0 .. 60_000 ms
|
||||||
|
fade : 0 .. 10_000 ms
|
||||||
|
```
|
||||||
|
|
||||||
|
Une valeur invalide ne bloque pas Config Desk : le runtime journalise uniquement le domaine/code et utilise en mémoire les defaults `1200/300`. Rien n'est réécrit automatiquement dans `.env`.
|
||||||
|
|
||||||
|
## 7. Séquence runtime
|
||||||
|
|
||||||
|
Après installation de son listener, `splash.ts` invoque :
|
||||||
|
|
||||||
|
```text
|
||||||
|
splash_frontend_ready
|
||||||
|
```
|
||||||
|
|
||||||
|
Le backend :
|
||||||
|
|
||||||
|
1. vérifie l'origine `splash` ;
|
||||||
|
2. verrouille la séquence one-shot ;
|
||||||
|
3. émet `fade_in` avec la durée configurée ;
|
||||||
|
4. attend `KSP_DESK_SPLASH_MINIMUM_MS` ;
|
||||||
|
5. émet `fade_out` ;
|
||||||
|
6. attend la durée de fade-out ;
|
||||||
|
7. appelle `tw_main::show_and_focus` ;
|
||||||
|
8. détruit la fenêtre splash.
|
||||||
|
|
||||||
|
Aucun troisième `close delay` n'est nécessaire.
|
||||||
|
|
||||||
|
## 8. Contrat splash TS-RS
|
||||||
|
|
||||||
|
`SplashOrderDto` est exporté sous :
|
||||||
|
|
||||||
|
```text
|
||||||
|
frontend/ts/bindings/ksp_app_config_desk/splash/SplashOrderDto.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Champs :
|
||||||
|
|
||||||
|
```text
|
||||||
|
action
|
||||||
|
message
|
||||||
|
durationMs
|
||||||
|
```
|
||||||
|
|
||||||
|
Actions actuelles :
|
||||||
|
|
||||||
|
```text
|
||||||
|
fade_in
|
||||||
|
fade_out
|
||||||
|
```
|
||||||
|
|
||||||
|
Le frontend ne reçoit pas la durée minimale : elle reste une décision backend.
|
||||||
|
|
||||||
|
## 9. Shell principal
|
||||||
|
|
||||||
|
`main.html` expose les cinq routes de référence :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Vue d'ensemble
|
||||||
|
Documents
|
||||||
|
Profils
|
||||||
|
Environnement / .env
|
||||||
|
Logging
|
||||||
|
```
|
||||||
|
|
||||||
|
`Vue d'ensemble` charge déjà `get_app_snapshot` et affiche :
|
||||||
|
|
||||||
|
- version de l'application ;
|
||||||
|
- nombre de descripteurs Config ;
|
||||||
|
- profil Logging actif ou fallback ;
|
||||||
|
- génération Logging ;
|
||||||
|
- état fallback Logging.
|
||||||
|
|
||||||
|
Les quatre autres routes restent des placeholders explicites jusqu'à leur tranche fonctionnelle. Aucune logique Config n'est simulée dans le frontend.
|
||||||
|
|
||||||
|
## 10. Wrapper IPC frontend
|
||||||
|
|
||||||
|
`frontend/ts/invoke.ts` centralise les appels Tauri ordinaires et trace :
|
||||||
|
|
||||||
|
```text
|
||||||
|
debug : commande demandée
|
||||||
|
trace : commande terminée
|
||||||
|
error : commande échouée
|
||||||
|
```
|
||||||
|
|
||||||
|
Le wrapper ne journalise jamais les arguments/payloads. Cette contrainte est importante avant l'arrivée des commandes `.env` et Secret.
|
||||||
|
|
||||||
|
`emit_frontend_log` continue volontairement à utiliser `invoke` directement afin de ne pas créer une récursion du bridge.
|
||||||
|
|
||||||
|
## 11. Traçabilité frontend normative
|
||||||
|
|
||||||
|
`KSP-APP-027` est ajouté :
|
||||||
|
|
||||||
|
- action utilisateur / transition significative -> `debug` ;
|
||||||
|
- rendu, remplacement DOM, étape technique fréquente -> `trace` ;
|
||||||
|
- chargement/refresh -> début + fin tracés sans payload sensible.
|
||||||
|
|
||||||
|
`main.ts` applique déjà cette règle aux clics de navigation, remplacements de vue, snapshot et statut du shell.
|
||||||
|
|
||||||
|
`splash.ts` l'applique aux listeners, ordres reçus, changements de statut et animations d'opacité.
|
||||||
|
|
||||||
|
Cette règle doit être réutilisée dans les futures applications Tauri KSP.
|
||||||
|
|
||||||
|
## 12. Tokio
|
||||||
|
|
||||||
|
Le lifecycle nécessite maintenant une temporisation async réelle. La dépendance workspace `tokio` conserve ses features existantes et ajoute seulement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
time
|
||||||
|
```
|
||||||
|
|
||||||
|
`ksp-app-config-desk` consomme `tokio.workspace = true`. Aucun feature `full` n'est activé.
|
||||||
|
|
||||||
|
## 13. Erreurs applicatives
|
||||||
|
|
||||||
|
Les nouveaux codes bornés sont :
|
||||||
|
|
||||||
|
```text
|
||||||
|
config_desk.splash_setting_invalid
|
||||||
|
config_desk.splash_origin_invalid
|
||||||
|
config_desk.tauri_window_missing
|
||||||
|
config_desk.tauri_window_operation_failed
|
||||||
|
```
|
||||||
|
|
||||||
|
Les wrappers Tauri continuent de retourner uniquement `CommandErrorDto { domain, code, message }` au frontend.
|
||||||
|
|
||||||
|
## 14. Fichiers
|
||||||
|
|
||||||
|
| Fichier | Action |
|
||||||
|
|--------------------------------------------------------|--------:|
|
||||||
|
| `.env.example` | modifié |
|
||||||
|
| `Cargo.toml` | modifié |
|
||||||
|
| `crates/ksp-app-config-desk/Cargo.toml` | modifié |
|
||||||
|
| `crates/ksp-app-config-desk/src/app_state.rs` | modifié |
|
||||||
|
| `crates/ksp-app-config-desk/src/constants.rs` | modifié |
|
||||||
|
| `crates/ksp-app-config-desk/src/errors.rs` | modifié |
|
||||||
|
| `crates/ksp-app-config-desk/src/lib.rs` | modifié |
|
||||||
|
| `crates/ksp-app-config-desk/src/splash.rs` | ajouté |
|
||||||
|
| `crates/ksp-app-config-desk/src/tauri.rs` | modifié |
|
||||||
|
| `crates/ksp-app-config-desk/src/tw_main.rs` | ajouté |
|
||||||
|
| `crates/ksp-app-config-desk/src/tw_splash.rs` | ajouté |
|
||||||
|
| `crates/ksp-app-config-desk/unit_tests/splash.rs` | ajouté |
|
||||||
|
| `crates/ksp-app-config-desk/frontend/main.html` | modifié |
|
||||||
|
| `crates/ksp-app-config-desk/frontend/sass/_app.scss` | modifié |
|
||||||
|
| `crates/ksp-app-config-desk/frontend/sass/splash.scss` | modifié |
|
||||||
|
| `crates/ksp-app-config-desk/frontend/ts/invoke.ts` | ajouté |
|
||||||
|
| `crates/ksp-app-config-desk/frontend/ts/main.ts` | modifié |
|
||||||
|
| `crates/ksp-app-config-desk/frontend/ts/splash.ts` | modifié |
|
||||||
|
| `crates/ksp-app-config-desk/README.md` | modifié |
|
||||||
|
| `crates/ksp-app-config-desk/USAGE.md` | modifié |
|
||||||
|
| `crates/ksp-app-config-desk/TODO.md` | modifié |
|
||||||
|
| `docs/plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md` | modifié |
|
||||||
|
| `docs/rules/RULES_KSP.md` | modifié |
|
||||||
|
| `deltas/0.1.4/pre.008.md` | ajouté |
|
||||||
|
|
||||||
|
## 15. Validations à exécuter
|
||||||
|
|
||||||
|
Depuis la racine du workspace :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-app-config-desk
|
||||||
|
cargo test -p ksp-config-lib
|
||||||
|
cargo tree -p ksp-app-config-desk
|
||||||
|
grep -n 'tokio v' <(cargo tree -p ksp-app-config-desk -e features)
|
||||||
|
cargo tauri dev -c crates/ksp-app-config-desk/tauri.conf.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Vérifier ensuite :
|
||||||
|
|
||||||
|
1. le splash apparaît avec la police `Dos Amazigh` ;
|
||||||
|
2. après environ 1,2 s plus fade, `main` devient visible et reçoit le focus ;
|
||||||
|
3. `splash` est détruite ;
|
||||||
|
4. la navigation change le titre/placeholder sans recharger la WebView ;
|
||||||
|
5. avec un niveau Logging `Debug`/`Trace`, les clics et mutations du shell sont observables ;
|
||||||
|
6. une readiness splash dupliquée ne lance pas une deuxième transition ;
|
||||||
|
7. les bindings TS-RS incluent `SplashOrderDto.ts`.
|
||||||
|
|
||||||
|
Pour vérifier les variables :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
KSP_DESK_SPLASH_MINIMUM_MS=3000 cargo tauri dev -c crates/ksp-app-config-desk/tauri.conf.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Le splash doit rester visible sensiblement plus longtemps sans changement de code.
|
||||||
|
|
||||||
|
## 16. Contrôles réalisés dans l'environnement de préparation
|
||||||
|
|
||||||
|
L'environnement de préparation ne possède pas `cargo`/`rustc`; les commandes Rust ci-dessus restent donc à exécuter localement avant commit.
|
||||||
|
|
||||||
|
Ont été vérifiés statiquement :
|
||||||
|
|
||||||
|
- syntaxe/transpilation TypeScript des sources modifiées ;
|
||||||
|
- absence de `?`, `unwrap`, `expect`, `panic` explicite et `unsafe` dans le Rust applicatif ;
|
||||||
|
- absence de lecture directe `std::env::var*` ;
|
||||||
|
- absence d'import direct `tracing` dans l'application ;
|
||||||
|
- lignes Rust <= 160 colonnes ;
|
||||||
|
- présence commentée des deux variables dans `.env.example` ;
|
||||||
|
- structure `tw_splash` / `tw_main` ;
|
||||||
|
- ordre one-shot du lifecycle.
|
||||||
|
|
||||||
|
## 17. Suite
|
||||||
|
|
||||||
|
Après validation, `pre.009` introduira **Documents + diagnostics** à partir de l'inventaire public du registre et de la réparation raw Config déjà disponibles. DataTables sera ajouté dans cette tranche si le premier tableau Documents requiert effectivement tri, filtrage ou sélection.
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md -->
|
<!-- file: docs/plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md -->
|
||||||
<!-- version: 7 -->
|
<!-- version: 8 -->
|
||||||
|
|
||||||
# Plan `0.1.4` — `ksp-app-config-desk`
|
# Plan `0.1.4` — `ksp-app-config-desk`
|
||||||
|
|
||||||
@@ -687,6 +687,8 @@ Le bouton `Log` appelle une commande Tauri dédiée. L'émission backend utilise
|
|||||||
|
|
||||||
En parallèle, le `frontend_log.ts` commun reprend/refond le mécanisme bot3 pour les logs techniques du frontend et le bridge console. Il passe par une commande `emit_frontend_log` séparée et des targets frontend whitelistés. Les deux chemins peuvent partager le même service d'émission Rust sans confondre leurs DTO et leurs responsabilités.
|
En parallèle, le `frontend_log.ts` commun reprend/refond le mécanisme bot3 pour les logs techniques du frontend et le bridge console. Il passe par une commande `emit_frontend_log` séparée et des targets frontend whitelistés. Les deux chemins peuvent partager le même service d'émission Rust sans confondre leurs DTO et leurs responsabilités.
|
||||||
|
|
||||||
|
Le shell applique également une instrumentation frontend systématique : clics et transitions d’état significatives en `debug`; rendu, remplacement de sections, événements fréquents et étapes techniques en `trace`. Les wrappers IPC journalisent le début et la fin des chargements sans recopier les arguments ou payloads susceptibles de devenir sensibles. Cette convention devient le modèle des futures applications desk KSP.
|
||||||
|
|
||||||
## 8. Matrice commandes Tauri / services / APIs KSP
|
## 8. Matrice commandes Tauri / services / APIs KSP
|
||||||
|
|
||||||
Les noms ci-dessous sont les noms fonctionnels cibles ; ils pourront être normalisés avant implémentation, mais leurs responsabilités sont fixées.
|
Les noms ci-dessous sont les noms fonctionnels cibles ; ils pourront être normalisés avant implémentation, mais leurs responsabilités sont fixées.
|
||||||
@@ -939,13 +941,16 @@ fade duration
|
|||||||
close delay si techniquement distinct
|
close delay si techniquement distinct
|
||||||
```
|
```
|
||||||
|
|
||||||
La ou les clés Config/.env exactes **ne sont pas figées dans `pre.001`**, conformément à la règle qui demande de les nommer lors de leur première utilisation concrète. Elles devront :
|
`pre.008` fige les deux clés communes nécessaires au lifecycle de référence :
|
||||||
|
|
||||||
- utiliser `KSP_*` ou `KSP_PUBLIC_*` selon exposition ;
|
```text
|
||||||
- être lues uniquement via Config ;
|
KSP_DESK_SPLASH_MINIMUM_MS=1200
|
||||||
- être ajoutées à `.env.example` avec commentaire dans le même delta ;
|
KSP_DESK_SPLASH_FADE_MS=300
|
||||||
- posséder fallback et bornes de validation ;
|
```
|
||||||
- être transmises au frontend uniquement si celui-ci a réellement besoin de la valeur.
|
|
||||||
|
Elles sont résolues uniquement via `ConfigEnvironment` avec priorité process > `.env` > fallback. `KSP_DESK_SPLASH_MINIMUM_MS` est bornée à 60 000 ms et `KSP_DESK_SPLASH_FADE_MS` à 10 000 ms. Une valeur invalide ne doit pas rendre Config Desk inutilisable : l’application conserve les timings de fallback en mémoire, journalise seulement domaine/code de l’erreur et laisse la future surface `.env` permettre la réparation.
|
||||||
|
|
||||||
|
Le frontend ne reçoit que la durée d’animation réellement nécessaire dans les ordres du splash; la durée minimale reste backend. Aucun close delay distinct n’est nécessaire : Rust attend la durée de fade-out avant d’afficher/focaliser `main` puis de détruire `splash`. Les variables sont ajoutées à `.env.example` dans `pre.008`.
|
||||||
|
|
||||||
## 14. Présentation et Markdown
|
## 14. Présentation et Markdown
|
||||||
|
|
||||||
@@ -1174,10 +1179,12 @@ pre.007 frontend logging commun
|
|||||||
- intégration console plugin compatible seulement
|
- intégration console plugin compatible seulement
|
||||||
|
|
||||||
pre.008 shell main + splash de référence
|
pre.008 shell main + splash de référence
|
||||||
|
- tw_splash + tw_main
|
||||||
- lifecycle splash configurable
|
- lifecycle splash configurable
|
||||||
- variable(s) Config/.env + .env.example même delta
|
- KSP_DESK_SPLASH_MINIMUM_MS / KSP_DESK_SPLASH_FADE_MS + .env.example
|
||||||
- navigation monofenêtre
|
- navigation monofenêtre
|
||||||
- helpers communs show/focus
|
- helpers show/focus/destroy
|
||||||
|
- instrumentation debug/trace des interactions et mutations frontend
|
||||||
|
|
||||||
pre.009 Documents + diagnostics
|
pre.009 Documents + diagnostics
|
||||||
- inventaire générique par registre
|
- inventaire générique par registre
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/rules/RULES_KSP.md -->
|
<!-- file: docs/rules/RULES_KSP.md -->
|
||||||
<!-- version: 24 -->
|
<!-- version: 25 -->
|
||||||
|
|
||||||
# Règles spécifiques à KSP
|
# Règles spécifiques à KSP
|
||||||
|
|
||||||
@@ -219,6 +219,7 @@
|
|||||||
- **KSP-APP-024** — Chaque application desk Tauri KSP possède un couple de ports Vite/HMR exclusif. L'allocation commence à `1430/1431` pour `ksp-app-config-desk` puis progresse par paires (`1432/1433`, `1434/1435`, etc.). Le port Vite est strict afin qu'une collision échoue explicitement au lieu de sélectionner silencieusement un autre port.
|
- **KSP-APP-024** — Chaque application desk Tauri KSP possède un couple de ports Vite/HMR exclusif. L'allocation commence à `1430/1431` pour `ksp-app-config-desk` puis progresse par paires (`1432/1433`, `1434/1435`, etc.). Le port Vite est strict afin qu'une collision échoue explicitement au lieu de sélectionner silencieusement un autre port.
|
||||||
- **KSP-APP-025** — Dans `package.json`, les bibliothèques consommées par le bundle applicatif appartiennent à `dependencies`; les outils de build/développement et paquets `@types/*` appartiennent à `devDependencies`. npm est utilisé directement uniquement pour installer ou mettre à jour ces dépendances. Le cycle normal de développement/build passe par Tauri, qui déclenche les scripts npm configurés via `beforeDevCommand`/`beforeBuildCommand`; les lockfiles frontend restent non versionnés.
|
- **KSP-APP-025** — Dans `package.json`, les bibliothèques consommées par le bundle applicatif appartiennent à `dependencies`; les outils de build/développement et paquets `@types/*` appartiennent à `devDependencies`. npm est utilisé directement uniquement pour installer ou mettre à jour ces dépendances. Le cycle normal de développement/build passe par Tauri, qui déclenche les scripts npm configurés via `beforeDevCommand`/`beforeBuildCommand`; les lockfiles frontend restent non versionnés.
|
||||||
- **KSP-APP-026** — KSP étant un workspace Rust multi-app, les commandes Tauri lancées depuis la racine sélectionnent explicitement la configuration de l'application avec `-c crates/<app>/tauri.conf.json` (par exemple `cargo tauri dev -c crates/ksp-app-config-desk/tauri.conf.json`). Après une installation npm effectuée depuis `crates/<app>`, l'opérateur revient à la racine du workspace avant le cycle Cargo/Tauri.
|
- **KSP-APP-026** — KSP étant un workspace Rust multi-app, les commandes Tauri lancées depuis la racine sélectionnent explicitement la configuration de l'application avec `-c crates/<app>/tauri.conf.json` (par exemple `cargo tauri dev -c crates/ksp-app-config-desk/tauri.conf.json`). Après une installation npm effectuée depuis `crates/<app>`, l'opérateur revient à la racine du workspace avant le cycle Cargo/Tauri.
|
||||||
|
- **KSP-APP-027** — Les applications Tauri KSP instrumentent systématiquement le comportement frontend via le bridge Logging commun : actions utilisateur et transitions d’état significatives en `debug`, événements techniques fins, rendu/remplacement de sections et étapes fréquentes en `trace`. Les chargements/rafraîchissements de données sont tracés au début et à la fin sans journaliser les payloads sensibles. Cette instrumentation doit permettre de reconstruire le déroulement frontend sans dépendre uniquement de l’état visuel.
|
||||||
|
|
||||||
## Data plane / control plane
|
## Data plane / control plane
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user