v0.2.6-pre.014
This commit is contained in:
14
.env.example
14
.env.example
@@ -1,5 +1,5 @@
|
||||
# file: .env.example
|
||||
# version: 3
|
||||
# version: 4
|
||||
|
||||
# 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.
|
||||
@@ -11,7 +11,8 @@ KSP_WALLETS_DIRECTORY=wallets
|
||||
|
||||
# Optional Wallet Desk password candidates are introduced by the later unlock tranche.
|
||||
# Additional KSP_SECRET_WALLET_PASS_<LABEL> variables may be defined locally; never commit real password values.
|
||||
# KSP_SECRET_WALLET_PASS_01=
|
||||
# KSP_SECRET_WALLET_PASS_WALLET_01='test'
|
||||
# KSP_SECRET_WALLET_PASS_01='test'
|
||||
|
||||
# Optional public Solana Devnet HTTP endpoint override used by config/std.transport.json.
|
||||
# The committed Transport document falls back to https://api.devnet.solana.com when this variable is absent.
|
||||
@@ -25,8 +26,11 @@ KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL=https://api.mainnet-beta.solana.com
|
||||
# Keep provider credentials in a KSP_SECRET_* variable; do not copy a real credential-bearing URL into committed JSON.
|
||||
# KSP_SECRET_SOLANA_HTTP_URL=https://provider.example/?api-key=replace-me
|
||||
|
||||
# Minimum time in milliseconds that a KSP desk splash remains visible after its frontend is ready.
|
||||
# Fade-in duration in milliseconds used by the common KSP desk splash lifecycle.
|
||||
KSP_DESK_SPLASH_FADE_IN_MS=300
|
||||
|
||||
# Minimum fully-visible time in milliseconds used by the common KSP desk splash lifecycle.
|
||||
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
|
||||
# Fade-out duration in milliseconds used by the common KSP desk splash lifecycle.
|
||||
KSP_DESK_SPLASH_FADE_OUT_MS=300
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# file: Cargo.toml
|
||||
# version: 185
|
||||
# version: 186
|
||||
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["crates/ksp-app-config-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-wallet-lib"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.2.6-pre.13.fix.2"
|
||||
version = "0.2.6-pre.14"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||
|
||||
19
ROADMAP.md
19
ROADMAP.md
@@ -1,5 +1,5 @@
|
||||
<!-- file: ROADMAP.md -->
|
||||
<!-- version: 75 -->
|
||||
<!-- version: 76 -->
|
||||
|
||||
# Roadmap KSP
|
||||
|
||||
@@ -50,14 +50,15 @@ Le roadmap décrit les objectifs à atteindre et les grandes étapes prévues. U
|
||||
- [X] `0.2.3` — HTTP Transactions stable : 11/11 wrappers typés publiés, classification `8 Read / 2 WriteSubmission / 1 Simulation`, no-resend ambigu prouvé pour les write submissions, `KSP-TRANSPORT-007` réaudité conforme sur les 37 wrappers HTTP courants, graphes Cargo et deux smokes Devnet validés ; `0.2.4` reprend les 15 Blocks/Economics restants.
|
||||
- [X] `0.2.4` — HTTP Blocks + Economics stable : 15/15 wrappers `V0_2_4` publiés, surface typed complète à 52/52 méthodes courantes, 14/14 historiques conservées, réaudit SIMD/inventaire final et `KSP-TRANSPORT-007` global validés ; deux smokes Devnet passés avant publication.
|
||||
- [X] `0.2.5` — Wallet foundation stable : `.kspwallet` V1, VIEW/OWNER indépendants, Argon2id/XChaCha20-Poly1305, autorité Ed25519 OWNER, persistence no-clobber, signature, administration/rotations/révocation VIEW forte, import/export Solana CLI JSON + Base58, canaris adversariaux, interop externe et documentation durable publiés. La clôture `pre.010-fix.001`–`fix.003` ajoute `ed25519-dalek 3.0.0` direct, normalise le Rust workspace et installe l’audit structurel Python complémentaire à rustfmt/Clippy. `Pubkey` reste via `ksp-core-lib`, la keypair reste encapsulée dans Wallet et Config/Transport/ExecutionPolicy/Store/Tauri restent hors Wallet.
|
||||
- [/] `0.2.6` — `ksp-app-wallet-desk` : composition Config + Wallet + HTTP, inventory/création/unlock/balance/import acquis ; administration OWNER, rotations, lifecycle VIEW, export, intégration/sécurité, polish desktop puis documentation finale restent à clôturer. Plan détaillé : `docs/plans/013-V0_2_6_WALLET_DESK_PLAN.md`.
|
||||
- [ ] `0.2.7` — Étendre `ksp-onchain-transport-lib` au WebSocket Solana standard complet ; permettre plusieurs sessions sur une même URL sans imposer encore un pool automatique complexe.
|
||||
- [ ] `0.2.8` — Ajouter Helius LaserStream WebSocket comme extension du moteur WebSocket standard, sans duplication de client.
|
||||
- [ ] `0.2.9` — Ajouter une première fondation Yellowstone gRPC standard/provider-neutral ; dimensionner la surface exacte à `pre.001` selon la documentation normative actuelle.
|
||||
- [ ] `0.2.10` — Introduire `ksp-offchain-transport-lib` avec un premier lecteur de prix, au minimum SOL/USD et SOL/EUR.
|
||||
- [ ] `0.2.11` — Introduire une petite application desk de visualisation/validation des prix offchain, puis intégrer cette capacité dans `ksp-app-wallet-desk` sans dupliquer la logique de récupération/normalisation possédée par le composant spécialisé.
|
||||
- [ ] `0.2.12` — Introduire la première surface de `ksp-interface-lib`, comprenant une API wire publique utilisable par les implémentations officielles et externes.
|
||||
- [ ] `0.2.13` — Introduire `ksp-program-api` comme premier contrat Program extensible, sans imposer encore `ksp-program-lib` complet.
|
||||
- [/] `0.2.6` — `ksp-app-wallet-desk` : surface fonctionnelle Wallet Desk acquise ; `pre.014` harmonise le gabarit/splash/Tauri multi-app puis `pre.015` clôture documentation et build final. Plan détaillé : `docs/plans/013-V0_2_6_WALLET_DESK_PLAN.md`.
|
||||
- [ ] `0.2.7` — Durcir la persistence `.kspwallet` dans `ksp-wallet-lib` avec un conteneur binaire versionné rétrocompatible ; conserver les semantics cryptographiques V1 et rejouer Wallet Desk comme consommateur sans changement fonctionnel attendu.
|
||||
- [ ] `0.2.8` — Étendre `ksp-onchain-transport-lib` au WebSocket Solana standard complet ; permettre plusieurs sessions sur une même URL sans imposer encore un pool automatique complexe.
|
||||
- [ ] `0.2.9` — Ajouter Helius LaserStream WebSocket comme extension du moteur WebSocket standard, sans duplication de client.
|
||||
- [ ] `0.2.10` — Ajouter une première fondation Yellowstone gRPC standard/provider-neutral ; dimensionner la surface exacte à `pre.001` selon la documentation normative actuelle.
|
||||
- [ ] `0.2.11` — Introduire `ksp-offchain-transport-lib` avec un premier lecteur de prix, au minimum SOL/USD et SOL/EUR.
|
||||
- [ ] `0.2.12` — Introduire une petite application desk de visualisation/validation des prix offchain, puis intégrer cette capacité dans `ksp-app-wallet-desk` sans dupliquer la logique de récupération/normalisation possédée par le composant spécialisé.
|
||||
- [ ] `0.2.13` — Introduire la première surface de `ksp-interface-lib`, comprenant une API wire publique utilisable par les implémentations officielles et externes.
|
||||
- [ ] `0.2.14` — Introduire `ksp-program-api` comme premier contrat Program extensible, sans imposer encore `ksp-program-lib` complet.
|
||||
|
||||
### Règles Transport pour toute la série
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-app-config-desk/README.md -->
|
||||
<!-- version: 24 -->
|
||||
<!-- version: 25 -->
|
||||
|
||||
# `ksp-app-config-desk`
|
||||
|
||||
@@ -82,7 +82,7 @@ Les DTO Rust applicatifs restent la source de vérité et les bindings généré
|
||||
|
||||
Le frontend possède désormais `shell_registry.ts`. Le registre décrit les vues du shell et les adapters d'éditeurs spécialisés par `file_id`. Le panneau Documents reste générique ; lorsqu'un document possède un adapter enregistré, **Ouvrir l'éditeur spécialisé** déclenche la navigation par événement de registre. `cfg.std.logging -> Logging` est le premier adapter. Ajouter un futur éditeur ne demande donc pas de réécrire le moteur Documents ni la logique générale d'activation des panneaux.
|
||||
|
||||
Deux audits d'intégration applicatifs complètent les audits Config/Logging existants : contrat Tauri/Vite/package scripts, centralisation des commandes Tauri, absence de `tauri-plugin-log`, interdiction des dialogues navigateur natifs et absence de stockage persistant pour le reveal Secret. Le script frontend `npm run check` exécute uniquement `tsc --noEmit`. Le build Vite de production n’est pas lancé séparément : il appartient au `beforeBuildCommand` de `cargo tauri build`, qui reste la dernière validation après tous les contrôles Rust, le type-check frontend et le parcours fonctionnel `tauri dev`.
|
||||
Deux audits d'intégration applicatifs complètent les audits Config/Logging existants : contrat Tauri/Vite/package scripts, centralisation des commandes Tauri, absence de `tauri-plugin-log`, interdiction des dialogues navigateur natifs et absence de stockage persistant pour le reveal Secret. Aucun script frontend standalone n’est lancé par l’opérateur : Tauri possède le cycle dev/build et déclenche les hooks npm de la crate. Le build Vite/TypeScript de production appartient au `beforeBuildCommand` du `cargo tauri build` crate-local, qui reste la dernière validation après tous les contrôles Rust et le parcours fonctionnel `cargo tauri dev`.
|
||||
|
||||
## Artefacts frontend
|
||||
|
||||
@@ -134,7 +134,7 @@ Rust valide le niveau et le `targetId`, choisit un callsite statique puis émet
|
||||
|
||||
## Bootstrap Config et Logging
|
||||
|
||||
Au démarrage, l'application construit `ConfigBootstrapOptions`, `ConfigFileRegistry`, `ConfigDocumentEngine` et `ConfigManagement` à partir des arguments complets du processus. En développement, le launcher normalise d'abord le current working directory Rust vers la racine du workspace : `cargo tauri dev -c ...` peut sinon lancer le binaire depuis la crate Tauri, ce qui ferait manquer les chemins Config relatifs `config/`, `config/schemas/` et le `.env` racine. Cette normalisation ne lit ni ne parse aucun fichier Config elle-même ; l'ownership reste intégralement dans `ksp-config-lib`. Elle tente ensuite de résoudre le profil Logging par défaut avec un snapshot `ConfigEnvironment` frais.
|
||||
Au démarrage, l'application construit `ConfigBootstrapOptions`, `ConfigFileRegistry`, `ConfigDocumentEngine` et `ConfigManagement` à partir des arguments complets du processus. En développement, le launcher normalise d'abord le current working directory Rust vers la racine du workspace : `cargo tauri dev` est volontairement lancé depuis la crate Tauri, ce qui ferait manquer les chemins Config relatifs `config/`, `config/schemas/` et le `.env` racine. Cette normalisation ne lit ni ne parse aucun fichier Config elle-même ; l'ownership reste intégralement dans `ksp-config-lib`. Elle tente ensuite de résoudre le profil Logging par défaut avec un snapshot `ConfigEnvironment` frais.
|
||||
|
||||
Si le document Logging, sa résolution environnementale ou l'initialisation du runtime configuré échoue avant installation du subscriber, Config Desk installe un fallback **transitoire** en mémoire : console stderr, niveau `Info`, aucun fichier, aucun span lifecycle. Ce fallback n'est jamais persisté et son diagnostic frontend est limité à `domain`, `code` et `message`.
|
||||
|
||||
@@ -146,7 +146,8 @@ Le `LoggingGuard` est conservé dans `AppState` et sert désormais au hot reload
|
||||
|
||||
```text
|
||||
KSP_DESK_SPLASH_MINIMUM_MS=1200
|
||||
KSP_DESK_SPLASH_FADE_MS=300
|
||||
KSP_DESK_SPLASH_FADE_IN_MS=300
|
||||
KSP_DESK_SPLASH_FADE_OUT_MS=300
|
||||
```
|
||||
|
||||
Après la readiness frontend, Rust émet `fade_in`, attend la durée minimale, émet `fade_out`, attend la durée de fade puis `tw_main.rs` affiche/focalise `main` avant destruction du splash. Le backend journalise en `debug` les valeurs résolues, leur provenance (`process`, `dotenv` ou `fallback`), chaque attente réellement observée et la durée totale. Avec `12000/3000`, la durée backend attendue entre readiness et activation de `main` est donc d'environ `15000 ms`. 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-app-config-desk/USAGE.md -->
|
||||
<!-- version: 24 -->
|
||||
<!-- version: 25 -->
|
||||
|
||||
# Utilisation de `ksp-app-config-desk`
|
||||
|
||||
@@ -31,7 +31,7 @@ cd ../../
|
||||
Le frontend n'est pas lancé directement avec npm. KSP est un workspace Rust multi-app : depuis la racine du workspace, la configuration Tauri de l'application doit être sélectionnée explicitement :
|
||||
|
||||
```bash
|
||||
cargo tauri dev -c crates/ksp-app-config-desk/tauri.conf.json
|
||||
(cd crates/ksp-app-config-desk && cargo tauri dev)
|
||||
```
|
||||
|
||||
Tauri exécute alors le hook :
|
||||
@@ -54,7 +54,7 @@ Si le port HTTP est déjà occupé, le démarrage doit échouer au lieu de séle
|
||||
Le build de l'application passe également par Tauri. Depuis la racine du workspace :
|
||||
|
||||
```bash
|
||||
cargo tauri build -c crates/ksp-app-config-desk/tauri.conf.json
|
||||
(cd crates/ksp-app-config-desk && cargo tauri build)
|
||||
```
|
||||
|
||||
`beforeBuildCommand` déclenche :
|
||||
@@ -112,12 +112,14 @@ Les variables communes sont :
|
||||
|
||||
```text
|
||||
KSP_DESK_SPLASH_MINIMUM_MS=1200
|
||||
KSP_DESK_SPLASH_FADE_MS=300
|
||||
KSP_DESK_SPLASH_FADE_IN_MS=300
|
||||
KSP_DESK_SPLASH_FADE_OUT_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. La durée minimale commence à cette readiness : Rust émet le fade-in, attend `minimum`, émet le fade-out, attend `fade`, puis active `main`. Ainsi `KSP_DESK_SPLASH_MINIMUM_MS=12000` et `KSP_DESK_SPLASH_FADE_MS=3000` donnent environ `15000 ms` de lifecycle backend. Des logs `debug` indiquent source des deux valeurs, attentes configurées/réelles et durée totale afin de vérifier ce contrat.
|
||||
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. La durée minimale commence à cette readiness : Rust émet le fade-in, attend `fade_in`, maintient ensuite le splash pleinement visible pendant `minimum`, émet le fade-out, attend `fade_out`, puis active `main`. Ainsi `KSP_DESK_SPLASH_MINIMUM_MS=12000` et `KSP_DESK_SPLASH_FADE_IN_MS=300
|
||||
KSP_DESK_SPLASH_FADE_OUT_MS=3000` donnent environ `15000 ms` de lifecycle backend. Des logs `debug` indiquent source des trois valeurs, attentes configurées/réelles et durée totale afin de vérifier ce contrat.
|
||||
|
||||
## Navigation principale
|
||||
|
||||
@@ -311,24 +313,18 @@ cargo test -p ksp-config-lib
|
||||
cargo test -p ksp-logging-lib
|
||||
```
|
||||
|
||||
Puis effectuer uniquement le type-check frontend depuis `crates/ksp-app-config-desk` :
|
||||
Aucun script npm de contrôle/dev/build n’est lancé directement par l’opérateur. Tauri possède le cycle frontend et déclenche les hooks npm de la crate.
|
||||
|
||||
Effectuer ensuite le contrôle fonctionnel depuis la racine en lançant Tauri dans la crate ciblée :
|
||||
|
||||
```bash
|
||||
npm run check
|
||||
```
|
||||
|
||||
`npm run check` exécute seulement `tsc --noEmit`. Il ne lance pas `vite build` : le build frontend de production est déjà la responsabilité de Tauri via `beforeBuildCommand = npm run build`.
|
||||
|
||||
Revenir ensuite à la racine et effectuer le contrôle fonctionnel :
|
||||
|
||||
```bash
|
||||
cargo tauri dev -c crates/ksp-app-config-desk/tauri.conf.json
|
||||
(cd crates/ksp-app-config-desk && cargo tauri dev)
|
||||
```
|
||||
|
||||
**Seulement lorsque tous les contrôles précédents sont validés**, exécuter en toute dernière opération :
|
||||
|
||||
```bash
|
||||
cargo tauri build -c crates/ksp-app-config-desk/tauri.conf.json
|
||||
(cd crates/ksp-app-config-desk && cargo tauri build)
|
||||
```
|
||||
|
||||
Cette dernière commande déclenche elle-même `npm run build`, donc `tsc && vite build`, avant le build/bundling Tauri. Aucun build Vite séparé n’est nécessaire dans le cycle de validation.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-app-config-desk/frontend/main.html -->
|
||||
<!-- version: 16 -->
|
||||
<!-- version: 17 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -21,20 +21,25 @@
|
||||
<span class="mx-2 fs-5 text-body-secondary" aria-hidden="true">—</span>
|
||||
<span id="headerViewTitle" class="fs-5 text-body text-nowrap">Vue d'ensemble</span>
|
||||
</div>
|
||||
<div class="app-nav nav nav-pills flex-nowrap gap-1 ms-auto" 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>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="app-main">
|
||||
<div class="app-scrollable" data-simplebar>
|
||||
<div class="container-fluid px-4 py-4">
|
||||
<main class="app-main container-fluid p-0">
|
||||
<div class="row g-0 h-100">
|
||||
<aside class="col-auto app-sidebar border-end bg-body-tertiary">
|
||||
<div class="app-sidebar-scroll h-100" data-simplebar>
|
||||
<nav class="nav nav-pills flex-column gap-1 p-3" aria-label="Navigation Config Desk">
|
||||
<button class="nav-link text-start active" type="button" data-view="overview"><i class="fa-solid fa-gauge-high me-2" aria-hidden="true"></i>Vue d'ensemble</button>
|
||||
<button class="nav-link text-start" type="button" data-view="documents"><i class="fa-solid fa-file-code me-2" aria-hidden="true"></i>Documents</button>
|
||||
<button class="nav-link text-start" type="button" data-view="profiles"><i class="fa-solid fa-layer-group me-2" aria-hidden="true"></i>Profils</button>
|
||||
<button class="nav-link text-start" type="button" data-view="environment"><i class="fa-solid fa-terminal me-2" aria-hidden="true"></i>Environnement / .env</button>
|
||||
<button class="nav-link text-start" type="button" data-view="logging"><i class="fa-solid fa-list-ul me-2" aria-hidden="true"></i>Logging</button>
|
||||
</nav>
|
||||
</div>
|
||||
</aside>
|
||||
<section class="col app-content app-scrollable h-100" data-simplebar>
|
||||
<div class="container-fluid px-4 py-4">
|
||||
<div class="card app-shell-card mx-auto shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<h1 id="viewTitle" class="h3 mb-2">Vue d'ensemble</h1>
|
||||
@@ -547,7 +552,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// file: crates/ksp-app-config-desk/frontend/sass/_app.scss
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
$app-header-height: 72px;
|
||||
$app-footer-height: 42px;
|
||||
$app-sidebar-width: 250px;
|
||||
|
||||
html,
|
||||
body {
|
||||
@@ -59,13 +60,19 @@ body {
|
||||
font-family: var(--bs-font-monospace);
|
||||
}
|
||||
|
||||
.app-nav {
|
||||
min-width: 0;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: thin;
|
||||
.app-sidebar {
|
||||
width: $app-sidebar-width;
|
||||
min-width: $app-sidebar-width;
|
||||
max-width: $app-sidebar-width;
|
||||
}
|
||||
|
||||
.app-nav-button {
|
||||
.app-sidebar-scroll,
|
||||
.app-content {
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.app-sidebar .nav-link {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-config-desk/frontend/sass/splash.scss
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
@font-face {
|
||||
font-family: "Dos Amazigh";
|
||||
@@ -9,13 +9,23 @@
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
display: flex;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
@@ -25,8 +35,12 @@ body {
|
||||
#splash-container {
|
||||
position: relative;
|
||||
width: 960px;
|
||||
max-width: 100%;
|
||||
height: 637px;
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
will-change: opacity;
|
||||
}
|
||||
|
||||
#splash-image {
|
||||
@@ -35,6 +49,11 @@ body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
object-fit: contain;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#app-name {
|
||||
@@ -49,17 +68,70 @@ body {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
text-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#splash-status {
|
||||
#debug-info {
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
bottom: 1rem;
|
||||
left: 1rem;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 3;
|
||||
padding: 0.55rem 0.75rem;
|
||||
width: 55%;
|
||||
max-height: 30%;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
background: rgba(0, 0, 0, 0.18);
|
||||
font-family: monospace;
|
||||
font-size: 0.82rem;
|
||||
font-size: 12px;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
#messages-container {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 3;
|
||||
max-height: 30%;
|
||||
overflow-y: auto;
|
||||
padding: 10px 10px 36px;
|
||||
background: rgba(0, 0, 0, 0.18);
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
#debug-info::-webkit-scrollbar,
|
||||
#messages-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.splash-message {
|
||||
margin-bottom: 8px;
|
||||
padding: 6px 10px;
|
||||
border-radius: 4px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
animation: splashMessageFadeIn 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.splash-message.info { background: rgba(33, 150, 243, 0.22); }
|
||||
.splash-message.warning { background: rgba(255, 152, 0, 0.22); }
|
||||
.splash-message.error { background: rgba(244, 67, 54, 0.22); }
|
||||
.splash-message.success { background: rgba(76, 175, 80, 0.22); }
|
||||
|
||||
@keyframes splashMessageFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-app-config-desk/frontend/splash.html -->
|
||||
<!-- version: 1 -->
|
||||
<!-- version: 2 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -11,10 +11,11 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="splash-container">
|
||||
<div id="splash-container" style="opacity: 0;">
|
||||
<img id="splash-image" src="imgs/splash.png" alt="Chargement de KSP Config Desk">
|
||||
<div id="app-name">KSP</div>
|
||||
<div id="splash-status" aria-live="polite">Chargement du shell frontend...</div>
|
||||
<div id="app-name">Config Desk</div>
|
||||
<div id="debug-info" aria-live="off" hidden></div>
|
||||
<div id="messages-container" aria-live="polite"></div>
|
||||
</div>
|
||||
<script type="module" src="ts/splash.ts" defer></script>
|
||||
</body>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-config-desk/frontend/ts/splash.ts
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
@@ -9,21 +9,14 @@ import { invokeKsp } from "./invoke";
|
||||
|
||||
installFrontendConsoleBridge("splash");
|
||||
|
||||
const defaultFadeDurationMs = 300;
|
||||
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(value: number): number {
|
||||
return value < 0.5 ? 2 * value * value : 1 - Math.pow(-2 * value + 2, 2) / 2;
|
||||
}
|
||||
|
||||
function easeInOut(progress: number): number {
|
||||
if (progress < 0.5) {
|
||||
return 2 * progress * progress;
|
||||
}
|
||||
return 1 - Math.pow(-2 * progress + 2, 2) / 2;
|
||||
function normalizeDurationMs(value: number | null): number {
|
||||
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
|
||||
}
|
||||
|
||||
async function animateOpacity(element: HTMLElement, fromOpacity: number, toOpacity: number, durationMs: number): Promise<void> {
|
||||
@@ -40,7 +33,7 @@ async function animateOpacity(element: HTMLElement, fromOpacity: number, toOpaci
|
||||
const opacityDelta = toOpacity - fromOpacity;
|
||||
const updateOpacity = (currentTime: number): void => {
|
||||
const elapsedMs = currentTime - startedAt;
|
||||
const rawProgress = Math.min(elapsedMs / durationMs, 1);
|
||||
const rawProgress = durationMs === 0 ? 1 : Math.min(elapsedMs / durationMs, 1);
|
||||
element.style.opacity = (fromOpacity + opacityDelta * easeInOut(rawProgress)).toString();
|
||||
if (rawProgress >= 1) {
|
||||
element.style.opacity = toOpacity.toString();
|
||||
@@ -56,21 +49,46 @@ async function animateOpacity(element: HTMLElement, fromOpacity: number, toOpaci
|
||||
frontendTrace("splash", "Splash opacity animation completed", { toOpacity, durationMs });
|
||||
}
|
||||
|
||||
function replaceStatus(message: string | null): void {
|
||||
if (!message) {
|
||||
function scrollToLatest(element: HTMLElement): void {
|
||||
element.scrollTop = element.scrollHeight;
|
||||
}
|
||||
|
||||
function addMessage(message: string, status: string | null): void {
|
||||
const container = document.querySelector<HTMLElement>("#messages-container");
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
const status = document.querySelector<HTMLElement>("#splash-status");
|
||||
if (status) {
|
||||
status.textContent = message;
|
||||
frontendTrace("splash", "Splash status replaced", { message });
|
||||
const line = document.createElement("div");
|
||||
const safeStatus = status === "warning" || status === "error" || status === "success" ? status : "info";
|
||||
line.className = `splash-message ${safeStatus}`;
|
||||
line.textContent = message;
|
||||
container.appendChild(line);
|
||||
scrollToLatest(container);
|
||||
}
|
||||
|
||||
function addDebugMessage(message: string): void {
|
||||
const container = document.querySelector<HTMLElement>("#debug-info");
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
container.hidden = false;
|
||||
const line = document.createElement("div");
|
||||
line.textContent = `${new Date().toLocaleTimeString()}: ${message}`;
|
||||
container.appendChild(line);
|
||||
scrollToLatest(container);
|
||||
}
|
||||
|
||||
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 (order.action === "add_message" && order.message) {
|
||||
addMessage(order.message, order.status);
|
||||
return;
|
||||
}
|
||||
if (order.action === "add_debug" && order.message) {
|
||||
addDebugMessage(order.message);
|
||||
return;
|
||||
}
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
@@ -99,7 +117,7 @@ async function initializeSplash(): Promise<void> {
|
||||
try {
|
||||
await invokeKsp<void>("splash", "splash_frontend_ready");
|
||||
} catch {
|
||||
replaceStatus("Le lifecycle du splash n'a pas pu démarrer.");
|
||||
addMessage("Le lifecycle du splash n'a pas pu démarrer.", "error");
|
||||
if (container) {
|
||||
container.style.opacity = "1";
|
||||
container.style.willChange = "auto";
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
{
|
||||
"name": "ksp-app-config-desk",
|
||||
"private": true,
|
||||
"version": "0.1.4",
|
||||
"version": "0.2.6-pre.14",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"tauri": "tauri",
|
||||
"check": "tsc --noEmit"
|
||||
"build": "tsc && vite build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fltsci/tauri-plugin-tracing": "^0.3",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-config-desk/src/app_state.rs
|
||||
// version: 8
|
||||
// version: 9
|
||||
|
||||
//! Shared backend state owned by the Tauri application.
|
||||
|
||||
@@ -48,8 +48,10 @@ impl AppState {
|
||||
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||
minimum_ms = splash_settings.minimum_ms(),
|
||||
minimum_source = splash_settings.minimum_source(),
|
||||
fade_ms = splash_settings.fade_ms(),
|
||||
fade_source = splash_settings.fade_source(),
|
||||
fade_in_ms = splash_settings.fade_in_ms(),
|
||||
fade_in_source = splash_settings.fade_in_source(),
|
||||
fade_out_ms = splash_settings.fade_out_ms(),
|
||||
fade_out_source = splash_settings.fade_out_source(),
|
||||
expected_backend_lifecycle_ms = splash_settings.expected_backend_lifecycle_ms(),
|
||||
"resolved Config Desk splash timings"
|
||||
);
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
// file: crates/ksp-app-config-desk/src/splash.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
//! Common splash settings and frontend event contracts for Config Desk.
|
||||
|
||||
use ts_rs::TS; // rust-rules: trait-import
|
||||
|
||||
const DEFAULT_SPLASH_FADE_MS: u32 = 300;
|
||||
const DEFAULT_SPLASH_FADE_IN_MS: u32 = 300;
|
||||
const DEFAULT_SPLASH_FADE_OUT_MS: u32 = 300;
|
||||
const DEFAULT_SPLASH_MINIMUM_MS: u64 = 1200;
|
||||
const ENV_SPLASH_FADE_MS: &str = "KSP_DESK_SPLASH_FADE_MS";
|
||||
const ENV_SPLASH_FADE_IN_MS: &str = "KSP_DESK_SPLASH_FADE_IN_MS";
|
||||
const ENV_SPLASH_FADE_OUT_MS: &str = "KSP_DESK_SPLASH_FADE_OUT_MS";
|
||||
const ENV_SPLASH_MINIMUM_MS: &str = "KSP_DESK_SPLASH_MINIMUM_MS";
|
||||
const MAX_SPLASH_FADE_MS: u32 = 10_000;
|
||||
const MAX_SPLASH_MINIMUM_MS: u64 = 60_000;
|
||||
@@ -15,10 +17,12 @@ const MAX_SPLASH_MINIMUM_MS: u64 = 60_000;
|
||||
/// Runtime timings used by the common desk splash lifecycle.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) struct SplashSettings {
|
||||
fade_in_ms: u32,
|
||||
minimum_ms: u64,
|
||||
fade_ms: u32,
|
||||
fade_out_ms: u32,
|
||||
fade_in_source: ksp_config_lib::ConfigEnvironmentSource,
|
||||
minimum_source: ksp_config_lib::ConfigEnvironmentSource,
|
||||
fade_source: ksp_config_lib::ConfigEnvironmentSource,
|
||||
fade_out_source: ksp_config_lib::ConfigEnvironmentSource,
|
||||
}
|
||||
|
||||
impl SplashSettings {
|
||||
@@ -29,13 +33,23 @@ impl SplashSettings {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let fade_in = environment.resolve_variable(ENV_SPLASH_FADE_IN_MS, std::option::Option::Some("300"));
|
||||
let fade_in = match fade_in {
|
||||
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 {
|
||||
let fade_out = environment.resolve_variable(ENV_SPLASH_FADE_OUT_MS, std::option::Option::Some("300"));
|
||||
let fade_out = match fade_out {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let fade_in_ms = parse_u32_setting(ENV_SPLASH_FADE_IN_MS, fade_in.value(), MAX_SPLASH_FADE_MS);
|
||||
let fade_in_ms = match fade_in_ms {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
@@ -44,35 +58,56 @@ impl SplashSettings {
|
||||
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 {
|
||||
let fade_out_ms = parse_u32_setting(ENV_SPLASH_FADE_OUT_MS, fade_out.value(), MAX_SPLASH_FADE_MS);
|
||||
let fade_out_ms = match fade_out_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, minimum_source: minimum.source(), fade_source: fade.source() });
|
||||
return std::result::Result::Ok(Self {
|
||||
fade_in_ms,
|
||||
minimum_ms,
|
||||
fade_out_ms,
|
||||
fade_in_source: fade_in.source(),
|
||||
minimum_source: minimum.source(),
|
||||
fade_out_source: fade_out.source(),
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns safe in-memory timings used when the managed environment cannot be resolved.
|
||||
#[must_use]
|
||||
pub(crate) const fn fallback() -> Self {
|
||||
return Self {
|
||||
fade_in_ms: DEFAULT_SPLASH_FADE_IN_MS,
|
||||
minimum_ms: DEFAULT_SPLASH_MINIMUM_MS,
|
||||
fade_ms: DEFAULT_SPLASH_FADE_MS,
|
||||
fade_out_ms: DEFAULT_SPLASH_FADE_OUT_MS,
|
||||
fade_in_source: ksp_config_lib::ConfigEnvironmentSource::Fallback,
|
||||
minimum_source: ksp_config_lib::ConfigEnvironmentSource::Fallback,
|
||||
fade_source: ksp_config_lib::ConfigEnvironmentSource::Fallback,
|
||||
fade_out_source: ksp_config_lib::ConfigEnvironmentSource::Fallback,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the minimum visible duration after splash frontend readiness.
|
||||
/// Returns the configured fade-in duration.
|
||||
#[must_use]
|
||||
pub(crate) const fn fade_in_ms(self) -> u32 {
|
||||
return self.fade_in_ms;
|
||||
}
|
||||
|
||||
/// Returns the minimum fully-visible splash duration.
|
||||
#[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.
|
||||
/// Returns the configured fade-out duration.
|
||||
#[must_use]
|
||||
pub(crate) const fn fade_ms(self) -> u32 {
|
||||
return self.fade_ms;
|
||||
pub(crate) const fn fade_out_ms(self) -> u32 {
|
||||
return self.fade_out_ms;
|
||||
}
|
||||
|
||||
/// Returns the safe provenance code for the fade-in duration.
|
||||
#[must_use]
|
||||
pub(crate) const fn fade_in_source(self) -> &'static str {
|
||||
return environment_source_code(self.fade_in_source);
|
||||
}
|
||||
|
||||
/// Returns the safe provenance code for the minimum duration.
|
||||
@@ -81,16 +116,16 @@ impl SplashSettings {
|
||||
return environment_source_code(self.minimum_source);
|
||||
}
|
||||
|
||||
/// Returns the safe provenance code for the fade duration.
|
||||
/// Returns the safe provenance code for the fade-out duration.
|
||||
#[must_use]
|
||||
pub(crate) const fn fade_source(self) -> &'static str {
|
||||
return environment_source_code(self.fade_source);
|
||||
pub(crate) const fn fade_out_source(self) -> &'static str {
|
||||
return environment_source_code(self.fade_out_source);
|
||||
}
|
||||
|
||||
/// Returns the minimum backend lifecycle duration from readiness until main activation.
|
||||
/// Returns the expected additive backend lifecycle from readiness through fade-out.
|
||||
#[must_use]
|
||||
pub(crate) fn expected_backend_lifecycle_ms(self) -> u64 {
|
||||
return self.minimum_ms + u64::from(self.fade_ms);
|
||||
return u64::from(self.fade_in_ms) + self.minimum_ms + u64::from(self.fade_out_ms);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +138,8 @@ pub(crate) struct SplashOrderDto {
|
||||
pub(crate) action: String,
|
||||
/// Optional status text.
|
||||
pub(crate) message: std::option::Option<String>,
|
||||
/// Optional message severity used by the bottom message feed.
|
||||
pub(crate) status: std::option::Option<String>,
|
||||
/// Optional animation duration in milliseconds.
|
||||
pub(crate) duration_ms: std::option::Option<u32>,
|
||||
}
|
||||
@@ -110,8 +147,13 @@ pub(crate) struct SplashOrderDto {
|
||||
impl SplashOrderDto {
|
||||
/// Creates a new `SplashOrderDto` value.
|
||||
#[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 };
|
||||
pub(crate) fn new(action: &str, message: std::option::Option<&str>, status: 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),
|
||||
status: status.map(std::string::ToString::to_string),
|
||||
duration_ms,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// file: crates/ksp-app-config-desk/src/tw_splash.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
//! Tauri-window lifecycle for the Config Desk splash window.
|
||||
|
||||
use tauri::Emitter; // rust-rules: trait-import
|
||||
use tauri::Manager; // rust-rules: trait-import
|
||||
|
||||
/// Crate-internal `WINDOW_LABEL_SPLASH` constant.
|
||||
/// Crate-internal splash window label.
|
||||
pub(crate) const WINDOW_LABEL_SPLASH: &str = "splash";
|
||||
|
||||
const SPLASH_EVENT_NAME: &str = "ksp-splash-order";
|
||||
@@ -23,7 +23,7 @@ pub(crate) fn require_splash_window(manager: &impl Manager<tauri::Wry>) -> ksp_c
|
||||
};
|
||||
}
|
||||
|
||||
/// Executes the crate-internal splash frontend ready service operation for the owning module.
|
||||
/// Starts the one-shot splash lifecycle after the splash frontend reports readiness.
|
||||
pub(crate) async fn splash_frontend_ready_service(
|
||||
app: tauri::AppHandle,
|
||||
invoking_window: tauri::WebviewWindow,
|
||||
@@ -36,12 +36,7 @@ pub(crate) async fn splash_frontend_ready_service(
|
||||
);
|
||||
}
|
||||
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"
|
||||
);
|
||||
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();
|
||||
@@ -50,49 +45,60 @@ pub(crate) async fn splash_frontend_ready_service(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||
window_label = WINDOW_LABEL_SPLASH,
|
||||
fade_in_ms = settings.fade_in_ms(),
|
||||
fade_in_source = settings.fade_in_source(),
|
||||
minimum_ms = settings.minimum_ms(),
|
||||
minimum_source = settings.minimum_source(),
|
||||
fade_ms = settings.fade_ms(),
|
||||
fade_source = settings.fade_source(),
|
||||
fade_out_ms = settings.fade_out_ms(),
|
||||
fade_out_source = settings.fade_out_source(),
|
||||
expected_backend_lifecycle_ms = settings.expected_backend_lifecycle_ms(),
|
||||
"splash frontend readiness accepted; starting splash to main lifecycle"
|
||||
);
|
||||
let fade_in = emit_order(
|
||||
let emitted = 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())),
|
||||
crate::SplashOrderDto::new("fade_in", std::option::Option::None, std::option::Option::None, std::option::Option::Some(settings.fade_in_ms())),
|
||||
);
|
||||
if let std::result::Result::Err(error) = fade_in {
|
||||
if let std::result::Result::Err(error) = emitted {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let minimum_wait_started = std::time::Instant::now();
|
||||
tokio::time::sleep(std::time::Duration::from_millis(settings.minimum_ms())).await;
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||
window_label = WINDOW_LABEL_SPLASH,
|
||||
configured_wait_ms = settings.minimum_ms(),
|
||||
actual_wait_ms = minimum_wait_started.elapsed().as_secs_f64() * 1000.0,
|
||||
lifecycle_elapsed_ms = lifecycle_started.elapsed().as_secs_f64() * 1000.0,
|
||||
"splash minimum wait completed"
|
||||
);
|
||||
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 {
|
||||
let emitted = emit_message(&invoking_window, "Initialisation de Config Desk...", "info");
|
||||
if let std::result::Result::Err(error) = emitted {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let fade_wait_started = std::time::Instant::now();
|
||||
tokio::time::sleep(std::time::Duration::from_millis(u64::from(settings.fade_ms()))).await;
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||
window_label = WINDOW_LABEL_SPLASH,
|
||||
configured_wait_ms = settings.fade_ms(),
|
||||
actual_wait_ms = fade_wait_started.elapsed().as_secs_f64() * 1000.0,
|
||||
lifecycle_elapsed_ms = lifecycle_started.elapsed().as_secs_f64() * 1000.0,
|
||||
"splash fade-out wait completed"
|
||||
let emitted = emit_debug(&invoking_window, "Listener frontend prêt; fade-in piloté par Rust.");
|
||||
if let std::result::Result::Err(error) = emitted {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
sleep_and_log(&invoking_window, "fade-in", u64::from(settings.fade_in_ms()), lifecycle_started).await;
|
||||
let emitted = emit_message(&invoking_window, "Configuration KSP chargée.", "success");
|
||||
if let std::result::Result::Err(error) = emitted {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let emitted = emit_debug(&invoking_window, "Configuration et provenance des timings résolues via ksp-config-lib.");
|
||||
if let std::result::Result::Err(error) = emitted {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let emitted = emit_message(&invoking_window, "Runtime Logging initialisé.", "success");
|
||||
if let std::result::Result::Err(error) = emitted {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let emitted = emit_message(&invoking_window, "Gestion Config prête.", "success");
|
||||
if let std::result::Result::Err(error) = emitted {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
sleep_and_log(&invoking_window, "minimum", settings.minimum_ms(), lifecycle_started).await;
|
||||
let emitted = emit_message(&invoking_window, "Initialisation terminée.", "success");
|
||||
if let std::result::Result::Err(error) = emitted {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let emitted = emit_order(
|
||||
&invoking_window,
|
||||
crate::SplashOrderDto::new("fade_out", std::option::Option::None, std::option::Option::None, std::option::Option::Some(settings.fade_out_ms())),
|
||||
);
|
||||
if let std::result::Result::Err(error) = emitted {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
sleep_and_log(&invoking_window, "fade-out", u64::from(settings.fade_out_ms()), lifecycle_started).await;
|
||||
let show_main = crate::show_main_window(&app);
|
||||
if let std::result::Result::Err(error) = show_main {
|
||||
return std::result::Result::Err(error);
|
||||
@@ -116,18 +122,45 @@ pub(crate) async fn splash_frontend_ready_service(
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn emit_message(window: &tauri::WebviewWindow, message: &str, status: &str) -> ksp_core_lib::Result<()> {
|
||||
return emit_order(
|
||||
window,
|
||||
crate::SplashOrderDto::new("add_message", std::option::Option::Some(message), std::option::Option::Some(status), std::option::Option::None),
|
||||
);
|
||||
}
|
||||
|
||||
fn emit_debug(window: &tauri::WebviewWindow, message: &str) -> ksp_core_lib::Result<()> {
|
||||
if !cfg!(debug_assertions) {
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
return emit_order(
|
||||
window,
|
||||
crate::SplashOrderDto::new("add_debug", std::option::Option::Some(message), std::option::Option::None, std::option::Option::None),
|
||||
);
|
||||
}
|
||||
|
||||
async fn sleep_and_log(window: &tauri::WebviewWindow, phase: &str, duration_ms: u64, lifecycle_started: std::time::Instant) {
|
||||
let wait_started = std::time::Instant::now();
|
||||
tokio::time::sleep(std::time::Duration::from_millis(duration_ms)).await;
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||
window_label = window.label(),
|
||||
phase,
|
||||
configured_wait_ms = duration_ms,
|
||||
actual_wait_ms = wait_started.elapsed().as_secs_f64() * 1000.0,
|
||||
lifecycle_elapsed_ms = lifecycle_started.elapsed().as_secs_f64() * 1000.0,
|
||||
"splash lifecycle wait completed"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
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"
|
||||
);
|
||||
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(
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "KSP Config Desk",
|
||||
"version": "0.1.4",
|
||||
"version": "0.2.6-pre.14",
|
||||
"identifier": "com.sasedev.ksp-app-config-desk",
|
||||
"build": {
|
||||
"beforeDevCommand": "npm run dev",
|
||||
"beforeDevCommand": {
|
||||
"script": "npm run dev",
|
||||
"cwd": "."
|
||||
},
|
||||
"devUrl": "http://localhost:1430",
|
||||
"beforeBuildCommand": "npm run build",
|
||||
"beforeBuildCommand": {
|
||||
"script": "npm run build",
|
||||
"cwd": "."
|
||||
},
|
||||
"frontendDist": "../../../builds/khadhroony-solana-project/ksp-app-config-desk/dist"
|
||||
},
|
||||
"app": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-config-desk/tests/desktop_contract.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
//! Desktop build/shell contract audits for Config Desk.
|
||||
|
||||
@@ -31,8 +31,10 @@ fn tauri_and_frontend_build_contracts_remain_explicit() {
|
||||
let root = app_root();
|
||||
let tauri = read_json(root.join("tauri.conf.json").as_path());
|
||||
assert_eq!(tauri.pointer("/build/devUrl").and_then(serde_json::Value::as_str), std::option::Option::Some("http://localhost:1430"));
|
||||
assert_eq!(tauri.pointer("/build/beforeDevCommand").and_then(serde_json::Value::as_str), std::option::Option::Some("npm run dev"));
|
||||
assert_eq!(tauri.pointer("/build/beforeBuildCommand").and_then(serde_json::Value::as_str), std::option::Option::Some("npm run build"));
|
||||
assert_eq!(tauri.pointer("/build/beforeDevCommand/script").and_then(serde_json::Value::as_str), std::option::Option::Some("npm run dev"));
|
||||
assert_eq!(tauri.pointer("/build/beforeDevCommand/cwd").and_then(serde_json::Value::as_str), std::option::Option::Some("."));
|
||||
assert_eq!(tauri.pointer("/build/beforeBuildCommand/script").and_then(serde_json::Value::as_str), std::option::Option::Some("npm run build"));
|
||||
assert_eq!(tauri.pointer("/build/beforeBuildCommand/cwd").and_then(serde_json::Value::as_str), std::option::Option::Some("."));
|
||||
let windows = tauri.pointer("/app/windows").and_then(serde_json::Value::as_array);
|
||||
assert!(windows.is_some());
|
||||
let windows = match windows {
|
||||
@@ -47,11 +49,13 @@ fn tauri_and_frontend_build_contracts_remain_explicit() {
|
||||
if let std::option::Option::Some(main) = main {
|
||||
assert_eq!(main.get("visible").and_then(serde_json::Value::as_bool), std::option::Option::Some(false));
|
||||
}
|
||||
let manifest = read_text(root.join("Cargo.toml").as_path());
|
||||
assert!(manifest.contains("name = \"ksp-app-config-desk\""));
|
||||
let package = read_json(root.join("package.json").as_path());
|
||||
let build = package.pointer("/scripts/build").and_then(serde_json::Value::as_str);
|
||||
let check = package.pointer("/scripts/check").and_then(serde_json::Value::as_str);
|
||||
assert!(build.is_some_and(|value| return value.contains("tsc") && value.contains("vite build")));
|
||||
assert_eq!(check, std::option::Option::Some("tsc --noEmit"));
|
||||
assert!(package.pointer("/scripts/check").is_none());
|
||||
assert!(package.pointer("/scripts/preview").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -70,3 +74,23 @@ fn shell_registry_owns_view_activation_and_specialized_editor_dispatch() {
|
||||
assert!(documents.contains("requestViewActivation"));
|
||||
assert!(html.contains("id=\"openSpecializedEditor\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_014_template_uses_sidebar_navigation_and_kbot_style_splash_contract() {
|
||||
let root = app_root();
|
||||
let main = read_text(root.join("frontend/main.html").as_path());
|
||||
let splash = read_text(root.join("frontend/splash.html").as_path());
|
||||
let splash_style = read_text(root.join("frontend/sass/splash.scss").as_path());
|
||||
let splash_ts = read_text(root.join("frontend/ts/splash.ts").as_path());
|
||||
assert!(main.starts_with("<!-- file: crates/ksp-app-config-desk/frontend/main.html -->"));
|
||||
assert!(main.contains("app-sidebar"));
|
||||
assert!(main.contains("nav nav-pills flex-column"));
|
||||
assert!(splash.starts_with("<!-- file: crates/ksp-app-config-desk/frontend/splash.html -->"));
|
||||
assert!(splash.contains("id=\"debug-info\""));
|
||||
assert!(splash.contains("id=\"messages-container\""));
|
||||
assert!(splash.contains("sass/splash.scss"));
|
||||
assert!(splash_style.contains("overflow: hidden"));
|
||||
assert!(splash_style.contains("user-select: none"));
|
||||
assert!(splash_ts.contains("add_debug"));
|
||||
assert!(splash_ts.contains("add_message"));
|
||||
}
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
// file: crates/ksp-app-config-desk/unit_tests/splash.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
#[test]
|
||||
fn fallback_splash_settings_are_short_and_ordered() {
|
||||
let settings = crate::SplashSettings::fallback();
|
||||
assert_eq!(settings.fade_in_ms(), 300);
|
||||
assert_eq!(settings.minimum_ms(), 1200);
|
||||
assert_eq!(settings.fade_ms(), 300);
|
||||
assert_eq!(settings.fade_out_ms(), 300);
|
||||
assert_eq!(settings.expected_backend_lifecycle_ms(), 1800);
|
||||
assert_eq!(settings.fade_in_source(), "fallback");
|
||||
assert_eq!(settings.minimum_source(), "fallback");
|
||||
assert_eq!(settings.fade_source(), "fallback");
|
||||
assert_eq!(settings.expected_backend_lifecycle_ms(), 1500);
|
||||
assert!(settings.minimum_ms() >= u64::from(settings.fade_ms()));
|
||||
assert_eq!(settings.fade_out_source(), "fallback");
|
||||
}
|
||||
|
||||
#[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"));
|
||||
fn splash_order_contract_keeps_action_message_status_and_duration() {
|
||||
let order =
|
||||
crate::SplashOrderDto::new("add_message", std::option::Option::Some("ready"), std::option::Option::Some("success"), std::option::Option::Some(300));
|
||||
assert_eq!(order.action, "add_message");
|
||||
assert_eq!(order.message.as_deref(), std::option::Option::Some("ready"));
|
||||
assert_eq!(order.status.as_deref(), std::option::Option::Some("success"));
|
||||
assert_eq!(order.duration_ms, std::option::Option::Some(300));
|
||||
}
|
||||
|
||||
@@ -24,14 +27,10 @@ fn splash_order_contract_keeps_action_message_and_duration() {
|
||||
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_minimum = super::parse_u64_setting("KSP_DESK_SPLASH_MINIMUM_MS", "60001", 60_000);
|
||||
let oversized_fade = super::parse_u32_setting("KSP_DESK_SPLASH_FADE_MS", "10001", 10_000);
|
||||
let oversized_fade_in = super::parse_u32_setting("KSP_DESK_SPLASH_FADE_IN_MS", "10001", 10_000);
|
||||
let oversized_fade_out = super::parse_u32_setting("KSP_DESK_SPLASH_FADE_OUT_MS", "10001", 10_000);
|
||||
assert!(malformed.is_err());
|
||||
assert!(oversized_minimum.is_err());
|
||||
assert!(oversized_fade.is_err());
|
||||
if let std::result::Result::Err(error) = oversized_minimum {
|
||||
assert!(error.context().iter().any(|field| return field.key() == "maximum_ms" && field.value() == "60000"));
|
||||
}
|
||||
if let std::result::Result::Err(error) = oversized_fade {
|
||||
assert!(error.context().iter().any(|field| return field.key() == "maximum_ms" && field.value() == "10000"));
|
||||
}
|
||||
assert!(oversized_fade_in.is_err());
|
||||
assert!(oversized_fade_out.is_err());
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<!doctype html>
|
||||
<!-- file: crates/ksp-app-wallet-desk/frontend/main.html -->
|
||||
<!-- version: 1 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
@@ -6,20 +8,22 @@
|
||||
<title>Wallet Desk — Dashboard</title>
|
||||
</head>
|
||||
<body>
|
||||
<header class="app-header bg-dark text-white shadow-sm">
|
||||
<div class="container-fluid h-100 d-flex align-items-center gap-3 px-4">
|
||||
<img class="app-logo" src="./imgs/logo.png" alt="KSP">
|
||||
<div class="d-flex flex-column me-auto">
|
||||
<span class="fw-semibold">Wallet Desk — <span id="headerViewTitle">Dashboard</span></span>
|
||||
<small id="shellStatus" class="text-white-50 app-shell-status">Initialisation du shell…</small>
|
||||
<header class="app-header shadow-sm">
|
||||
<nav class="navbar h-100 py-0 bg-light text-dark">
|
||||
<div class="container-fluid h-100 d-flex align-items-center gap-3 px-4 flex-nowrap">
|
||||
<img class="app-logo" src="./imgs/logo.png" alt="KSP">
|
||||
<div class="d-flex flex-column me-auto">
|
||||
<span class="fw-semibold text-primary">Wallet Desk — <span id="headerViewTitle" class="text-body">Dashboard</span></span>
|
||||
<small id="shellStatus" class="text-body-secondary app-shell-status">Initialisation du shell…</small>
|
||||
</div>
|
||||
<button class="btn btn-outline-secondary btn-sm" type="button" data-shell-action="deselect" disabled>
|
||||
<i class="fa-solid fa-xmark me-2" aria-hidden="true"></i>Deselect
|
||||
</button>
|
||||
<button class="btn btn-outline-primary btn-sm" type="button" data-shell-action="lock" disabled>
|
||||
<i class="fa-solid fa-lock me-2" aria-hidden="true"></i>Lock
|
||||
</button>
|
||||
</div>
|
||||
<button class="btn btn-outline-light btn-sm" type="button" data-shell-action="deselect" disabled>
|
||||
<i class="fa-solid fa-xmark me-2" aria-hidden="true"></i>Deselect
|
||||
</button>
|
||||
<button class="btn btn-outline-light btn-sm" type="button" data-shell-action="lock" disabled>
|
||||
<i class="fa-solid fa-lock me-2" aria-hidden="true"></i>Lock
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
<main class="app-main container-fluid p-0">
|
||||
<div class="row g-0 h-100">
|
||||
@@ -42,7 +46,7 @@
|
||||
<h1 id="viewTitle" class="h3 mb-1">Dashboard</h1>
|
||||
<p class="text-body-secondary mb-0">Identité autorisée et balance SOL via le Transport HTTP composite sont actives.</p>
|
||||
</div>
|
||||
<span class="badge text-bg-secondary">0.2.6-pre.013</span>
|
||||
<span class="badge text-bg-secondary">0.2.6-pre.014</span>
|
||||
</div>
|
||||
<section data-view-panel="dashboard">
|
||||
<div class="row g-3">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-wallet-desk/frontend/sass/splash.scss
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
@font-face {
|
||||
font-family: "Dos Amazigh";
|
||||
@@ -9,13 +9,23 @@
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
display: flex;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
@@ -25,8 +35,12 @@ body {
|
||||
#splash-container {
|
||||
position: relative;
|
||||
width: 960px;
|
||||
max-width: 100%;
|
||||
height: 637px;
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
will-change: opacity;
|
||||
}
|
||||
|
||||
#splash-image {
|
||||
@@ -35,6 +49,11 @@ body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
object-fit: contain;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#app-name {
|
||||
@@ -49,17 +68,70 @@ body {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
text-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#splash-status {
|
||||
#debug-info {
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
bottom: 1rem;
|
||||
left: 1rem;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 3;
|
||||
padding: 0.55rem 0.75rem;
|
||||
width: 55%;
|
||||
max-height: 30%;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
background: rgba(0, 0, 0, 0.18);
|
||||
font-family: monospace;
|
||||
font-size: 0.82rem;
|
||||
font-size: 12px;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
#messages-container {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 3;
|
||||
max-height: 30%;
|
||||
overflow-y: auto;
|
||||
padding: 10px 10px 36px;
|
||||
background: rgba(0, 0, 0, 0.18);
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
#debug-info::-webkit-scrollbar,
|
||||
#messages-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.splash-message {
|
||||
margin-bottom: 8px;
|
||||
padding: 6px 10px;
|
||||
border-radius: 4px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
animation: splashMessageFadeIn 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.splash-message.info { background: rgba(33, 150, 243, 0.22); }
|
||||
.splash-message.warning { background: rgba(255, 152, 0, 0.22); }
|
||||
.splash-message.error { background: rgba(244, 67, 54, 0.22); }
|
||||
.splash-message.success { background: rgba(76, 175, 80, 0.22); }
|
||||
|
||||
@keyframes splashMessageFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
<!doctype html>
|
||||
<!-- file: crates/ksp-app-wallet-desk/frontend/splash.html -->
|
||||
<!-- version: 1 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Chargement — KSP Wallet Desk</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="splash-container">
|
||||
<img id="splash-image" src="./imgs/splash.png" alt="">
|
||||
<div id="app-name">Wallet Desk</div>
|
||||
<div id="splash-status">Initialisation…</div>
|
||||
</div>
|
||||
<script type="module" src="./ts/splash.ts"></script>
|
||||
</body>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<link rel="stylesheet" href="sass/splash.scss">
|
||||
<title>KSP Wallet Desk — Chargement</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="splash-container" style="opacity: 0;">
|
||||
<img id="splash-image" src="imgs/splash.png" alt="Chargement de KSP Wallet Desk">
|
||||
<div id="app-name">Wallet Desk</div>
|
||||
<div id="debug-info" aria-live="off" hidden></div>
|
||||
<div id="messages-container" aria-live="polite"></div>
|
||||
</div>
|
||||
<script type="module" src="ts/splash.ts" defer></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-wallet-desk/frontend/ts/main.ts
|
||||
// version: 14
|
||||
// version: 15
|
||||
|
||||
import { Modal } from "bootstrap";
|
||||
import DataTable from "datatables.net-bs5";
|
||||
@@ -190,7 +190,7 @@ function initializeWalletTable(): void {
|
||||
zeroRecords: "Aucun wallet correspondant.",
|
||||
},
|
||||
});
|
||||
frontendDebug("main", "Wallet inventory DataTable initialized", { phase: "pre.013-integration-compliance" });
|
||||
frontendDebug("main", "Wallet inventory DataTable initialized", { phase: "pre.014-desktop-polish" });
|
||||
}
|
||||
|
||||
function renderWalletInventory(entries: WalletInventoryEntryDto[]): void {
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
// file: crates/ksp-app-wallet-desk/frontend/ts/splash.ts
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import type { SplashOrderDto } from "./bindings/ksp_app_wallet_desk/splash/SplashOrderDto.ts";
|
||||
import { frontendDebug, frontendError, frontendInfo, frontendTrace, installFrontendConsoleBridge } from "./frontend_log";
|
||||
import { invokeKsp } from "./invoke";
|
||||
import "../sass/splash.scss";
|
||||
|
||||
installFrontendConsoleBridge("splash");
|
||||
|
||||
@@ -50,21 +49,46 @@ async function animateOpacity(element: HTMLElement, fromOpacity: number, toOpaci
|
||||
frontendTrace("splash", "Splash opacity animation completed", { toOpacity, durationMs });
|
||||
}
|
||||
|
||||
function replaceStatus(message: string | null): void {
|
||||
if (!message) {
|
||||
function scrollToLatest(element: HTMLElement): void {
|
||||
element.scrollTop = element.scrollHeight;
|
||||
}
|
||||
|
||||
function addMessage(message: string, status: string | null): void {
|
||||
const container = document.querySelector<HTMLElement>("#messages-container");
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
const status = document.querySelector<HTMLElement>("#splash-status");
|
||||
if (status) {
|
||||
status.textContent = message;
|
||||
frontendTrace("splash", "Splash status replaced", { message });
|
||||
const line = document.createElement("div");
|
||||
const safeStatus = status === "warning" || status === "error" || status === "success" ? status : "info";
|
||||
line.className = `splash-message ${safeStatus}`;
|
||||
line.textContent = message;
|
||||
container.appendChild(line);
|
||||
scrollToLatest(container);
|
||||
}
|
||||
|
||||
function addDebugMessage(message: string): void {
|
||||
const container = document.querySelector<HTMLElement>("#debug-info");
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
container.hidden = false;
|
||||
const line = document.createElement("div");
|
||||
line.textContent = `${new Date().toLocaleTimeString()}: ${message}`;
|
||||
container.appendChild(line);
|
||||
scrollToLatest(container);
|
||||
}
|
||||
|
||||
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 (order.action === "add_message" && order.message) {
|
||||
addMessage(order.message, order.status);
|
||||
return;
|
||||
}
|
||||
if (order.action === "add_debug" && order.message) {
|
||||
addDebugMessage(order.message);
|
||||
return;
|
||||
}
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
@@ -93,7 +117,7 @@ async function initializeSplash(): Promise<void> {
|
||||
try {
|
||||
await invokeKsp<void>("splash", "splash_frontend_ready");
|
||||
} catch {
|
||||
replaceStatus("Le lifecycle du splash n'a pas pu démarrer.");
|
||||
addMessage("Le lifecycle du splash n'a pas pu démarrer.", "error");
|
||||
if (container) {
|
||||
container.style.opacity = "1";
|
||||
container.style.willChange = "auto";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ksp-app-wallet-desk",
|
||||
"private": true,
|
||||
"version": "0.2.6-pre.13.fix.2",
|
||||
"version": "0.2.6-pre.14",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-wallet-desk/src/app_state.rs
|
||||
// version: 17
|
||||
// version: 18
|
||||
|
||||
//! Shared backend state owned by the Wallet Desk Tauri application.
|
||||
|
||||
@@ -57,7 +57,7 @@ impl AppState {
|
||||
crate::SplashSettings::fallback()
|
||||
},
|
||||
};
|
||||
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WINDOWS, minimum_ms = splash_settings.minimum_ms(), minimum_source = splash_settings.minimum_source(), fade_ms = splash_settings.fade_ms(), fade_source = splash_settings.fade_source(), expected_backend_lifecycle_ms = splash_settings.expected_backend_lifecycle_ms(), "resolved Wallet Desk splash timings");
|
||||
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WINDOWS, minimum_ms = splash_settings.minimum_ms(), minimum_source = splash_settings.minimum_source(), fade_in_ms = splash_settings.fade_in_ms(), fade_in_source = splash_settings.fade_in_source(), fade_out_ms = splash_settings.fade_out_ms(), fade_out_source = splash_settings.fade_out_source(), expected_backend_lifecycle_ms = splash_settings.expected_backend_lifecycle_ms(), "resolved Wallet Desk splash timings");
|
||||
return std::result::Result::Ok(Self {
|
||||
config_management,
|
||||
logging_runtime: std::sync::Mutex::new(LoggingRuntimeState {
|
||||
@@ -126,7 +126,7 @@ impl AppState {
|
||||
effective_wallets_directory_created_on_startup: self.wallet_config_startup.effective_directory_created_on_startup(),
|
||||
fallback_logging_active: runtime.fallback_active,
|
||||
root_wallets_directory_created_on_startup: self.wallet_config_startup.root_directory_created_on_startup(),
|
||||
shell_phase: "pre.013-integration-compliance".to_owned(),
|
||||
shell_phase: "pre.014-desktop-polish".to_owned(),
|
||||
startup_diagnostic: runtime.startup_diagnostic.clone(),
|
||||
transport_available_endpoint_count,
|
||||
transport_clusters: self.transport_runtime.clusters(),
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
// file: crates/ksp-app-wallet-desk/src/splash.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Common splash settings and frontend event contracts for Wallet Desk.
|
||||
|
||||
use ts_rs::TS; // rust-rules: trait-import
|
||||
|
||||
const DEFAULT_SPLASH_FADE_MS: u32 = 300;
|
||||
const DEFAULT_SPLASH_FADE_IN_MS: u32 = 300;
|
||||
const DEFAULT_SPLASH_FADE_OUT_MS: u32 = 300;
|
||||
const DEFAULT_SPLASH_MINIMUM_MS: u64 = 1200;
|
||||
const ENV_SPLASH_FADE_MS: &str = "KSP_DESK_SPLASH_FADE_MS";
|
||||
const ENV_SPLASH_FADE_IN_MS: &str = "KSP_DESK_SPLASH_FADE_IN_MS";
|
||||
const ENV_SPLASH_FADE_OUT_MS: &str = "KSP_DESK_SPLASH_FADE_OUT_MS";
|
||||
const ENV_SPLASH_MINIMUM_MS: &str = "KSP_DESK_SPLASH_MINIMUM_MS";
|
||||
const MAX_SPLASH_FADE_MS: u32 = 10_000;
|
||||
const MAX_SPLASH_MINIMUM_MS: u64 = 60_000;
|
||||
@@ -15,10 +17,12 @@ const MAX_SPLASH_MINIMUM_MS: u64 = 60_000;
|
||||
/// Runtime timings used by the common desk splash lifecycle.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) struct SplashSettings {
|
||||
fade_in_ms: u32,
|
||||
minimum_ms: u64,
|
||||
fade_ms: u32,
|
||||
fade_out_ms: u32,
|
||||
fade_in_source: ksp_config_lib::ConfigEnvironmentSource,
|
||||
minimum_source: ksp_config_lib::ConfigEnvironmentSource,
|
||||
fade_source: ksp_config_lib::ConfigEnvironmentSource,
|
||||
fade_out_source: ksp_config_lib::ConfigEnvironmentSource,
|
||||
}
|
||||
|
||||
impl SplashSettings {
|
||||
@@ -29,13 +33,23 @@ impl SplashSettings {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let fade_in = environment.resolve_variable(ENV_SPLASH_FADE_IN_MS, std::option::Option::Some("300"));
|
||||
let fade_in = match fade_in {
|
||||
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 {
|
||||
let fade_out = environment.resolve_variable(ENV_SPLASH_FADE_OUT_MS, std::option::Option::Some("300"));
|
||||
let fade_out = match fade_out {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let fade_in_ms = parse_u32_setting(ENV_SPLASH_FADE_IN_MS, fade_in.value(), MAX_SPLASH_FADE_MS);
|
||||
let fade_in_ms = match fade_in_ms {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
@@ -44,35 +58,56 @@ impl SplashSettings {
|
||||
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 {
|
||||
let fade_out_ms = parse_u32_setting(ENV_SPLASH_FADE_OUT_MS, fade_out.value(), MAX_SPLASH_FADE_MS);
|
||||
let fade_out_ms = match fade_out_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, minimum_source: minimum.source(), fade_source: fade.source() });
|
||||
return std::result::Result::Ok(Self {
|
||||
fade_in_ms,
|
||||
minimum_ms,
|
||||
fade_out_ms,
|
||||
fade_in_source: fade_in.source(),
|
||||
minimum_source: minimum.source(),
|
||||
fade_out_source: fade_out.source(),
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns safe in-memory timings used when the managed environment cannot be resolved.
|
||||
#[must_use]
|
||||
pub(crate) const fn fallback() -> Self {
|
||||
return Self {
|
||||
fade_in_ms: DEFAULT_SPLASH_FADE_IN_MS,
|
||||
minimum_ms: DEFAULT_SPLASH_MINIMUM_MS,
|
||||
fade_ms: DEFAULT_SPLASH_FADE_MS,
|
||||
fade_out_ms: DEFAULT_SPLASH_FADE_OUT_MS,
|
||||
fade_in_source: ksp_config_lib::ConfigEnvironmentSource::Fallback,
|
||||
minimum_source: ksp_config_lib::ConfigEnvironmentSource::Fallback,
|
||||
fade_source: ksp_config_lib::ConfigEnvironmentSource::Fallback,
|
||||
fade_out_source: ksp_config_lib::ConfigEnvironmentSource::Fallback,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the minimum visible duration after splash frontend readiness.
|
||||
/// Returns the configured fade-in duration.
|
||||
#[must_use]
|
||||
pub(crate) const fn fade_in_ms(self) -> u32 {
|
||||
return self.fade_in_ms;
|
||||
}
|
||||
|
||||
/// Returns the minimum fully-visible splash duration.
|
||||
#[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.
|
||||
/// Returns the configured fade-out duration.
|
||||
#[must_use]
|
||||
pub(crate) const fn fade_ms(self) -> u32 {
|
||||
return self.fade_ms;
|
||||
pub(crate) const fn fade_out_ms(self) -> u32 {
|
||||
return self.fade_out_ms;
|
||||
}
|
||||
|
||||
/// Returns the safe provenance code for the fade-in duration.
|
||||
#[must_use]
|
||||
pub(crate) const fn fade_in_source(self) -> &'static str {
|
||||
return environment_source_code(self.fade_in_source);
|
||||
}
|
||||
|
||||
/// Returns the safe provenance code for the minimum duration.
|
||||
@@ -81,16 +116,16 @@ impl SplashSettings {
|
||||
return environment_source_code(self.minimum_source);
|
||||
}
|
||||
|
||||
/// Returns the safe provenance code for the fade duration.
|
||||
/// Returns the safe provenance code for the fade-out duration.
|
||||
#[must_use]
|
||||
pub(crate) const fn fade_source(self) -> &'static str {
|
||||
return environment_source_code(self.fade_source);
|
||||
pub(crate) const fn fade_out_source(self) -> &'static str {
|
||||
return environment_source_code(self.fade_out_source);
|
||||
}
|
||||
|
||||
/// Returns the minimum backend lifecycle duration from readiness until main activation.
|
||||
/// Returns the expected additive backend lifecycle from readiness through fade-out.
|
||||
#[must_use]
|
||||
pub(crate) fn expected_backend_lifecycle_ms(self) -> u64 {
|
||||
return self.minimum_ms + u64::from(self.fade_ms);
|
||||
return u64::from(self.fade_in_ms) + self.minimum_ms + u64::from(self.fade_out_ms);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +138,8 @@ pub(crate) struct SplashOrderDto {
|
||||
pub(crate) action: String,
|
||||
/// Optional status text.
|
||||
pub(crate) message: std::option::Option<String>,
|
||||
/// Optional message severity used by the bottom message feed.
|
||||
pub(crate) status: std::option::Option<String>,
|
||||
/// Optional animation duration in milliseconds.
|
||||
pub(crate) duration_ms: std::option::Option<u32>,
|
||||
}
|
||||
@@ -110,8 +147,13 @@ pub(crate) struct SplashOrderDto {
|
||||
impl SplashOrderDto {
|
||||
/// Creates a new `SplashOrderDto` value.
|
||||
#[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 };
|
||||
pub(crate) fn new(action: &str, message: std::option::Option<&str>, status: 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),
|
||||
status: status.map(std::string::ToString::to_string),
|
||||
duration_ms,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-wallet-desk/src/tw_splash.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Tauri-window lifecycle for the Wallet Desk splash window.
|
||||
|
||||
@@ -41,27 +41,64 @@ pub(crate) async fn splash_frontend_ready_service(
|
||||
}
|
||||
let settings = state.splash_settings();
|
||||
let lifecycle_started = std::time::Instant::now();
|
||||
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WINDOWS, window_label = WINDOW_LABEL_SPLASH, minimum_ms = settings.minimum_ms(), minimum_source = settings.minimum_source(), fade_ms = settings.fade_ms(), fade_source = settings.fade_source(), expected_backend_lifecycle_ms = settings.expected_backend_lifecycle_ms(), "splash frontend readiness accepted; starting splash to main lifecycle");
|
||||
let fade_in = emit_order(
|
||||
&invoking_window,
|
||||
crate::SplashOrderDto::new("fade_in", std::option::Option::Some("Initialisation de KSP Wallet Desk..."), std::option::Option::Some(settings.fade_ms())),
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||
window_label = WINDOW_LABEL_SPLASH,
|
||||
fade_in_ms = settings.fade_in_ms(),
|
||||
fade_in_source = settings.fade_in_source(),
|
||||
minimum_ms = settings.minimum_ms(),
|
||||
minimum_source = settings.minimum_source(),
|
||||
fade_out_ms = settings.fade_out_ms(),
|
||||
fade_out_source = settings.fade_out_source(),
|
||||
expected_backend_lifecycle_ms = settings.expected_backend_lifecycle_ms(),
|
||||
"splash frontend readiness accepted; starting splash to main lifecycle"
|
||||
);
|
||||
if let std::result::Result::Err(error) = fade_in {
|
||||
let emitted = emit_order(
|
||||
&invoking_window,
|
||||
crate::SplashOrderDto::new("fade_in", std::option::Option::None, std::option::Option::None, std::option::Option::Some(settings.fade_in_ms())),
|
||||
);
|
||||
if let std::result::Result::Err(error) = emitted {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let minimum_wait_started = std::time::Instant::now();
|
||||
tokio::time::sleep(std::time::Duration::from_millis(settings.minimum_ms())).await;
|
||||
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WINDOWS, window_label = WINDOW_LABEL_SPLASH, configured_wait_ms = settings.minimum_ms(), actual_wait_ms = minimum_wait_started.elapsed().as_secs_f64() * 1000.0, lifecycle_elapsed_ms = lifecycle_started.elapsed().as_secs_f64() * 1000.0, "splash minimum wait completed");
|
||||
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 {
|
||||
let emitted = emit_message(&invoking_window, "Initialisation de Wallet Desk...", "info");
|
||||
if let std::result::Result::Err(error) = emitted {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let fade_wait_started = std::time::Instant::now();
|
||||
tokio::time::sleep(std::time::Duration::from_millis(u64::from(settings.fade_ms()))).await;
|
||||
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WINDOWS, window_label = WINDOW_LABEL_SPLASH, configured_wait_ms = settings.fade_ms(), actual_wait_ms = fade_wait_started.elapsed().as_secs_f64() * 1000.0, lifecycle_elapsed_ms = lifecycle_started.elapsed().as_secs_f64() * 1000.0, "splash fade-out wait completed");
|
||||
let emitted = emit_debug(&invoking_window, "Listener frontend prêt; fade-in piloté par Rust.");
|
||||
if let std::result::Result::Err(error) = emitted {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
sleep_and_log(&invoking_window, "fade-in", u64::from(settings.fade_in_ms()), lifecycle_started).await;
|
||||
let emitted = emit_message(&invoking_window, "Configuration KSP chargée.", "success");
|
||||
if let std::result::Result::Err(error) = emitted {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let emitted = emit_debug(&invoking_window, "Configuration et provenance des timings résolues via ksp-config-lib.");
|
||||
if let std::result::Result::Err(error) = emitted {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let emitted = emit_message(&invoking_window, "Runtime Logging initialisé.", "success");
|
||||
if let std::result::Result::Err(error) = emitted {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let emitted = emit_message(&invoking_window, "Wallet et Transport prêts.", "success");
|
||||
if let std::result::Result::Err(error) = emitted {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
sleep_and_log(&invoking_window, "minimum", settings.minimum_ms(), lifecycle_started).await;
|
||||
let emitted = emit_message(&invoking_window, "Initialisation terminée.", "success");
|
||||
if let std::result::Result::Err(error) = emitted {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let emitted = emit_order(
|
||||
&invoking_window,
|
||||
crate::SplashOrderDto::new("fade_out", std::option::Option::None, std::option::Option::None, std::option::Option::Some(settings.fade_out_ms())),
|
||||
);
|
||||
if let std::result::Result::Err(error) = emitted {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
sleep_and_log(&invoking_window, "fade-out", u64::from(settings.fade_out_ms()), lifecycle_started).await;
|
||||
let show_main = crate::show_main_window(&app);
|
||||
if let std::result::Result::Err(error) = show_main {
|
||||
return std::result::Result::Err(error);
|
||||
@@ -74,10 +111,50 @@ pub(crate) async fn splash_frontend_ready_service(
|
||||
.with_source(error),
|
||||
);
|
||||
}
|
||||
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WINDOWS, window_label = WINDOW_LABEL_SPLASH, expected_backend_lifecycle_ms = settings.expected_backend_lifecycle_ms(), actual_backend_lifecycle_ms = lifecycle_started.elapsed().as_secs_f64() * 1000.0, "splash window destroyed after main activation");
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||
window_label = WINDOW_LABEL_SPLASH,
|
||||
expected_backend_lifecycle_ms = settings.expected_backend_lifecycle_ms(),
|
||||
actual_backend_lifecycle_ms = lifecycle_started.elapsed().as_secs_f64() * 1000.0,
|
||||
"splash window destroyed after main activation"
|
||||
);
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn emit_message(window: &tauri::WebviewWindow, message: &str, status: &str) -> ksp_core_lib::Result<()> {
|
||||
return emit_order(
|
||||
window,
|
||||
crate::SplashOrderDto::new("add_message", std::option::Option::Some(message), std::option::Option::Some(status), std::option::Option::None),
|
||||
);
|
||||
}
|
||||
|
||||
fn emit_debug(window: &tauri::WebviewWindow, message: &str) -> ksp_core_lib::Result<()> {
|
||||
if !cfg!(debug_assertions) {
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
return emit_order(
|
||||
window,
|
||||
crate::SplashOrderDto::new("add_debug", std::option::Option::Some(message), std::option::Option::None, std::option::Option::None),
|
||||
);
|
||||
}
|
||||
|
||||
async fn sleep_and_log(window: &tauri::WebviewWindow, phase: &str, duration_ms: u64, lifecycle_started: std::time::Instant) {
|
||||
let wait_started = std::time::Instant::now();
|
||||
tokio::time::sleep(std::time::Duration::from_millis(duration_ms)).await;
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||
window_label = window.label(),
|
||||
phase,
|
||||
configured_wait_ms = duration_ms,
|
||||
actual_wait_ms = wait_started.elapsed().as_secs_f64() * 1000.0,
|
||||
lifecycle_elapsed_ms = lifecycle_started.elapsed().as_secs_f64() * 1000.0,
|
||||
"splash lifecycle wait completed"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "KSP Wallet Desk",
|
||||
"version": "0.2.6-pre.13.fix.2",
|
||||
"version": "0.2.6-pre.14",
|
||||
"identifier": "com.sasedev.ksp-app-wallet-desk",
|
||||
"build": {
|
||||
"beforeDevCommand": "npm run dev",
|
||||
"beforeDevCommand": {
|
||||
"script": "npm run dev",
|
||||
"cwd": "."
|
||||
},
|
||||
"devUrl": "http://localhost:1432",
|
||||
"beforeBuildCommand": "npm run build",
|
||||
"beforeBuildCommand": {
|
||||
"script": "npm run build",
|
||||
"cwd": "."
|
||||
},
|
||||
"frontendDist": "../../../builds/khadhroony-solana-project/ksp-app-wallet-desk/dist"
|
||||
},
|
||||
"app": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-wallet-desk/tests/desktop_contract.rs
|
||||
// version: 14
|
||||
// version: 15
|
||||
|
||||
//! Desktop build, shell and Config-status contract audits for Wallet Desk.
|
||||
|
||||
@@ -31,9 +31,13 @@ fn tauri_shell_uses_reserved_wallet_desk_ports_and_template_windows() {
|
||||
let root = app_root();
|
||||
let tauri = read_json(root.join("tauri.conf.json").as_path());
|
||||
assert_eq!(tauri.pointer("/build/devUrl").and_then(serde_json::Value::as_str), std::option::Option::Some("http://localhost:1432"));
|
||||
assert_eq!(tauri.pointer("/build/beforeDevCommand").and_then(serde_json::Value::as_str), std::option::Option::Some("npm run dev"));
|
||||
assert_eq!(tauri.pointer("/build/beforeBuildCommand").and_then(serde_json::Value::as_str), std::option::Option::Some("npm run build"));
|
||||
assert_eq!(tauri.pointer("/build/beforeDevCommand/script").and_then(serde_json::Value::as_str), std::option::Option::Some("npm run dev"));
|
||||
assert_eq!(tauri.pointer("/build/beforeDevCommand/cwd").and_then(serde_json::Value::as_str), std::option::Option::Some("."));
|
||||
assert_eq!(tauri.pointer("/build/beforeBuildCommand/script").and_then(serde_json::Value::as_str), std::option::Option::Some("npm run build"));
|
||||
assert_eq!(tauri.pointer("/build/beforeBuildCommand/cwd").and_then(serde_json::Value::as_str), std::option::Option::Some("."));
|
||||
assert_eq!(tauri.pointer("/app/security/csp"), std::option::Option::Some(&serde_json::Value::Null));
|
||||
let manifest = read_text(root.join("Cargo.toml").as_path());
|
||||
assert!(manifest.contains("name = \"ksp-app-wallet-desk\""));
|
||||
let windows = tauri.pointer("/app/windows").and_then(serde_json::Value::as_array);
|
||||
assert!(windows.is_some());
|
||||
let windows = match windows {
|
||||
@@ -347,3 +351,24 @@ fn pre_013_integration_compliance_and_devnet_smoke_are_durable_release_gates() {
|
||||
assert!(smoke.contains("#[ignore"));
|
||||
assert!(validation.contains("Config -> Wallet -> Transport -> Devnet getBalance"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_014_template_has_normalized_html_headers_light_header_and_kbot_style_splash_contract() {
|
||||
let root = app_root();
|
||||
let main = read_text(root.join("frontend/main.html").as_path());
|
||||
let splash = read_text(root.join("frontend/splash.html").as_path());
|
||||
let splash_style = read_text(root.join("frontend/sass/splash.scss").as_path());
|
||||
let splash_ts = read_text(root.join("frontend/ts/splash.ts").as_path());
|
||||
assert!(main.starts_with("<!-- file: crates/ksp-app-wallet-desk/frontend/main.html -->"));
|
||||
assert!(main.contains("navbar h-100 py-0 bg-light text-dark"));
|
||||
assert!(main.contains("app-sidebar"));
|
||||
assert!(splash.starts_with("<!-- file: crates/ksp-app-wallet-desk/frontend/splash.html -->"));
|
||||
assert!(splash.contains("id=\"debug-info\""));
|
||||
assert!(splash.contains("id=\"messages-container\""));
|
||||
assert!(splash.contains("sass/splash.scss"));
|
||||
assert!(splash_style.contains("overflow: hidden"));
|
||||
assert!(splash_style.contains("user-select: none"));
|
||||
assert!(splash_ts.contains("add_debug"));
|
||||
assert!(splash_ts.contains("add_message"));
|
||||
assert!(!splash_ts.contains("../sass/splash.scss"));
|
||||
}
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
// file: crates/ksp-app-wallet-desk/unit_tests/splash.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
#[test]
|
||||
fn fallback_splash_settings_are_short_and_ordered() {
|
||||
let settings = crate::SplashSettings::fallback();
|
||||
assert_eq!(settings.fade_in_ms(), 300);
|
||||
assert_eq!(settings.minimum_ms(), 1200);
|
||||
assert_eq!(settings.fade_ms(), 300);
|
||||
assert_eq!(settings.fade_out_ms(), 300);
|
||||
assert_eq!(settings.expected_backend_lifecycle_ms(), 1800);
|
||||
assert_eq!(settings.fade_in_source(), "fallback");
|
||||
assert_eq!(settings.minimum_source(), "fallback");
|
||||
assert_eq!(settings.fade_source(), "fallback");
|
||||
assert_eq!(settings.expected_backend_lifecycle_ms(), 1500);
|
||||
assert!(settings.minimum_ms() >= u64::from(settings.fade_ms()));
|
||||
assert_eq!(settings.fade_out_source(), "fallback");
|
||||
}
|
||||
|
||||
#[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"));
|
||||
fn splash_order_contract_keeps_action_message_status_and_duration() {
|
||||
let order =
|
||||
crate::SplashOrderDto::new("add_message", std::option::Option::Some("ready"), std::option::Option::Some("success"), std::option::Option::Some(300));
|
||||
assert_eq!(order.action, "add_message");
|
||||
assert_eq!(order.message.as_deref(), std::option::Option::Some("ready"));
|
||||
assert_eq!(order.status.as_deref(), std::option::Option::Some("success"));
|
||||
assert_eq!(order.duration_ms, std::option::Option::Some(300));
|
||||
}
|
||||
|
||||
@@ -24,14 +27,10 @@ fn splash_order_contract_keeps_action_message_and_duration() {
|
||||
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_minimum = super::parse_u64_setting("KSP_DESK_SPLASH_MINIMUM_MS", "60001", 60_000);
|
||||
let oversized_fade = super::parse_u32_setting("KSP_DESK_SPLASH_FADE_MS", "10001", 10_000);
|
||||
let oversized_fade_in = super::parse_u32_setting("KSP_DESK_SPLASH_FADE_IN_MS", "10001", 10_000);
|
||||
let oversized_fade_out = super::parse_u32_setting("KSP_DESK_SPLASH_FADE_OUT_MS", "10001", 10_000);
|
||||
assert!(malformed.is_err());
|
||||
assert!(oversized_minimum.is_err());
|
||||
assert!(oversized_fade.is_err());
|
||||
if let std::result::Result::Err(error) = oversized_minimum {
|
||||
assert!(error.context().iter().any(|field| return field.key() == "maximum_ms" && field.value() == "60000"));
|
||||
}
|
||||
if let std::result::Result::Err(error) = oversized_fade {
|
||||
assert!(error.context().iter().any(|field| return field.key() == "maximum_ms" && field.value() == "10000"));
|
||||
}
|
||||
assert!(oversized_fade_in.is_err());
|
||||
assert!(oversized_fade_out.is_err());
|
||||
}
|
||||
|
||||
385
deltas/0.2.6/pre.014.md
Normal file
385
deltas/0.2.6/pre.014.md
Normal file
@@ -0,0 +1,385 @@
|
||||
<!-- file: deltas/0.2.6/pre.014.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta `0.2.6-pre.014` — polish desktop partagé, splash kbot3 et lancement Tauri multi-app
|
||||
|
||||
## Base requise
|
||||
|
||||
```text
|
||||
0.2.6-pre.013-fix.002 appliquée
|
||||
workspace.package.version = 0.2.6-pre.13.fix.2
|
||||
```
|
||||
|
||||
La validation opérateur de la base est intégralement verte :
|
||||
|
||||
```text
|
||||
cargo fmt --all OK
|
||||
python3 scripts/audit_rust_workspace_rules.py clean
|
||||
cargo check --workspace OK
|
||||
cargo clippy --workspace --all-targets OK
|
||||
ksp-logging-lib ownership 2/2 OK
|
||||
Wallet Desk release_compliance 4/4 OK
|
||||
cargo test -p ksp-app-wallet-desk OK
|
||||
cargo test --workspace OK
|
||||
smoke Wallet Desk Devnet de pre.013 déjà validé sur fix.001
|
||||
```
|
||||
|
||||
## Type de livraison
|
||||
|
||||
```text
|
||||
ksp-general-0.2.6-pre.014.zip
|
||||
```
|
||||
|
||||
Le signal technique devient :
|
||||
|
||||
```text
|
||||
workspace.package.version = 0.2.6-pre.14
|
||||
commit = v0.2.6-pre.014
|
||||
```
|
||||
|
||||
`ksp-app-config-desk/package.json`, `ksp-app-config-desk/tauri.conf.json`, `ksp-app-wallet-desk/package.json` et `ksp-app-wallet-desk/tauri.conf.json` suivent `0.2.6-pre.14`.
|
||||
|
||||
Aucun tag prerelease.
|
||||
|
||||
## Objectifs
|
||||
|
||||
Cette tranche ne change aucun contrat Wallet métier. Elle ferme les défauts de gabarit desktop remontés avant la documentation finale :
|
||||
|
||||
```text
|
||||
en-têtes HTML Khadhroony normalisés
|
||||
splash sans scrollbars fenêtre
|
||||
splash avec fade-in / minimum / fade-out distincts
|
||||
messages généraux défilants en bas
|
||||
messages debug défilants en haut
|
||||
nom/titre central du splash non sélectionnable
|
||||
navigation Config Desk déplacée du header vers une sidebar
|
||||
palette Wallet Desk réalignée sur la référence claire Config Desk
|
||||
lancement Tauri multi-app corrigé et rendu durable
|
||||
prompt 0.2.7 intercalé pour le conteneur binaire .kspwallet
|
||||
```
|
||||
|
||||
## En-têtes HTML
|
||||
|
||||
Les quatre fichiers HTML des deux applications possèdent désormais les deux premières lignes normalisées :
|
||||
|
||||
```html
|
||||
<!-- file: ... -->
|
||||
<!-- version: N -->
|
||||
```
|
||||
|
||||
Les Wallet HTML, qui n'en possédaient pas, démarrent à `version: 1`.
|
||||
|
||||
## Splash partagé
|
||||
|
||||
Le comportement reprend les fonctionnalités génériques éprouvées dans kbot3 sans reprendre ses constantes codées en dur.
|
||||
|
||||
Les deux Desks utilisent :
|
||||
|
||||
```text
|
||||
KSP_DESK_SPLASH_FADE_IN_MS
|
||||
KSP_DESK_SPLASH_MINIMUM_MS
|
||||
KSP_DESK_SPLASH_FADE_OUT_MS
|
||||
```
|
||||
|
||||
Fallbacks :
|
||||
|
||||
```text
|
||||
fade-in = 300 ms
|
||||
minimum = 1200 ms pleinement visible
|
||||
fade-out = 300 ms
|
||||
```
|
||||
|
||||
Bornes :
|
||||
|
||||
```text
|
||||
fade-in / fade-out <= 10 000 ms
|
||||
minimum <= 60 000 ms
|
||||
```
|
||||
|
||||
La provenance continue d'être résolue uniquement par `ksp-config-lib` : process > `.env` > fallback.
|
||||
|
||||
Le lifecycle backend devient additif et explicite :
|
||||
|
||||
```text
|
||||
frontend ready
|
||||
-> fade-in + attente fade-in
|
||||
-> messages init + minimum pleinement visible
|
||||
-> fade-out + attente fade-out
|
||||
-> show/focus main
|
||||
-> destroy splash
|
||||
```
|
||||
|
||||
`expected_backend_lifecycle_ms` vaut donc :
|
||||
|
||||
```text
|
||||
fade_in + minimum + fade_out
|
||||
```
|
||||
|
||||
## Messages splash
|
||||
|
||||
Comme dans kbot3 :
|
||||
|
||||
- le bas du splash contient un feed de messages généraux avec statuts `info/warning/error/success` ;
|
||||
- le haut contient un feed diagnostic uniquement lorsque Rust est compilé avec `debug_assertions` ;
|
||||
- les deux zones auto-scrollent vers le dernier élément ;
|
||||
- leurs scrollbars internes sont masquées visuellement.
|
||||
|
||||
Le frontend ne reçoit aucun secret dans ces messages.
|
||||
|
||||
## Scrollbars et sélection
|
||||
|
||||
Wallet Desk chargeait auparavant `splash.scss` depuis le module TypeScript, alors que Config Desk le chargeait directement dans `<head>`. Le style pouvait donc arriver après le premier rendu WebView et laisser apparaître momentanément les scrollbars natives.
|
||||
|
||||
Les deux applications chargent maintenant `sass/splash.scss` directement dans le HTML avant JavaScript.
|
||||
|
||||
Le CSS fixe également :
|
||||
|
||||
```text
|
||||
html/body width/height 100%
|
||||
overflow hidden dès le premier rendu
|
||||
box-sizing border-box
|
||||
container max-width/max-height 100%
|
||||
overflow hidden sur le container
|
||||
scrolling borné aux feeds internes
|
||||
```
|
||||
|
||||
Le logo/image et le nom central appliquent `user-select: none`, `-webkit-user-select: none`, `-webkit-user-drag: none` et `pointer-events: none`.
|
||||
|
||||
## Harmonisation du main
|
||||
|
||||
La navigation Config Desk n'est plus placée horizontalement dans le header. Elle reprend le pattern apprécié de Wallet Desk :
|
||||
|
||||
```text
|
||||
header : identité + vue active
|
||||
main : sidebar pills verticale + contenu scrollable
|
||||
footer : inchangé
|
||||
```
|
||||
|
||||
Wallet Desk conserve sa sidebar mais son header revient sur la palette claire de référence de Config Desk au lieu du header sombre divergent.
|
||||
|
||||
Les `data-view` historiques de Config Desk restent inchangés ; aucune logique d'activation TypeScript n'est réécrite.
|
||||
|
||||
## Correction Tauri multi-app
|
||||
|
||||
Le défaut suivant a été reproduit conceptuellement à partir de la configuration actuelle :
|
||||
|
||||
```text
|
||||
commande lancée depuis la racine avec -c Config Desk
|
||||
-> Vite Config Desk démarre sur 1430
|
||||
-> backend Tauri découvert peut être Wallet Desk
|
||||
```
|
||||
|
||||
La cause est que Tauri v2 traite `-c/--config` comme une configuration **fusionnée avec la configuration par défaut déjà résolue** ; cette option n'est pas un sélecteur de crate/projet.
|
||||
|
||||
Le workflow KSP devient donc crate-local :
|
||||
|
||||
```bash
|
||||
(cd crates/ksp-app-config-desk && cargo tauri dev)
|
||||
(cd crates/ksp-app-wallet-desk && cargo tauri dev)
|
||||
```
|
||||
|
||||
et pour le build :
|
||||
|
||||
```bash
|
||||
(cd crates/<app> && cargo tauri build)
|
||||
```
|
||||
|
||||
Les hooks Tauri sont aussi explicités sous forme objet :
|
||||
|
||||
```json
|
||||
{
|
||||
"script": "npm run dev",
|
||||
"cwd": "."
|
||||
}
|
||||
```
|
||||
|
||||
et équivalent pour `npm run build`.
|
||||
|
||||
Les canaris vérifient maintenant le port, le hook/cwd et le nom du package Rust propre à l'application.
|
||||
|
||||
`KSP-APP-026`, `KSP-APP-029` et `KSP-APP-034` sont corrigées. Le lancement racine avec `-c crates/<app>/tauri.conf.json` est désormais interdit comme méthode de sélection multi-app.
|
||||
|
||||
Config Desk abandonne aussi son script standalone `check` afin d'aligner les deux applications sur la règle actuelle : l'opérateur n'exécute directement npm que pour installer/mettre à jour les dépendances ; Tauri possède les scripts dev/build.
|
||||
|
||||
## Intercalation de `0.2.7` — conteneur binaire `.kspwallet`
|
||||
|
||||
La question du JSON V1 lisible dans un éditeur est dissociée de `0.2.6` et du futur WebSocket.
|
||||
|
||||
Décision de cadrage :
|
||||
|
||||
```text
|
||||
Base64 seul NON
|
||||
conteneur binaire versionné à auditer/implémenter en 0.2.7
|
||||
compatibilité lecture JSON V1 obligatoire
|
||||
Wallet Desk modifié fonctionnellement NON attendu
|
||||
```
|
||||
|
||||
Base64 seul reste textuel, trivialement réversible, sans gain cryptographique et avec surcharge de taille.
|
||||
|
||||
`0.2.7` doit séparer :
|
||||
|
||||
```text
|
||||
container_version
|
||||
!=
|
||||
format_version logique/cryptographique
|
||||
```
|
||||
|
||||
Ainsi V1 conserve ses semantics VIEW/OWNER et un futur `format_version >= 2` reste disponible pour de vraies évolutions d'autorisation/crypto.
|
||||
|
||||
Le prompt ajouté est :
|
||||
|
||||
```text
|
||||
prompts/012-V0_2_7_START_PROMPT.md
|
||||
```
|
||||
|
||||
WebSocket et les releases suivantes sont décalés d'un numéro dans le roadmap/inventaire/séquence fonctionnelle globale.
|
||||
|
||||
## Futur second facteur
|
||||
|
||||
`docs/IDEAS.md` précise :
|
||||
|
||||
- `ksp-wallet-lib` restera propriétaire du format, des challenges et de la vérification ;
|
||||
- Wallet Desk devra évoluer lorsqu'un facteur est interactif : OTP, enrollment/recovery, hardware/WebAuthn, consentement externe ;
|
||||
- un seed TOTP stocké uniquement dans le même fichier ne doit pas être assimilé automatiquement à un second facteur indépendant contre un attaquant possédant ce fichier.
|
||||
|
||||
Aucun V2/OTP/2FA n'est implémenté dans cette tranche.
|
||||
|
||||
## Documentation globale
|
||||
|
||||
Exception justifiée à la règle « ROADMAP seulement au début/à la fin » : l'insertion d'une nouvelle release `0.2.7` change réellement la séquence globale. Sont donc mis à jour :
|
||||
|
||||
```text
|
||||
ROADMAP.md
|
||||
docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md
|
||||
docs/architecture/004-COMPONENT_INVENTORY.md
|
||||
```
|
||||
|
||||
`CHANGELOG.md` reste inchangé et sera traité à la clôture.
|
||||
|
||||
## Fichiers ajoutés
|
||||
|
||||
```text
|
||||
deltas/0.2.6/pre.014.md
|
||||
prompts/012-V0_2_7_START_PROMPT.md
|
||||
```
|
||||
|
||||
## Fichiers modifiés
|
||||
|
||||
```text
|
||||
.env.example
|
||||
Cargo.toml
|
||||
ROADMAP.md
|
||||
crates/ksp-app-config-desk/README.md
|
||||
crates/ksp-app-config-desk/USAGE.md
|
||||
crates/ksp-app-config-desk/frontend/main.html
|
||||
crates/ksp-app-config-desk/frontend/sass/_app.scss
|
||||
crates/ksp-app-config-desk/frontend/sass/splash.scss
|
||||
crates/ksp-app-config-desk/frontend/splash.html
|
||||
crates/ksp-app-config-desk/frontend/ts/splash.ts
|
||||
crates/ksp-app-config-desk/package.json
|
||||
crates/ksp-app-config-desk/src/app_state.rs
|
||||
crates/ksp-app-config-desk/src/splash.rs
|
||||
crates/ksp-app-config-desk/src/tw_splash.rs
|
||||
crates/ksp-app-config-desk/tauri.conf.json
|
||||
crates/ksp-app-config-desk/tests/desktop_contract.rs
|
||||
crates/ksp-app-config-desk/unit_tests/splash.rs
|
||||
crates/ksp-app-wallet-desk/frontend/main.html
|
||||
crates/ksp-app-wallet-desk/frontend/sass/splash.scss
|
||||
crates/ksp-app-wallet-desk/frontend/splash.html
|
||||
crates/ksp-app-wallet-desk/frontend/ts/main.ts
|
||||
crates/ksp-app-wallet-desk/frontend/ts/splash.ts
|
||||
crates/ksp-app-wallet-desk/package.json
|
||||
crates/ksp-app-wallet-desk/src/app_state.rs
|
||||
crates/ksp-app-wallet-desk/src/splash.rs
|
||||
crates/ksp-app-wallet-desk/src/tw_splash.rs
|
||||
crates/ksp-app-wallet-desk/tauri.conf.json
|
||||
crates/ksp-app-wallet-desk/tests/desktop_contract.rs
|
||||
crates/ksp-app-wallet-desk/unit_tests/splash.rs
|
||||
docs/IDEAS.md
|
||||
docs/architecture/004-COMPONENT_INVENTORY.md
|
||||
docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md
|
||||
docs/plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md
|
||||
docs/plans/013-V0_2_6_WALLET_DESK_PLAN.md
|
||||
docs/rules/RULES_KSP.md
|
||||
prompts/000-README.md
|
||||
prompts/011-V0_2_6_START_PROMPT.md
|
||||
```
|
||||
|
||||
## Fichiers supprimés
|
||||
|
||||
```text
|
||||
aucun
|
||||
```
|
||||
|
||||
## Contrôles assistant
|
||||
|
||||
```text
|
||||
General Rust rule audit clean
|
||||
Rust export completeness audit 0 candidate
|
||||
KSP workspace Rust rule audit clean
|
||||
TOML / JSON parse clean
|
||||
HTML parse + IDs uniques
|
||||
splash TypeScript aucun diagnostic syntaxique local
|
||||
(imports non résolus attendus sans node_modules/bindings)
|
||||
CHANGELOG inchangé
|
||||
```
|
||||
|
||||
Cargo n'est pas disponible dans l'environnement de génération ; compilation, Clippy et tests restent autoritatifs côté opérateur.
|
||||
|
||||
## Validation opérateur requise
|
||||
|
||||
```bash
|
||||
cargo fmt --all
|
||||
python3 scripts/audit_rust_workspace_rules.py
|
||||
cargo check --workspace
|
||||
cargo clippy --workspace --all-targets
|
||||
cargo test -p ksp-app-config-desk
|
||||
cargo test -p ksp-app-wallet-desk
|
||||
cargo test --workspace
|
||||
```
|
||||
|
||||
Validation visuelle Config Desk :
|
||||
|
||||
```bash
|
||||
KSP_DESK_SPLASH_FADE_IN_MS=1500 \
|
||||
KSP_DESK_SPLASH_MINIMUM_MS=6000 \
|
||||
KSP_DESK_SPLASH_FADE_OUT_MS=1500 \
|
||||
bash -lc 'cd crates/ksp-app-config-desk && cargo tauri dev'
|
||||
```
|
||||
|
||||
Vérifier :
|
||||
|
||||
```text
|
||||
backend = ksp-app-config-desk
|
||||
Vite = 1430 / HMR = 1431
|
||||
aucune scrollbar splash fenêtre
|
||||
fade-in puis minimum puis fade-out observables
|
||||
messages généraux en bas
|
||||
diagnostics en haut en debug
|
||||
titre central non sélectionnable
|
||||
navigation verticale dans le main
|
||||
```
|
||||
|
||||
Validation visuelle Wallet Desk :
|
||||
|
||||
```bash
|
||||
KSP_DESK_SPLASH_FADE_IN_MS=1500 \
|
||||
KSP_DESK_SPLASH_MINIMUM_MS=6000 \
|
||||
KSP_DESK_SPLASH_FADE_OUT_MS=1500 \
|
||||
KSP_WALLETS_DIRECTORY=var/wallet-desk-pre014 \
|
||||
bash -lc 'cd crates/ksp-app-wallet-desk && cargo tauri dev'
|
||||
```
|
||||
|
||||
Vérifier :
|
||||
|
||||
```text
|
||||
backend = ksp-app-wallet-desk
|
||||
Vite = 1432 / HMR = 1433
|
||||
aucune scrollbar splash fenêtre
|
||||
mêmes trois phases/messages
|
||||
titre central non sélectionnable
|
||||
header clair cohérent avec Config Desk
|
||||
fonctionnalités Wallet pre.013 non régressées
|
||||
```
|
||||
|
||||
**Ne pas exécuter `cargo tauri build` en `pre.014`.** Le build final reste réservé à `pre.015` et doit rester l'absolue dernière opération de validation de `0.2.6`.
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/IDEAS.md -->
|
||||
<!-- version: 19 -->
|
||||
<!-- version: 20 -->
|
||||
|
||||
# Idées à explorer
|
||||
|
||||
@@ -202,6 +202,14 @@ Formats/cibles à inventorier et prioriser selon usage réel :
|
||||
|
||||
Chaque format doit être étudié côté sécurité, round-trip, secret/public, dépendances et compatibilité avant engagement.
|
||||
|
||||
### Conteneur binaire `.kspwallet` et formats logiques futurs
|
||||
|
||||
**Status :** Retenu pour audit en `0.2.7` / facteurs futurs à explorer
|
||||
|
||||
Le JSON V1 actuel est un format d’interop lisible. Base64 seul n’apporte aucune sécurité et resterait un texte trivialement décodable avec environ un tiers de surcharge. `0.2.7` doit donc étudier un **conteneur de persistence binaire versionné** séparé du `format_version` logique/cryptographique : magic/framing explicite, lecture rétrocompatible du JSON V1 historique, écriture binaire par défaut après validation, migration explicite et test vectors. Le choix du codec/framing exact est audité avant engagement ; il ne doit pas casser les transcripts/AAD, VIEW/OWNER, keypair ou import/export.
|
||||
|
||||
Un futur `format_version >= 2` pourra introduire d’autres modèles d’autorisation, notamment password + facteur supplémentaire. `ksp-wallet-lib` restera propriétaire du format, des challenges et de la vérification, mais toute interaction réelle (OTP, enrollment/recovery, hardware/WebAuthn, validation distante) exigera une évolution de Wallet Desk ou du client concerné. Un seed TOTP stocké uniquement dans le même fichier que le wallet ne doit pas être présenté automatiquement comme un second facteur indépendant contre un attaquant possédant ce fichier.
|
||||
|
||||
## Pipelines
|
||||
|
||||
### Pas de pipeline monolithique
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/architecture/004-COMPONENT_INVENTORY.md -->
|
||||
<!-- version: 15 -->
|
||||
<!-- version: 16 -->
|
||||
|
||||
# Inventaire initial des composants KSP
|
||||
|
||||
@@ -26,13 +26,14 @@ Ce document maintient l'inventaire synthétique des composants retenus ou presse
|
||||
| On-chain HTTP | `ksp-onchain-transport-lib` | lib | Stable | `0.2.1`–`0.2.4` | HTTP standard complet : 52/52 current + 14/14 historical |
|
||||
| Wallet | `ksp-wallet-lib` | lib | Stable | `0.2.5` | `.kspwallet`, VIEW/OWNER, secrets, signature, import/export |
|
||||
| Wallet Desk | `ksp-app-wallet-desk` | app | Retenu | `0.2.6` | Wallet + Config composite + HTTP/balance |
|
||||
| Standard WS | `ksp-onchain-transport-lib` | lib | Retenu | `0.2.7` | WebSocket Solana complet, sessions/subscriptions |
|
||||
| Helius WS | `ksp-onchain-transport-lib` | lib | Retenu | `0.2.8` | LaserStream WebSocket comme extension du moteur standard |
|
||||
| Yellowstone | `ksp-onchain-transport-lib` | lib | Pressenti | `0.2.9` | client gRPC standard/provider-neutral |
|
||||
| Off-chain price | `ksp-offchain-transport-lib` | lib | Retenu | `0.2.10` | première abstraction/provider de prix SOL/USD, SOL/EUR |
|
||||
| Price Desk | nom à fixer | app | Retenu | `0.2.11` | visualisation/validation des prix + intégration Wallet Desk |
|
||||
| Wire | `ksp-interface-lib` | lib | Retenu | `0.2.12` | façade wire officielle + API publique wire |
|
||||
| Program API | `ksp-program-api` | API | Retenu | `0.2.13` | contrats extensibles Program |
|
||||
| Wallet persistence | `ksp-wallet-lib` | lib | Retenu | `0.2.7` | conteneur binaire rétrocompatible autour du payload V1 |
|
||||
| Standard WS | `ksp-onchain-transport-lib` | lib | Retenu | `0.2.8` | WebSocket Solana complet, sessions/subscriptions |
|
||||
| Helius WS | `ksp-onchain-transport-lib` | lib | Retenu | `0.2.9` | LaserStream WebSocket comme extension du moteur standard |
|
||||
| Yellowstone | `ksp-onchain-transport-lib` | lib | Pressenti | `0.2.10` | client gRPC standard/provider-neutral |
|
||||
| Off-chain price | `ksp-offchain-transport-lib` | lib | Retenu | `0.2.11` | première abstraction/provider de prix SOL/USD, SOL/EUR |
|
||||
| Price Desk | nom à fixer | app | Retenu | `0.2.12` | visualisation/validation des prix + intégration Wallet Desk |
|
||||
| Wire | `ksp-interface-lib` | lib | Retenu | `0.2.13` | façade wire officielle + API publique wire |
|
||||
| Program API | `ksp-program-api` | API | Retenu | `0.2.14` | contrats extensibles Program |
|
||||
| Program impl. | `ksp-program-lib` | lib | Retenu | vertical slices ultérieurs | implementations Program officielles |
|
||||
| Program extension | `ksp-program-<name>-lib` | lib externe | À la demande | dès besoin | implementation externe de `ksp-program-api` |
|
||||
| Store API | `ksp-store-api` | API | Retenu | `0.3.1` | contrats persistence backend-agnostic, RAW d'abord |
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md -->
|
||||
<!-- version: 74 -->
|
||||
<!-- version: 75 -->
|
||||
|
||||
# Séquence des releases fonctionnelles KSP
|
||||
|
||||
@@ -361,13 +361,14 @@ Par défaut :
|
||||
0.2.4 HTTP Blocks + Economics + compliance complète
|
||||
0.2.5 wallet foundation (.kspwallet)
|
||||
0.2.6 Wallet Desk
|
||||
0.2.7 standard Solana WebSocket
|
||||
0.2.8 Helius LaserStream WebSocket
|
||||
0.2.9 Yellowstone gRPC standard foundation
|
||||
0.2.10 off-chain price transport
|
||||
0.2.11 price visualization desk + intégration prix dans Wallet Desk
|
||||
0.2.12 interface/wire foundation
|
||||
0.2.13 program-api foundation
|
||||
0.2.7 .kspwallet binary persistence container
|
||||
0.2.8 standard Solana WebSocket
|
||||
0.2.9 Helius LaserStream WebSocket
|
||||
0.2.10 Yellowstone gRPC standard foundation
|
||||
0.2.11 off-chain price transport
|
||||
0.2.12 price visualization desk + intégration prix dans Wallet Desk
|
||||
0.2.13 interface/wire foundation
|
||||
0.2.14 program-api foundation
|
||||
```
|
||||
|
||||
`0.2.1-pre.001` a appliqué le gate de sizing et refusé le scope HTTP monolithique initial : l'inventaire du 2026-08-17 contient 52 méthodes courantes et 14 méthodes Deprecated historiques. Ce premier delta avait réparti la couverture typée sur `0.2.1`–`0.2.6`. `0.2.1-pre.001-fix.001` recalibre ensuite les 48 méthodes restantes sur trois releases complémentaires `0.2.2`–`0.2.4`, soit trois sessions nominales au maximum si chaque release utilise sa session complète. Si une release se clôt plus vite que prévu, la même session peut enchaîner la suivante après clôture complète de la précédente et nouveau gate de sizing positif. Un Wallet Desk utile doit pouvoir lire le solde du wallet : `getBalance` fait donc partie des quatre canaris de la foundation `0.2.1`, avant Wallet. Les transports live arrivent ensuite ; Interface/Program restent préparés avant les couches de données décodées.
|
||||
@@ -448,17 +449,23 @@ rel.001 publication stable 0.2.6
|
||||
|
||||
La tranche `pre.014` est réservée aux défauts visuels/templating observés en usage réel, notamment les scrollbars occasionnelles du splashscreen ; son contenu précis sera borné à partir du retour opérateur avant implémentation. `ROADMAP.md` reste synthétique et `CHANGELOG.md` n'est synchronisé qu'à la phase documentaire finale.
|
||||
|
||||
## `0.2.7` — WebSocket Solana standard
|
||||
## `0.2.7` — `.kspwallet` binary persistence container
|
||||
|
||||
Mission : faire évoluer uniquement la couche de persistence de `ksp-wallet-lib` afin qu’un `.kspwallet` nouvellement écrit ne soit plus un document JSON directement lisible dans un éditeur texte ordinaire, sans prétendre ajouter de sécurité cryptographique par simple obfuscation.
|
||||
|
||||
Le gate `pre.001` doit comparer framing binaire custom, codecs binaires stables et éventuelle compression, puis fixer un conteneur explicitement versionné. Base64 seul est exclu comme solution : il reste textuel, trivialement réversible et augmente la taille. Le conteneur et le `format_version` logique/cryptographique sont séparés afin de conserver V1 pour les semantics VIEW/OWNER actuelles et de réserver les futurs formats logiques à de vraies évolutions d’autorisation. La lecture rétrocompatible des JSON V1 historiques et les migrations/test vectors sont obligatoires. Wallet Desk ne doit pas être modifié fonctionnellement ; ses tests/smokes sont rejoués comme preuve consommateur.
|
||||
|
||||
## `0.2.8` — WebSocket Solana standard
|
||||
|
||||
Mission : couvrir la surface WebSocket standard officielle ciblée.
|
||||
|
||||
Une URL peut avoir plusieurs sessions physiques ; une session peut avoir plusieurs subscriptions. Un pool automatique de sessions est reporté jusqu'à besoin concret.
|
||||
|
||||
## `0.2.8` — Helius LaserStream WebSocket
|
||||
## `0.2.9` — Helius LaserStream WebSocket
|
||||
|
||||
Mission : étendre le moteur WebSocket standard avec les opérations/filtres/capabilities Helius ciblés sans copier le client.
|
||||
|
||||
## `0.2.9` — Yellowstone gRPC standard
|
||||
## `0.2.10` — Yellowstone gRPC standard
|
||||
|
||||
Mission : introduire un backend Yellowstone standard/provider-neutral.
|
||||
|
||||
@@ -466,21 +473,21 @@ Le `pre.001` est un gate de sizing : inventorier toute la surface normative cibl
|
||||
|
||||
Les profiles/adapters Helius/Triton/ERPC/Chainstack/Shyft sont reportés après les priorités fondatrices.
|
||||
|
||||
## `0.2.10` / `0.2.11` — Off-chain price + app
|
||||
## `0.2.11` / `0.2.12` — Off-chain price + app
|
||||
|
||||
`0.2.10` introduit `ksp-offchain-transport-lib` avec au minimum SOL/USD et SOL/EUR via une abstraction indépendante du premier provider.
|
||||
`0.2.11` introduit `ksp-offchain-transport-lib` avec au minimum SOL/USD et SOL/EUR via une abstraction indépendante du premier provider.
|
||||
|
||||
`0.2.11` ajoute une petite application desk de visualisation/validation. Après stabilisation de cette application spécialisée, la même release doit intégrer la capacité de prix offchain dans `ksp-app-wallet-desk` sans dupliquer la récupération/normalisation appartenant au composant spécialisé.
|
||||
`0.2.12` ajoute une petite application desk de visualisation/validation. Après stabilisation de cette application spécialisée, la même release doit intégrer la capacité de prix offchain dans `ksp-app-wallet-desk` sans dupliquer la récupération/normalisation appartenant au composant spécialisé.
|
||||
|
||||
Metadata HTTP/IPFS/Arweave viendra au premier besoin Metadata réel.
|
||||
|
||||
## `0.2.12` — Interface foundation
|
||||
## `0.2.13` — Interface foundation
|
||||
|
||||
`ksp-interface-lib` devient la façade wire officielle et expose une API publique wire utilisable par les implementations officielles et externes.
|
||||
|
||||
Aucune `ksp-interface-api` séparée n'est retenue pour l'instant.
|
||||
|
||||
## `0.2.13` — Program API foundation
|
||||
## `0.2.14` — Program API foundation
|
||||
|
||||
Introduire `ksp-program-api`, sans suffixe `-lib`, comme contrat d'extension Program.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md -->
|
||||
<!-- version: 26 -->
|
||||
<!-- version: 27 -->
|
||||
|
||||
# Plan `0.1.4` — `ksp-app-config-desk`
|
||||
|
||||
@@ -233,7 +233,7 @@ Les artefacts frontend construits doivent être séparés des sources. Pour `ksp
|
||||
|
||||
Cette valeur est utilisée par `build.frontendDist` dans `tauri.conf.json`. `vite.config.ts` utilise la même destination comme `build.outDir` depuis son introduction en `pre.005`. Elle remplace le `../dist`/`../../dist` historique du gabarit bot3.
|
||||
|
||||
La gestion npm distingue le runtime frontend du tooling. Les bibliothèques consommées par le bundle appartiennent à `dependencies`; les outils de compilation/développement (`@tauri-apps/cli`, Vite, TypeScript, Sass) et les paquets `@types/*` appartiennent à `devDependencies`. npm est utilisé directement uniquement pour installer ou mettre à jour ces dépendances. Pour la première installation, l'opérateur se place dans `crates/ksp-app-config-desk`, exécute `npm i -D`, puis revient à la racine avec `cd ../../`. Le cycle normal de développement et de build passe ensuite par Tauri depuis la racine du workspace, avec configuration explicite de l'application : `cargo tauri dev -c crates/ksp-app-config-desk/tauri.conf.json` et `cargo tauri build -c crates/ksp-app-config-desk/tauri.conf.json`. Tauri déclenche lui-même `npm run dev` / `npm run build` au travers des hooks configurés. Aucun lockfile npm n'est versionné.
|
||||
La gestion npm distingue le runtime frontend du tooling. Les bibliothèques consommées par le bundle appartiennent à `dependencies`; les outils de compilation/développement (`@tauri-apps/cli`, Vite, TypeScript, Sass) et les paquets `@types/*` appartiennent à `devDependencies`. npm est utilisé directement uniquement pour installer ou mettre à jour ces dépendances. Pour la première installation, l'opérateur se place dans `crates/ksp-app-config-desk` et exécute `npm i -D`. Le cycle normal de développement et de build reste ensuite crate-local : `(cd crates/ksp-app-config-desk && cargo tauri dev)` et `(cd crates/ksp-app-config-desk && cargo tauri build)`. `-c/--config` n'est pas utilisé pour sélectionner l'application car Tauri le traite comme un overlay de configuration. Tauri déclenche lui-même `npm run dev` / `npm run build` au travers des hooks configurés. Aucun lockfile npm n'est versionné.
|
||||
|
||||
Chaque application Tauri desk KSP reçoit un couple de ports Vite/HMR propre. La première application réserve :
|
||||
|
||||
@@ -944,24 +944,25 @@ fade duration
|
||||
close delay si techniquement distinct
|
||||
```
|
||||
|
||||
`pre.008` fige les deux clés communes nécessaires au lifecycle de référence :
|
||||
`0.2.6-pre.014` aligne les deux Desks sur trois clés communes, en reprenant la séparation fonctionnelle du splash kbot3 :
|
||||
|
||||
```text
|
||||
KSP_DESK_SPLASH_FADE_IN_MS=300
|
||||
KSP_DESK_SPLASH_MINIMUM_MS=1200
|
||||
KSP_DESK_SPLASH_FADE_MS=300
|
||||
KSP_DESK_SPLASH_FADE_OUT_MS=300
|
||||
```
|
||||
|
||||
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.
|
||||
Elles sont résolues uniquement via `ConfigEnvironment` avec priorité process > `.env` > fallback. `KSP_DESK_SPLASH_MINIMUM_MS` est bornée à 60 000 ms et chaque fade à 10 000 ms. Une valeur invalide ne rend pas Config Desk inutilisable : l’application conserve les timings de fallback en mémoire et journalise seulement domaine/code de l’erreur.
|
||||
|
||||
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. La référence temporelle est la readiness frontend : Rust émet `fade_in`, attend `minimum`, émet `fade_out`, attend `fade`, puis affiche/focalise `main` et détruit `splash`. Aucun close delay distinct n’est nécessaire. Avec `minimum=12000` et `fade=3000`, le lifecycle backend attendu est donc proche de `15000 ms`, et non des trois délais indépendants historiques de bot3. `tw_splash` journalise en `debug` la provenance des valeurs, les attentes configurées et réellement observées ainsi que la durée totale. Les variables sont ajoutées à `.env.example` dans `pre.008`.
|
||||
Le frontend reçoit uniquement les durées d’animation nécessaires dans les ordres du splash; la durée minimale reste backend. Après readiness frontend, Rust émet `fade_in`, attend `fade_in`, maintient la splash pleinement visible pendant `minimum`, émet `fade_out`, attend `fade_out`, puis affiche/focalise `main` et détruit `splash`. Avec `fade_in=3000`, `minimum=12000` et `fade_out=3000`, le lifecycle backend attendu est proche de `18000 ms`. Le splash possède en outre le flux de messages généraux en bas et le flux diagnostic debug en haut repris du comportement kbot3.
|
||||
|
||||
### 13.3 Racine runtime en développement
|
||||
|
||||
Avec le workflow workspace `cargo tauri dev -c crates/ksp-app-config-desk/tauri.conf.json`, Tauri peut lancer le binaire Rust avec la crate applicative comme current working directory. Les defaults Config `config/`, `config/schemas/` et `.env` sont cependant des chemins de projet enracinés à la racine du workspace. En build debug, le launcher replace donc le CWD sur `env!("CARGO_MANIFEST_DIR")/../..` **avant** le bootstrap Config. Il ne lit aucune ressource KSP lui-même ; `ksp-config-lib` continue à gérer paths, `.env`, precedence et validation. Un runtime distribué ne doit pas dépendre de cette racine source et sera cadré séparément.
|
||||
Avec le workflow crate-local `(cd crates/ksp-app-config-desk && cargo tauri dev)`, Tauri lance le projet applicatif attendu et le binaire Rust démarre avec la crate applicative comme current working directory. Les defaults Config `config/`, `config/schemas/` et `.env` sont cependant des chemins de projet enracinés à la racine du workspace. En build debug, le launcher replace donc le CWD sur `env!("CARGO_MANIFEST_DIR")/../..` **avant** le bootstrap Config. Il ne lit aucune ressource KSP lui-même ; `ksp-config-lib` continue à gérer paths, `.env`, precedence et validation. Un runtime distribué ne doit pas dépendre de cette racine source et sera cadré séparément.
|
||||
|
||||
### 13.4 Header et navigation
|
||||
|
||||
Le logo de Config Desk porte déjà l’identité graphique KSP. Le header ne répète donc pas `KSP` en texte : il suit `Config Desk — <vue active>`. Les cinq commandes principales restent des tabs/pills alignées à droite tant que leur nombre reste compact ; les futures applications qui dépassent cette surface utiliseront un dropdown. Les clics/changements de tabs sont tracés (`trace` pour le clic/état technique, `debug` pour l’activation utilisateur significative).
|
||||
Le logo de Config Desk porte déjà l’identité graphique KSP. Le header ne répète donc pas `KSP` en texte : il suit `Config Desk — <vue active>`. Les cinq commandes principales utilisent désormais des pills verticales dans une sidebar du contenu principal, alignées avec Wallet Desk ; le header conserve uniquement l'identité fonctionnelle et le titre de la vue active. Les clics/changements de tabs sont tracés (`trace` pour le clic/état technique, `debug` pour l’activation utilisateur significative).
|
||||
|
||||
## 14. Présentation et Markdown
|
||||
|
||||
@@ -1029,7 +1030,7 @@ Le shell applicatif matérialise désormais cette architecture dans `frontend/ts
|
||||
|
||||
Le bootstrap Logging sépare également la **résolution du plan de démarrage** de l'**installation du subscriber**. Une source invalide peut ainsi être testée comme plan fallback sans initialiser le subscriber global du binaire de tests.
|
||||
|
||||
La validation frontend de `pre.018` utilise `npm run check` pour **`tsc --noEmit` uniquement**. Le build Vite de production n'est pas lancé séparément : `cargo tauri build` déclenche déjà `npm run build` via `beforeBuildCommand`. Pour KSP, le build Tauri est réservé à la **dernière validation**, après `fmt/check/clippy`, les tests, le type-check frontend et le parcours fonctionnel `tauri dev`.
|
||||
La convention desktop actuelle n’exécute plus de script npm de contrôle/dev/build directement côté opérateur. Tauri possède le cycle applicatif et déclenche `npm run dev` / `npm run build` via ses hooks crate-local. Le build Tauri reste réservé à la **dernière validation**, après `fmt/check/clippy`, les tests et le parcours fonctionnel `cargo tauri dev`.
|
||||
|
||||
## 16. Stratégie de tests
|
||||
|
||||
@@ -1200,7 +1201,7 @@ pre.007 frontend logging commun
|
||||
pre.008 shell main + splash de référence
|
||||
- tw_splash + tw_main
|
||||
- lifecycle splash configurable
|
||||
- KSP_DESK_SPLASH_MINIMUM_MS / KSP_DESK_SPLASH_FADE_MS + .env.example
|
||||
- KSP_DESK_SPLASH_FADE_IN_MS / KSP_DESK_SPLASH_MINIMUM_MS / KSP_DESK_SPLASH_FADE_OUT_MS + .env.example
|
||||
- normalisation du CWD debug vers la racine workspace avant bootstrap Config
|
||||
- navigation monofenêtre + header `Config Desk — <vue>`
|
||||
- helpers show/focus/destroy
|
||||
@@ -1456,3 +1457,10 @@ Aucune question n'empêche d'ouvrir le développement après validation du prés
|
||||
5. retour Rust -> console WebKit : étudié à la clôture `pre.019` puis **reporté hors `0.1.4`**. Le bridge WebView -> Rust -> KSP et le panneau Test Logging couvrent le besoin fonctionnel de cette release. Une future réflexion Rust -> WebKit devra rester sous ownership de `ksp-logging-lib`, sans second subscriber, double émission ni boucle avec le bridge `console.*`.
|
||||
|
||||
Ces points doivent être résolus par code/tests dans les prereleases prévues, pas par contournement applicatif.
|
||||
|
||||
|
||||
### Harmonisation desktop `0.2.6-pre.014`
|
||||
|
||||
Le polish partagé avec Wallet Desk déplace les pills de navigation Config dans une sidebar verticale du contenu principal, tout en conservant la palette claire historique de Config Desk. Les quatre fichiers HTML des deux Desks utilisent les en-têtes normalisés `file/version`. Le splash reprend les flux kbot3 (messages généraux en bas, diagnostics debug en haut) avec trois timings Config-owned distincts `KSP_DESK_SPLASH_FADE_IN_MS`, `KSP_DESK_SPLASH_MINIMUM_MS` et `KSP_DESK_SPLASH_FADE_OUT_MS`.
|
||||
|
||||
Le même chantier corrige le lancement Tauri multi-app : chaque app est lancée depuis sa crate, car `-c/--config` est un overlay et non un sélecteur de backend.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/plans/013-V0_2_6_WALLET_DESK_PLAN.md -->
|
||||
<!-- version: 30 -->
|
||||
<!-- version: 31 -->
|
||||
|
||||
# Plan `0.2.6` — Wallet Desk
|
||||
|
||||
@@ -1137,7 +1137,7 @@ cd ../..
|
||||
Le cycle normal de développement/validation frontend passe par Tauri depuis la racine du workspace :
|
||||
|
||||
```bash
|
||||
cargo tauri dev -c crates/ksp-app-wallet-desk/tauri.conf.json
|
||||
(cd crates/ksp-app-wallet-desk && cargo tauri dev)
|
||||
```
|
||||
|
||||
Tauri déclenche alors automatiquement le script configuré dans `beforeDevCommand`. Les contrôles fonctionnels frontend à forte valeur portent notamment sur :
|
||||
@@ -1754,7 +1754,7 @@ cd ../..
|
||||
Le parcours normal de développement et de validation fonctionnelle frontend passe par :
|
||||
|
||||
```bash
|
||||
cargo tauri dev -c crates/ksp-app-wallet-desk/tauri.conf.json
|
||||
(cd crates/ksp-app-wallet-desk && cargo tauri dev)
|
||||
```
|
||||
|
||||
Tauri exécute automatiquement le `beforeDevCommand` configuré. Aucun script npm applicatif de contrôle, développement ou build n'est lancé directement par l'opérateur.
|
||||
@@ -1768,11 +1768,11 @@ cargo check --workspace
|
||||
cargo clippy --workspace --all-targets
|
||||
cargo test -p ksp-app-wallet-desk
|
||||
cargo test --workspace
|
||||
cargo tauri dev -c crates/ksp-app-wallet-desk/tauri.conf.json
|
||||
cargo tauri build -c crates/ksp-app-wallet-desk/tauri.conf.json
|
||||
(cd crates/ksp-app-wallet-desk && cargo tauri dev)
|
||||
(cd crates/ksp-app-wallet-desk && cargo tauri build)
|
||||
```
|
||||
|
||||
`cargo tauri build -c crates/ksp-app-wallet-desk/tauri.conf.json` reste **la toute dernière opération** de validation. Il déclenche lui-même le build frontend via `beforeBuildCommand`. Le smoke Devnet opt-in est exécuté séparément avant cette dernière opération lorsqu'il appartient au checkpoint final.
|
||||
`(cd crates/ksp-app-wallet-desk && cargo tauri build)` reste **la toute dernière opération** de validation. Il déclenche lui-même le build frontend via `beforeBuildCommand`. Le smoke Devnet opt-in est exécuté séparément avant cette dernière opération lorsqu'il appartient au checkpoint final.
|
||||
|
||||
## 26. Hors périmètre confirmé
|
||||
|
||||
@@ -1793,11 +1793,11 @@ RPC direct depuis frontend
|
||||
prix offchain pendant 0.2.6
|
||||
```
|
||||
|
||||
## 27. TODO `0.2.11` — intégration prix offchain
|
||||
## 27. TODO `0.2.12` — intégration prix offchain
|
||||
|
||||
La visualisation de prix reste hors `0.2.6`.
|
||||
|
||||
Une application spécialisée doit d'abord valider les sources offchain, normalisation, cache, rafraîchissement et UX. Ensuite `0.2.11` intégrera cette capacité dans Wallet Desk via le composant partagé retenu, sans dupliquer la logique de récupération de prix.
|
||||
Une application spécialisée doit d'abord valider les sources offchain, normalisation, cache, rafraîchissement et UX. Ensuite `0.2.12` intégrera cette capacité dans Wallet Desk via le composant partagé retenu, sans dupliquer la logique de récupération de prix.
|
||||
|
||||
Aucune API fictive de prix n'est introduite dans Wallet Desk en `0.2.6`.
|
||||
|
||||
@@ -1839,3 +1839,36 @@ v0.2.6-pre.001-fix.002
|
||||
```
|
||||
|
||||
Après application/commit du fix documentaire, `pre.002` démarre la crate Tauri et le shell complet du gabarit, avec Bootstrap, Font Awesome, DataTables/Select, SimpleBar, resize-observer-polyfill, splash/main et logging bridge.
|
||||
|
||||
|
||||
## `pre.014` — polish desktop partagé et lancement Tauri multi-app
|
||||
|
||||
La tranche harmonise Config Desk et Wallet Desk sans modifier les contrats Wallet métier :
|
||||
|
||||
```text
|
||||
HTML : en-têtes Khadhroony file/version sur main + splash
|
||||
Splash : fade-in / minimum / fade-out distincts via Config
|
||||
Splash : messages généraux défilants en bas
|
||||
Splash debug : diagnostics défilants en haut uniquement en debug
|
||||
Splash : aucune scrollbar fenêtre, titre central non sélectionnable
|
||||
Main : pills/tabs dans une sidebar du contenu, pas dans le header
|
||||
Palette : Wallet Desk revient sur la palette claire de référence Config Desk
|
||||
Tauri : lancement crate-local obligatoire dans le workspace multi-app
|
||||
```
|
||||
|
||||
La cause du mélange observé Config-Vite / Wallet-backend est explicitement corrigée : `-c/--config` fusionne une configuration avec le projet Tauri déjà découvert ; il ne choisit pas la crate. Les commandes de validation visuelle sont donc :
|
||||
|
||||
```bash
|
||||
KSP_DESK_SPLASH_FADE_IN_MS=1500 \
|
||||
KSP_DESK_SPLASH_MINIMUM_MS=6000 \
|
||||
KSP_DESK_SPLASH_FADE_OUT_MS=1500 \
|
||||
bash -lc 'cd crates/ksp-app-config-desk && cargo tauri dev'
|
||||
|
||||
KSP_DESK_SPLASH_FADE_IN_MS=1500 \
|
||||
KSP_DESK_SPLASH_MINIMUM_MS=6000 \
|
||||
KSP_DESK_SPLASH_FADE_OUT_MS=1500 \
|
||||
KSP_WALLETS_DIRECTORY=var/wallet-desk-pre014 \
|
||||
bash -lc 'cd crates/ksp-app-wallet-desk && cargo tauri dev'
|
||||
```
|
||||
|
||||
Le build Tauri de production reste reporté à `pre.015` et doit demeurer l’absolue dernière opération.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/rules/RULES_KSP.md -->
|
||||
<!-- version: 36 -->
|
||||
<!-- version: 37 -->
|
||||
|
||||
# Règles spécifiques à KSP
|
||||
|
||||
@@ -224,15 +224,15 @@
|
||||
- **KSP-APP-023** — Le gabarit Tauri KSP place les sources web sous `frontend/`, les modules TypeScript sous `frontend/ts/`, les styles SCSS sous `frontend/sass/` et les bindings TS-RS générés sous `frontend/ts/bindings/`. Les bindings et artefacts générés ne sont pas versionnés ; les DTO Rust applicatifs restent la source des contrats TS-RS.
|
||||
- **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-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** — Dans un workspace Rust multi-app, `-c/--config` de Tauri est un overlay de configuration et ne sélectionne pas la crate applicative. Les commandes Tauri sont donc exécutées depuis le répertoire de la crate ciblée : `(cd crates/<app> && cargo tauri dev)` ou `(cd crates/<app> && cargo tauri build)`. Le lancement depuis la racine avec `-c crates/<app>/tauri.conf.json` est interdit comme sélecteur d’application.
|
||||
- **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.
|
||||
- **KSP-APP-028** — Le header d’une application desk ne répète pas inutilement l’identité déjà portée par son logo : il affiche le nom ou l’abréviation fonctionnelle de l’application, puis un tiret cadratin `—` et le titre de la vue active. Les commandes principales peu nombreuses peuvent utiliser des tabs/pills alignées à droite ; lorsque leur nombre nuit à la lisibilité ou à l’espace disponible, un dropdown est préféré.
|
||||
- **KSP-APP-029** — En développement workspace, une application desk Tauri normalise le current working directory du processus Rust vers la racine du workspace avant le bootstrap Config lorsque `cargo tauri ... -c crates/<app>/tauri.conf.json` lance le binaire depuis la crate. Cette adaptation ne lit ni ne parse directement `config/`, `.env` ou les variables `KSP_*`/`KSPB_*` : `ksp-config-lib` reste seul propriétaire de ces ressources. Le comportement de distribution/release reste défini séparément et ne doit pas dépendre d’un checkout source.
|
||||
- **KSP-APP-029** — En développement workspace, Tauri est lancé depuis `crates/<app>` conformément à `KSP-APP-026`. Le processus Rust peut ensuite normaliser son current working directory vers la racine du workspace avant le bootstrap Config lorsque les chemins de développement KSP y sont enracinés. Cette adaptation ne lit ni ne parse directement `config/`, `.env` ou les variables `KSP_*`/`KSPB_*` : `ksp-config-lib` reste seul propriétaire de ces ressources. Le runtime distribué ne doit pas dépendre d’un checkout source.
|
||||
- **KSP-APP-030** — Lorsqu’une application KSP persiste des logs applicatifs, chaque lancement doit disposer d’un fichier propre et non partagé avec un lancement précédent. Le nom encode au minimum l’identité applicative et un horodatage de démarrage, par exemple `app-name.YYYYMMDD-HHMMSS.log` / `.json` / `.jsonl` selon le format du sink ; une rotation quotidienne ne doit pas fusionner plusieurs exécutions applicatives dans le même fichier.
|
||||
- **KSP-APP-031** — Le niveau de logging spécifique à une application/crate peut être élevé temporairement à `debug` ou `trace` pendant une phase de développement ou correction. Avant la clôture/release de cette application/crate, son niveau de référence est ramené à `info` ou `warn` selon le besoin opératoire ; il n’est remonté que lorsqu’un développement/correctif est explicitement rouvert.
|
||||
- **KSP-APP-032** — Les interfaces desk KSP n’utilisent pas les dialogues bloquants natifs du navigateur (`window.alert`, `window.confirm`, `window.prompt`) pour les interactions applicatives normales. Les confirmations destructives ou privilégiées utilisent un modal Bootstrap intégré à l’UI, instrumenté par le bridge Logging ; toute exception doit être explicitement justifiée et documentée.
|
||||
- **KSP-APP-033** — Un test d’une application ou d’un manager qui peut modifier un document Config du workspace ne traite jamais les valeurs courantes de ce document comme une fixture immuable. Les tests de valeurs exactes utilisent une fixture isolée ; les tests qui lisent la Config workspace vérifient uniquement des invariants, la validité et la cohérence source → résolution → runtime afin de rester valides après une édition légitime par Config Desk.
|
||||
- **KSP-APP-034** — Pour une application Tauri KSP, npm n'est jamais invoqué directement pour lancer les scripts applicatifs de développement, contrôle ou build. Conformément à `KSP-APP-025`, les seules commandes npm directes sont celles nécessaires à l'installation ou à la mise à jour des dépendances déclarées dans `package.json` (`npm i -D` lors de l'installation initiale retenue par le gabarit, ou `npm update` lors d'une mise à jour). Le cycle applicatif passe par `cargo tauri dev -c crates/<app>/tauri.conf.json` et `cargo tauri build -c crates/<app>/tauri.conf.json`; Tauri déclenche lui-même les scripts configurés via `beforeDevCommand` et `beforeBuildCommand`. Dans une séquence de validation finale, `cargo tauri build -c crates/<app>/tauri.conf.json` est exécuté **en toute dernière opération**, après `fmt/check/clippy`, les tests et le parcours fonctionnel `cargo tauri dev`.
|
||||
- **KSP-APP-034** — Pour une application Tauri KSP, npm n’est jamais invoqué directement pour lancer les scripts applicatifs de développement, contrôle ou build. Les seules commandes npm directes servent à installer ou mettre à jour les dépendances déclarées. Le cycle applicatif est crate-local : `(cd crates/<app> && cargo tauri dev)` et `(cd crates/<app> && cargo tauri build)` ; Tauri déclenche lui-même les hooks `beforeDevCommand` / `beforeBuildCommand`, dont le `cwd` reste explicitement la crate. Dans une validation finale, le `cargo tauri build` crate-local est exécuté **en toute dernière opération**, après fmt/audit/check/clippy, tests et parcours fonctionnel.
|
||||
|
||||
## Data plane / control plane
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: prompts/000-README.md -->
|
||||
<!-- version: 17 -->
|
||||
<!-- version: 18 -->
|
||||
|
||||
# Prompts KSP
|
||||
|
||||
@@ -31,4 +31,6 @@ Le prompt générique `0.1.x` a été affiné pendant `0.0.3` puis remplacé par
|
||||
- [`008-V0_2_3_START_PROMPT.md`](008-V0_2_3_START_PROMPT.md) — prompt préparé par la dernière prerelease de `0.2.2`, destiné à ouvrir `0.2.3 — HTTP Transactions` après publication stable de `0.2.2`; il cible les 11 méthodes Transactions et impose un audit actuel ainsi que la politique no-resend des write submissions.
|
||||
- [`009-V0_2_4_START_PROMPT.md`](009-V0_2_4_START_PROMPT.md) — prompt préparé par `0.2.3-pre.009`, destiné à ouvrir `0.2.4 — HTTP Blocks + Economics + compliance HTTP finale` après publication stable de `0.2.3`; il cible les 15 wrappers restants et impose `KSP-TRANSPORT-007` ainsi qu'un nouvel audit/sizing à `pre.001`.
|
||||
- [`010-V0_2_5_START_PROMPT.md`](010-V0_2_5_START_PROMPT.md) — prompt préparé par `0.2.4-pre.009` puis finalisé en version 2 par `pre.009-fix.001`, destiné à ouvrir `0.2.5 — Wallet foundation` sur la base stable `v0.2.4`; il impose audit/threat-model/sizing avant choix cryptographiques et cadre `.kspwallet` interopérable, capacités indépendantes VIEW/OWNER, metadata protégées, key slots/rotations, signature, persistence atomique et import/export extensible sans `WalletPolicy`.
|
||||
- [`011-V0_2_6_START_PROMPT.md`](011-V0_2_6_START_PROMPT.md) — prompt préparé par `0.2.5-pre.010` puis renforcé pendant `pre.010-fix.001`–`fix.003`, destiné à ouvrir `0.2.6 — Wallet Desk` après le tag stable `v0.2.5`; il impose une première tranche audit/sizing, rappelle les règles Rust/audit structurel, cadre Config composite + Wallet + HTTP `getBalance`, lifecycle VIEW/OWNER, sécurité password/export, validation frontend/Tauri et conserve le TODO `0.2.11` d’intégration des prix offchain dans Wallet Desk après validation de la Price Desk spécialisée.
|
||||
- [`011-V0_2_6_START_PROMPT.md`](011-V0_2_6_START_PROMPT.md) — prompt préparé par `0.2.5-pre.010` puis renforcé pendant `pre.010-fix.001`–`fix.003`, destiné à ouvrir `0.2.6 — Wallet Desk` après le tag stable `v0.2.5`; il impose une première tranche audit/sizing, rappelle les règles Rust/audit structurel, cadre Config composite + Wallet + HTTP `getBalance`, lifecycle VIEW/OWNER, sécurité password/export, validation frontend/Tauri et conserve le TODO `0.2.12` d’intégration des prix offchain dans Wallet Desk après validation de la Price Desk spécialisée.
|
||||
|
||||
- [`012-V0_2_7_START_PROMPT.md`](012-V0_2_7_START_PROMPT.md) — prompt préparé pendant `0.2.6-pre.014`, destiné à ouvrir `0.2.7 — conteneur binaire .kspwallet` après publication stable de `0.2.6`; il sépare version de conteneur et `format_version` logique V1, exclut Base64 comme pseudo-sécurité, impose compatibilité JSON V1/migration/interop et utilise Wallet Desk comme canari consommateur sans modification fonctionnelle attendue.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: prompts/011-V0_2_6_START_PROMPT.md -->
|
||||
<!-- version: 3 -->
|
||||
<!-- version: 4 -->
|
||||
|
||||
# Prompt de démarrage `0.2.6` — Wallet Desk
|
||||
|
||||
@@ -387,7 +387,7 @@ Ne pas copier aveuglément les dépendances inutiles. Réutiliser les patterns
|
||||
|
||||
Le port de développement doit être réservé distinctement de Config Desk après audit des ports déjà utilisés.
|
||||
|
||||
Le build frontend standalone `npm run check` reste un type-check/lint/test, pas un production build. Le build production final est déclenché par Tauri via `beforeBuildCommand`.
|
||||
Aucun script npm de contrôle/dev/build n’est lancé directement par l’opérateur. Tauri possède le cycle frontend et déclenche ses hooks npm ; le build production final reste déclenché via `beforeBuildCommand`.
|
||||
|
||||
## 13. Logging
|
||||
|
||||
@@ -512,7 +512,6 @@ Couvrir au minimum :
|
||||
Au minimum :
|
||||
|
||||
```text
|
||||
npm run check
|
||||
```
|
||||
|
||||
Ajouter des tests ciblés seulement lorsque leur valeur est démontrée ; ne pas construire le bundle production dans ce script.
|
||||
@@ -621,13 +620,12 @@ cargo check --workspace
|
||||
cargo clippy --workspace --all-targets
|
||||
cargo test -p ksp-app-wallet-desk
|
||||
cargo test --workspace
|
||||
npm run check --prefix crates/ksp-app-wallet-desk
|
||||
```
|
||||
|
||||
Le build Tauri de production est la **dernière** opération de validation :
|
||||
|
||||
```bash
|
||||
cargo tauri build -c crates/ksp-app-wallet-desk/tauri.conf.json
|
||||
(cd crates/ksp-app-wallet-desk && cargo tauri build)
|
||||
```
|
||||
|
||||
Le smoke Devnet opt-in, s'il est livré, est exécuté séparément avec une commande documentée.
|
||||
@@ -654,7 +652,7 @@ Le gate n'est considéré franchi que si ces livrables sont écrits/consignés d
|
||||
## 24. Hors périmètre
|
||||
|
||||
```text
|
||||
modification du wire .kspwallet V1 sans défaut démontré
|
||||
modification du format logique/cryptographique .kspwallet V1 sans défaut démontré (le conteneur de persistence est traité séparément en 0.2.7)
|
||||
WalletPolicy / execution policy
|
||||
construction/simulation/envoi de transaction
|
||||
WebSocket/gRPC
|
||||
@@ -668,11 +666,11 @@ trading
|
||||
|
||||
Le Wallet Desk valide et administre Wallet ; il ne devient pas l'application globale KSP.
|
||||
|
||||
### TODO futur `0.2.11` — prix offchain dans Wallet Desk
|
||||
### TODO futur `0.2.12` — prix offchain dans Wallet Desk
|
||||
|
||||
La visualisation de prix offchain n'appartient pas au scope de `0.2.6`. Une application spécialisée de visualisation de prix offchain doit d'abord être réalisée et valider durablement ses sources, contrats, rafraîchissement, cache et UX.
|
||||
|
||||
Pour `0.2.11`, prévoir explicitement un chantier d'intégration de cette capacité dans `ksp-app-wallet-desk` afin qu'un wallet puisse afficher les informations de prix offchain pertinentes sans dupliquer la logique de récupération/normalisation possédée par le composant spécialisé. Le futur travail devra réauditer la frontière entre application, bibliothèque/service partagé et Transport avant implémentation ; le TODO n'autorise pas l'ajout anticipé de logique de prix dans Wallet Desk pendant `0.2.6`.
|
||||
Pour `0.2.12`, prévoir explicitement un chantier d'intégration de cette capacité dans `ksp-app-wallet-desk` afin qu'un wallet puisse afficher les informations de prix offchain pertinentes sans dupliquer la logique de récupération/normalisation possédée par le composant spécialisé. Le futur travail devra réauditer la frontière entre application, bibliothèque/service partagé et Transport avant implémentation ; le TODO n'autorise pas l'ajout anticipé de logique de prix dans Wallet Desk pendant `0.2.6`.
|
||||
|
||||
## 25. Résultat attendu de `0.2.6`
|
||||
|
||||
|
||||
268
prompts/012-V0_2_7_START_PROMPT.md
Normal file
268
prompts/012-V0_2_7_START_PROMPT.md
Normal file
@@ -0,0 +1,268 @@
|
||||
<!-- file: prompts/012-V0_2_7_START_PROMPT.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Prompt de démarrage `0.2.7` — conteneur binaire `.kspwallet`
|
||||
|
||||
## 1. Contexte de reprise
|
||||
|
||||
La base attendue est la release stable :
|
||||
|
||||
```text
|
||||
v0.2.6
|
||||
```
|
||||
|
||||
`0.2.5` a stabilisé `ksp-wallet-lib` et le format logique/cryptographique `.kspwallet` V1 : capacités VIEW/OWNER indépendantes, Argon2id, XChaCha20-Poly1305, autorité OWNER Ed25519, persistence atomique/no-clobber, rotations, révocation VIEW forte, signature et transferts Solana CLI JSON/Base58.
|
||||
|
||||
`0.2.6` a validé cette surface au travers de `ksp-app-wallet-desk` sans déplacer la propriété du format ou des secrets hors de Wallet.
|
||||
|
||||
La release à ouvrir est :
|
||||
|
||||
```text
|
||||
0.2.7 — conteneur binaire de persistence .kspwallet
|
||||
```
|
||||
|
||||
La première tranche est `0.2.7-pre.001` et commence obligatoirement par **audit + threat-model + options de framing/codec + compatibilité/migration + sizing**, avant toute modification du wire de persistence.
|
||||
|
||||
## 2. Motivation
|
||||
|
||||
Le document `.kspwallet` V1 actuellement persisté est un JSON strict. Ses valeurs sensibles restent cryptographiquement protégées, mais le fichier peut être ouvert et lu comme texte par un éditeur ordinaire.
|
||||
|
||||
L'objectif de `0.2.7` est de rendre la **persistence physique** binaire et explicitement framed, sans prétendre que cette transformation ajoute une protection cryptographique supplémentaire.
|
||||
|
||||
### Non-solution explicite : Base64 seul
|
||||
|
||||
Base64 seul n'est pas une solution acceptable :
|
||||
|
||||
- il reste textuel/ASCII ;
|
||||
- il est trivialement réversible ;
|
||||
- il ne fournit aucune confidentialité ni intégrité supplémentaire ;
|
||||
- il augmente typiquement la taille d'environ un tiers.
|
||||
|
||||
Le changement recherché est un vrai **conteneur binaire de persistence**, pas une obfuscation textuelle.
|
||||
|
||||
## 3. Séparation normative : conteneur vs format logique
|
||||
|
||||
Ne pas consommer artificiellement `format_version = 2` uniquement parce que la persistence devient binaire.
|
||||
|
||||
Distinguer explicitement :
|
||||
|
||||
```text
|
||||
container/framing version
|
||||
!=
|
||||
wallet logical/cryptographic format_version
|
||||
```
|
||||
|
||||
Le payload logique V1 doit pouvoir conserver ses invariants actuels :
|
||||
|
||||
```text
|
||||
VIEW / OWNER
|
||||
KDF / salts / slots
|
||||
AEAD / nonces / AAD
|
||||
OWNER signature/transcript
|
||||
metadata/secret compartments
|
||||
Solana keypair identity
|
||||
rotation / strong VIEW administration
|
||||
```
|
||||
|
||||
Un futur `format_version >= 2` reste disponible pour de vraies évolutions d'autorisation/crypto, notamment second facteur.
|
||||
|
||||
## 4. Audit obligatoire `pre.001`
|
||||
|
||||
Comparer au minimum :
|
||||
|
||||
1. framing binaire custom KSP avec grammar explicite ;
|
||||
2. codecs binaires Serde adaptés et stables ;
|
||||
3. formats self-describing éventuels ;
|
||||
4. éventuelle compression du payload si elle apporte une vraie valeur ;
|
||||
5. impact interop externe Rust/Python/Go/C ;
|
||||
6. complexité de parsing borné et rejet des inputs adversariaux ;
|
||||
7. dépendances nouvelles et leur maturité/maintenance ;
|
||||
8. capacité à préserver les test vectors cryptographiques V1 ;
|
||||
9. migration et détection sûre du JSON V1 legacy ;
|
||||
10. règles d'unknown container version / taille / trailing bytes / canonicalité.
|
||||
|
||||
Ne choisir aucun codec uniquement parce qu'il est pratique dans Rust : le format doit rester spécifiable indépendamment de l'implémentation.
|
||||
|
||||
## 5. Compatibilité obligatoire
|
||||
|
||||
`ksp-wallet-lib` doit rester capable de lire les `.kspwallet` JSON V1 déjà créés.
|
||||
|
||||
Le candidat doit définir explicitement :
|
||||
|
||||
```text
|
||||
legacy JSON V1 read
|
||||
new binary container read
|
||||
new binary container write default
|
||||
migration explicite legacy -> binary
|
||||
no-clobber / atomic replace
|
||||
rollback/interruption behavior
|
||||
```
|
||||
|
||||
Une simple ouverture d'un legacy wallet ne doit pas le réécrire silencieusement sans contrat explicite.
|
||||
|
||||
## 6. Sécurité
|
||||
|
||||
Le conteneur binaire n'est **pas** présenté comme une nouvelle couche de chiffrement.
|
||||
|
||||
Le threat model V1 reste notamment :
|
||||
|
||||
- attaquant possédant le fichier ;
|
||||
- connaissance complète de la spec ;
|
||||
- essais offline illimités sur les passwords ;
|
||||
- connaissance éventuelle d'un seul credential VIEW ou OWNER ;
|
||||
- capacité à modifier/remplacer/rollback le fichier.
|
||||
|
||||
Les gains de `0.2.7` sont :
|
||||
|
||||
```text
|
||||
framing déterministe
|
||||
non-textual persistence
|
||||
parsing/versioning physique explicite
|
||||
meilleure séparation container/payload
|
||||
préparation des futures migrations
|
||||
```
|
||||
|
||||
Pas :
|
||||
|
||||
```text
|
||||
obscurity = security
|
||||
Base64 = encryption
|
||||
binaire = protection contre attaque offline
|
||||
```
|
||||
|
||||
## 7. Frontière `ksp-wallet-lib`
|
||||
|
||||
Le changement doit rester possédé par `ksp-wallet-lib`.
|
||||
|
||||
`ksp-app-wallet-desk` ne doit pas connaître :
|
||||
|
||||
- le codec du conteneur ;
|
||||
- sa magic/version physique ;
|
||||
- son layout ;
|
||||
- ses offsets ;
|
||||
- la distinction legacy/new lors des opérations ordinaires.
|
||||
|
||||
Les APIs de haut niveau Wallet doivent absorber cette évolution autant que possible.
|
||||
|
||||
## 8. Wallet Desk comme canari consommateur
|
||||
|
||||
Aucune modification fonctionnelle de `ksp-app-wallet-desk` n'est attendue uniquement pour ce changement de persistence.
|
||||
|
||||
Après modification de Wallet, rejouer au minimum :
|
||||
|
||||
```bash
|
||||
cargo test -p ksp-wallet-lib
|
||||
cargo test -p ksp-app-wallet-desk
|
||||
cargo test --workspace
|
||||
```
|
||||
|
||||
Puis un parcours fonctionnel Wallet Desk doit prouver au minimum :
|
||||
|
||||
```text
|
||||
inventory
|
||||
create
|
||||
locked inspect
|
||||
VIEW unlock
|
||||
OWNER unlock
|
||||
metadata
|
||||
rotation
|
||||
strong VIEW disable/recreate
|
||||
export/import
|
||||
balance HTTP
|
||||
```
|
||||
|
||||
Si le Desk doit être modifié pour comprendre le conteneur physique, considérer cela comme un signal d'abstraction insuffisante et réauditer la frontière Wallet avant d'accepter le changement.
|
||||
|
||||
## 9. Interop et test vectors
|
||||
|
||||
Mettre à jour la spec `.kspwallet` avec une grammar byte-level du conteneur :
|
||||
|
||||
- magic ;
|
||||
- version du conteneur ;
|
||||
- longueur(s) ;
|
||||
- codec/payload kind ;
|
||||
- endianess ;
|
||||
- bornes ;
|
||||
- trailing bytes ;
|
||||
- canonicalité ;
|
||||
- unknown-version policy.
|
||||
|
||||
Conserver les test vectors cryptographiques V1 lorsque les transcripts/AAD ne changent pas, et ajouter des fixtures déterministes container + payload.
|
||||
|
||||
Prévoir au moins un parseur externe de canari afin d'éviter un format binaire « Rust-only » accidentel.
|
||||
|
||||
## 10. Futur `format_version >= 2` / second facteur
|
||||
|
||||
Ce chantier **ne réalise pas** password + OTP/2FA.
|
||||
|
||||
Pour une future version logique :
|
||||
|
||||
- `ksp-wallet-lib` possède le format, les facteurs, challenges et vérifications ;
|
||||
- un client interactif tel que Wallet Desk devra évoluer lorsqu'un OTP, enrollment/recovery, hardware/WebAuthn ou consentement externe doit être saisi/présenté ;
|
||||
- un seed TOTP enfermé uniquement dans le même fichier ne doit pas être assimilé automatiquement à un second facteur indépendant contre un attaquant qui possède ce fichier.
|
||||
|
||||
Le conteneur `0.2.7` doit simplement laisser cette évolution possible sans la pré-concevoir excessivement.
|
||||
|
||||
## 11. Dépendances
|
||||
|
||||
Toute nouvelle dépendance externe commune est déclarée dans `[workspace.dependencies]` puis consommée avec `.workspace = true`.
|
||||
|
||||
Avant ajout :
|
||||
|
||||
- vérifier la version actuelle ;
|
||||
- auditer maintenance/licence/graphe ;
|
||||
- éviter les générations obsolètes inutiles ;
|
||||
- vérifier qu'une petite grammar custom n'est pas plus durable qu'un codec lourd, ou inversement.
|
||||
|
||||
## 12. Discipline Rust / tests
|
||||
|
||||
Conserver toutes les règles KSP actuelles : Rust 2024, async-first, pas de `unsafe`, `unwrap`, `expect`, `panic` runtime, audits structurels, dépendance Logging via `ksp-logging-lib`, Config hors Wallet.
|
||||
|
||||
Validation générale :
|
||||
|
||||
```bash
|
||||
cargo fmt --all
|
||||
python3 scripts/audit_rust_workspace_rules.py
|
||||
cargo check --workspace
|
||||
cargo clippy --workspace --all-targets
|
||||
cargo test -p ksp-wallet-lib
|
||||
cargo test -p ksp-app-wallet-desk
|
||||
cargo test --workspace
|
||||
```
|
||||
|
||||
## 13. Forecast souple à recalibrer en `pre.001`
|
||||
|
||||
Point de départ recommandé :
|
||||
|
||||
```text
|
||||
pre.001 audit framing/codec/threat-model/interop/sizing
|
||||
pre.002 spec container + parser bounded + fixtures
|
||||
pre.003 writer binary + persistence integration
|
||||
pre.004 legacy JSON read + migration explicite
|
||||
pre.005 adversarial/interop/test vectors
|
||||
pre.006 Wallet Desk regression + live functional smoke
|
||||
pre.007 compliance/docs/prompt suivant
|
||||
rel.001
|
||||
```
|
||||
|
||||
Ce découpage est indicatif ; le gate `pre.001` doit le recalibrer selon le choix de format réel.
|
||||
|
||||
## 14. Résultat attendu
|
||||
|
||||
À la clôture stable :
|
||||
|
||||
```text
|
||||
les nouveaux .kspwallet sont persistés dans un conteneur binaire spécifié
|
||||
les anciens JSON V1 restent lisibles
|
||||
aucune sécurité fictive n'est attribuée au binaire/Base64
|
||||
les semantics cryptographiques V1 restent intactes sauf défaut démontré
|
||||
les migrations sont explicites et testées
|
||||
Wallet Desk fonctionne sans connaissance du format physique
|
||||
interop externe et parsing adversarial sont prouvés
|
||||
```
|
||||
|
||||
## 15. Instruction d'ouverture
|
||||
|
||||
Commencer par relire la spec `.kspwallet` V1, les modules `wire`, `transcript`, `persistence`, `wallet`, `owner`, `transfer` et tous les test vectors actuels.
|
||||
|
||||
Ensuite produire l'audit `0.2.7-pre.001` avant de coder le nouveau conteneur.
|
||||
Reference in New Issue
Block a user