v0.4.7-pre.009
This commit is contained in:
@@ -1,8 +1,32 @@
|
||||
<!-- file: kb-app-demo-desktop/CHANGELOG.md -->
|
||||
<!-- version: 7 -->
|
||||
<!-- version: 10 -->
|
||||
|
||||
# CHANGELOG — kb-app-demo-desktop
|
||||
|
||||
## 0.4.7-pre.009-fix-002
|
||||
|
||||
- correction du panneau qui présentait des contrats Devnet sans exécuter de commande réseau ;
|
||||
- chargement explicite du profil Devnet et préparation réelle de sa base PostgreSQL ;
|
||||
- affichage du contexte de profil et du rapport de readiness dans le JSON viewer commun ;
|
||||
- marquage explicite de l'absence de simulation et de preuves réseau ;
|
||||
- consolidation du rapport de validation `pre.009` et suppression du rapport de correctif dupliqué.
|
||||
|
||||
## 0.4.7-pre.009-fix-001
|
||||
|
||||
- renommage de la fenêtre en `demo_execution_metadata` ;
|
||||
- ajout des accordéons extensibles par famille et mode ;
|
||||
- réutilisation du JSON viewer partagé ;
|
||||
- ajout des inventaires Devnet Metaplex en parallèle des scénarios synthétiques ;
|
||||
- alignement des versions Tauri et npm sur `0.4.7`.
|
||||
|
||||
## 0.4.7-pre.009 — panneau Exécution Metadata Devnet
|
||||
|
||||
- ajout d'une fenêtre dédiée aux scénarios Exécution Metadata ;
|
||||
- adaptateurs Tauri minces vers `kb-pipeline-demo-scenarios` ;
|
||||
- affichage des opérations, exigences de préflight et preuves attendues ;
|
||||
- restauration du scénario sélectionné lors de la réouverture de la fenêtre ;
|
||||
- aucune logique métier ni soumission automatique dans le desktop.
|
||||
|
||||
## 0.4.6
|
||||
|
||||
- alignement de la crate sur la version fonctionnelle bot3 `0.4.6` ;
|
||||
|
||||
@@ -1,51 +1,35 @@
|
||||
<!-- file: kb-app-demo-desktop/README.md -->
|
||||
<!-- version: 1 -->
|
||||
<!-- version: 2 -->
|
||||
|
||||
# kb-app-demo-desktop
|
||||
|
||||
`kb-app-demo-desktop` est l’application Tauri de démonstration et de validation opérateur de `khadhroony-bot3`.
|
||||
`kb-app-demo-desktop` est l’application Tauri de démonstration, de diagnostic et de validation opérateur de `khadhroony-bot3`.
|
||||
|
||||
La crate reste volontairement mixte :
|
||||
La crate reste un package mixte bibliothèque et binaire. Elle fournit les fenêtres et adaptateurs nécessaires pour utiliser les capacités réutilisables du workspace sans déplacer leur logique métier dans l’interface.
|
||||
|
||||
- bibliothèque `kb_app_demo_desktop_lib` ;
|
||||
- binaire `kb-app-demo-desktop`.
|
||||
## Fonctionnalités
|
||||
|
||||
## Responsabilités
|
||||
|
||||
- démarrage Tauri et gestion d’une instance unique ;
|
||||
- chargement de la configuration et du logging ;
|
||||
- fenêtres de diagnostic et démonstration ;
|
||||
- commandes Tauri pour HTTP, WebSocket, SQL, backfill, extraction Core et replay ;
|
||||
- commandes de simulation et exécution Devnet ;
|
||||
- adaptation TS-RS des résultats des crates réutilisables ;
|
||||
- gestion de progression, annulation et état applicatif.
|
||||
- configuration et diagnostics de logging ;
|
||||
- transport HTTP JSON-RPC et WebSocket ;
|
||||
- diagnostics PostgreSQL, requêtes et replay candidates ;
|
||||
- backfill, extraction Core et replay de décodage ;
|
||||
- démonstrations SPL Token, Token-2022 et ATA ;
|
||||
- exécution Devnet Solana Core, SPL et metadata ;
|
||||
- affichage des plans, simulations, confirmations, postconditions et matérialisations ;
|
||||
- progression, annulation et restauration de l’état des fenêtres.
|
||||
|
||||
## Frontière architecturale
|
||||
|
||||
La logique générique reste dans :
|
||||
La logique générique reste dans `kb-lib`, `kb-pipeline`, `kb-pipeline-demo-scenarios`, `kb-onchain-transport`, `kb-store` et `kb-wallet`. Le desktop conserve uniquement les commandes Tauri minces, les modèles TS-RS et la présentation opérateur.
|
||||
|
||||
- `kb-pipeline-demo-scenarios` pour les scénarios réutilisables ;
|
||||
- `kb-pipeline` pour l’orchestration ;
|
||||
- `kb-onchain-transport` pour les communications réseau ;
|
||||
- `kb-store` pour PostgreSQL ;
|
||||
- `kb-lib` pour décodage, exécution et matérialisation ;
|
||||
- `kb-wallet` pour les signers locaux.
|
||||
|
||||
Le desktop ne doit pas devenir l’unique endroit où un scénario fonctionnel existe.
|
||||
|
||||
## Validation frontend
|
||||
|
||||
La validation autorisée est :
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
cargo tauri dev -c kb-app-demo-desktop/tauri.conf.json
|
||||
```
|
||||
|
||||
Ne pas utiliser `npm --prefix kb-app-demo-desktop run build` comme validation du desktop.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [USAGE.md](USAGE.md)
|
||||
- [TODO.md](TODO.md)
|
||||
- [CHANGELOG.md](CHANGELOG.md)
|
||||
- [Scénarios réutilisables](../kb-pipeline-demo-scenarios/README.md)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
## `0.4.7`
|
||||
|
||||
- [ ] Metaplex Token Metadata - ajouter les panneaux nécessaires aux scénarios complétés.
|
||||
- [ ] Exécution Metadata Devnet - ajouter les panneaux nécessaires aux scénarios complétés.
|
||||
|
||||
## Versions ultérieures
|
||||
|
||||
@@ -17,3 +17,5 @@
|
||||
|
||||
- [ ] Registre ElGamal - confirmer le déploiement réseau et la disponibilité des preuves avant de rendre le panneau exécutable.
|
||||
- [ ] Registre ElGamal - raccorder un handler uniquement après validation d’un scénario réutilisable hors desktop.
|
||||
|
||||
- Validation manuelle Metaplex — exécuter les parcours réseau disponibles et confirmer la cohérence préflight/simulation/postconditions pendant `0.4.7-pre.010`.
|
||||
|
||||
@@ -244,3 +244,19 @@ La base validée de `pre.062` contient 117 tests pour cette crate.
|
||||
- le desktop est une application de démonstration et validation, pas le worker de production ;
|
||||
- les payloads Tauri sont une API applicative spécifique au frontend ;
|
||||
- l’application nécessite un environnement graphique pour sa validation complète.
|
||||
|
||||
## Ouvrir le panneau Exécution Metadata Devnet
|
||||
|
||||
Depuis la fenêtre principale, choisir **Exécution Devnet → Exécution Metadata Devnet**. Le panneau charge l’inventaire canonique par la commande Tauri `demo_execution_metadata_scenarios`, puis affiche pour chaque scénario :
|
||||
|
||||
- les codes d’opération ;
|
||||
- les exigences de collection et de rule set ;
|
||||
- les contrôles de préflight ;
|
||||
- la politique simulation-first ;
|
||||
- les preuves de postcondition requises.
|
||||
|
||||
Le scénario sélectionné est conservé dans le stockage local de la WebView et restauré à la réouverture.
|
||||
|
||||
## Exécution metadata Devnet
|
||||
|
||||
Depuis **Exécution Devnet → Metadata**, ouvrir la fenêtre `demo_execution_metadata`. L’accordéon Metaplex sépare les scénarios synthétiques des scénarios Devnet et conserve une structure extensible pour SPL Token Metadata. Les résultats JSON sont rendus avec le composant `json_viewer` partagé.
|
||||
|
||||
@@ -16,10 +16,11 @@
|
||||
"demo_core_extraction",
|
||||
"demo_decode_replay",
|
||||
"demo_execution_solana_core",
|
||||
"demo_execution_spl"
|
||||
"demo_execution_spl",
|
||||
"demo_execution_metadata"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"tracing:default"
|
||||
]
|
||||
}
|
||||
}
|
||||
104
kb-app-demo-desktop/frontend/demo_execution_metadata.html
Normal file
104
kb-app-demo-desktop/frontend/demo_execution_metadata.html
Normal file
@@ -0,0 +1,104 @@
|
||||
<!-- file: kb-app-demo-desktop/frontend/demo_execution_metadata.html -->
|
||||
<!-- version: 3 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<link rel="icon" href="imgs/logo.png">
|
||||
<link rel="stylesheet" href="sass/main.scss">
|
||||
<title>Khadhroony Bot3 — Exécution Metadata Devnet</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="app-header">
|
||||
<nav class="navbar h-100 py-0 bg-dark text-light">
|
||||
<div class="container-fluid px-4">
|
||||
<div class="navbar-brand d-flex align-items-center"><img alt="Logo" src="imgs/logo.png" class="app-logo"><span class="ps-2 fs-4 fw-bold text-primary font-logo">Bot3 — <span class="badge text-bg-primary">Exécution Metadata Devnet</span></span></div>
|
||||
<span class="badge text-bg-success" id="metadataStatusBadge">Prêt</span>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
<main class="app-main">
|
||||
<div class="osb-scrollable pt-1 pb-4" data-simplebar>
|
||||
<div class="container-fluid px-4 py-4">
|
||||
<div class="alert alert-info">Cette fenêtre accueille les familles d’exécution metadata. Metaplex Token Metadata est la première famille ; SPL Token Metadata sera ajouté ultérieurement sans changer la frontière de la fenêtre.</div>
|
||||
<div class="row g-4">
|
||||
<div class="col-12 col-xl-5">
|
||||
<div class="accordion shadow-sm" id="metadataExecutionAccordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button" data-bs-target="#metaplexMetadataCollapse" data-bs-toggle="collapse" type="button">Metaplex Token Metadata</button>
|
||||
</h2>
|
||||
<div class="accordion-collapse collapse show" id="metaplexMetadataCollapse" data-bs-parent="#metadataExecutionAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="accordion" id="metaplexScenarioModeAccordion">
|
||||
<div class="accordion-item">
|
||||
<h3 class="accordion-header">
|
||||
<button class="accordion-button" data-bs-target="#metaplexSyntheticCollapse" data-bs-toggle="collapse" type="button">Scénarios synthétiques</button>
|
||||
</h3>
|
||||
<div class="accordion-collapse collapse show" id="metaplexSyntheticCollapse" data-bs-parent="#metaplexScenarioModeAccordion">
|
||||
<div class="accordion-body"><select class="form-select" id="metadataSyntheticScenarioSelect"></select></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h3 class="accordion-header">
|
||||
<button class="accordion-button collapsed" data-bs-target="#metaplexDevnetCollapse" data-bs-toggle="collapse" type="button">Scénarios Devnet</button>
|
||||
</h3>
|
||||
<div class="accordion-collapse collapse" id="metaplexDevnetCollapse" data-bs-parent="#metaplexScenarioModeAccordion">
|
||||
<div class="accordion-body">
|
||||
<label class="form-label" for="metadataProfileSelect">Profil Devnet</label>
|
||||
<select class="form-select mb-3" id="metadataProfileSelect"></select>
|
||||
<label class="form-label" for="metadataDevnetScenarioSelect">Contrat de scénario</label>
|
||||
<select class="form-select" id="metadataDevnetScenarioSelect"></select>
|
||||
<div class="d-grid mt-3"><button class="btn btn-outline-primary" id="prepareMetadataProfileButton" type="button">Charger le profil et préparer la base</button></div>
|
||||
<div class="d-flex justify-content-between align-items-center mt-3"><span class="small text-muted">État du profil</span><span class="badge text-bg-secondary" id="metadataProfileStatusBadge">Non chargé</span></div>
|
||||
<div class="mt-3" id="metadataProfileOutput"></div>
|
||||
<div class="alert alert-warning mt-3 mb-0">Cette version prépare réellement le profil et sa base Devnet, mais n’exécute pas encore de transaction Metaplex. Les contrats affichés ne constituent pas une preuve de simulation réseau.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-grid mt-3">
|
||||
<button class="btn btn-primary" id="refreshMetadataScenariosButton" type="button">Recharger les contrats</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card shadow-sm border-0 mt-4">
|
||||
<div class="card-body">
|
||||
<h2 class="h5">Exigences</h2>
|
||||
<dl class="row mb-0" id="metadataRequirements"></dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-xl-7">
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-body">
|
||||
<h2 class="h5">Préflight et opérations</h2>
|
||||
<div id="metadataPreflightOutput"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-body">
|
||||
<h2 class="h5">Simulation et confirmation</h2>
|
||||
<div id="metadataSimulationOutput"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<h2 class="h5">Postconditions et preuves</h2>
|
||||
<div id="metadataPostconditionsOutput"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<script type="module" src="ts/demo_execution_metadata.ts"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: kb-app-demo-desktop/frontend/main.html -->
|
||||
<!-- version: 9 -->
|
||||
<!-- version: 11 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
</li>
|
||||
<li><a id="openDemoExecutionSolanaCoreLink" class="dropdown-item" href="#">Solana Core</a></li>
|
||||
<li><a id="openDemoExecutionSplLink" class="dropdown-item" href="#">SPL</a></li>
|
||||
<li><a id="openDemoExecutionMetadataLink" class="dropdown-item" href="#">Exécution Metadata Devnet</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
166
kb-app-demo-desktop/frontend/ts/demo_execution_metadata.ts
Normal file
166
kb-app-demo-desktop/frontend/ts/demo_execution_metadata.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
// file: kb-app-demo-desktop/frontend/ts/demo_execution_metadata.ts
|
||||
// version: 3
|
||||
import "bootstrap";
|
||||
import "simplebar";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { frontendError } from "./frontend_log.ts";
|
||||
import { installFrontendConsoleBridge } from "./frontend_log.ts";
|
||||
import { renderJsonViewer } from "./json_viewer.ts";
|
||||
import type { DemoExecutionMetadataScenarioPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataScenarioPayload.ts";
|
||||
import type { DemoExecutionDevnetStoreReadinessPayload } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionDevnetStoreReadinessPayload.ts";
|
||||
import type { DemoExecutionSolanaCoreOptionsPayload } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreOptionsPayload.ts";
|
||||
|
||||
const storageKey = "kb-app-demo-desktop.metadata.selected-scenario";
|
||||
const profileStorageKey = "kb-app-demo-desktop.metadata.selected-profile";
|
||||
let scenarios: DemoExecutionMetadataScenarioPayload[] = [];
|
||||
let selectedProfileName: string | null = null;
|
||||
|
||||
function element<T extends HTMLElement>(selector: string): T {
|
||||
const value = document.querySelector<T>(selector);
|
||||
if (value === null) {
|
||||
throw new Error(`Élément introuvable : ${selector}`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function activeSelect(): HTMLSelectElement {
|
||||
return document.querySelector("#metaplexDevnetCollapse.show") !== null ? element<HTMLSelectElement>("#metadataDevnetScenarioSelect") : element<HTMLSelectElement>("#metadataSyntheticScenarioSelect");
|
||||
}
|
||||
|
||||
function selectedScenario(): DemoExecutionMetadataScenarioPayload | null {
|
||||
const id = activeSelect().value;
|
||||
return scenarios.find(scenario => scenario.id === id) ?? null;
|
||||
}
|
||||
|
||||
function fillSelect(selector: string, mode: string): void {
|
||||
const select = element<HTMLSelectElement>(selector);
|
||||
select.replaceChildren();
|
||||
for (const scenario of scenarios.filter(candidate => candidate.mode === mode)) {
|
||||
const option = document.createElement("option");
|
||||
option.value = scenario.id;
|
||||
option.textContent = `${scenario.assetFamily} — ${scenario.id}`;
|
||||
select.append(option);
|
||||
}
|
||||
}
|
||||
|
||||
function renderSelectedScenario(): void {
|
||||
const scenario = selectedScenario();
|
||||
if (scenario === null) {
|
||||
return;
|
||||
}
|
||||
window.localStorage.setItem(storageKey, scenario.id);
|
||||
const networkMode = scenario.mode !== "synthetic";
|
||||
element<HTMLElement>("#metadataRequirements").innerHTML = `<dt class="col-6">Famille</dt><dd class="col-6"><code>${scenario.assetFamily}</code></dd><dt class="col-6">Mode</dt><dd class="col-6"><code>${scenario.mode}</code></dd><dt class="col-6">Profil</dt><dd class="col-6"><code>${networkMode ? selectedProfileName ?? "non chargé" : "non applicable"}</code></dd><dt class="col-6">Collection</dt><dd class="col-6">${scenario.requiresCollection ? "requise" : "non requise"}</dd><dt class="col-6">Rule set</dt><dd class="col-6">${scenario.requiresProgrammableRules ? "requis" : "non requis"}</dd><dt class="col-6">Validation</dt><dd class="col-6"><code>${scenario.validationStatus}</code></dd>`;
|
||||
renderJsonViewer("#metadataPreflightOutput", {
|
||||
scenarioId: scenario.id,
|
||||
operationCodes: scenario.operationCodes,
|
||||
checks: ["program_id", "owner", "pda_and_seeds", "required_accounts", "authorities", "stateful_correlations"],
|
||||
networkExecutionPerformed: false,
|
||||
});
|
||||
renderJsonViewer("#metadataSimulationOutput", {
|
||||
mode: scenario.mode,
|
||||
policy: "simulation_first",
|
||||
networkExecutionPerformed: false,
|
||||
automaticSubmission: false,
|
||||
operatorConfirmationRequired: true,
|
||||
expectedState: scenario.validationStatus,
|
||||
note: networkMode ? "Le contrat Devnet est sélectionné, mais aucune transaction Metaplex n'est simulée par cette commande de chargement." : "Scénario synthétique déterministe.",
|
||||
});
|
||||
renderJsonViewer("#metadataPostconditionsOutput", {
|
||||
required: scenario.requiresPostcondition,
|
||||
evidenceKinds: scenario.requiredEvidence,
|
||||
networkEvidenceCollected: false,
|
||||
rule: "Aucune validation réseau n'est déclarée sans simulation RPC réelle, signature éventuelle, slot et postconditions.",
|
||||
});
|
||||
}
|
||||
|
||||
async function loadProfiles(): Promise<void> {
|
||||
const options = await invoke<DemoExecutionSolanaCoreOptionsPayload>("demo_execution_solana_core_options");
|
||||
const select = element<HTMLSelectElement>("#metadataProfileSelect");
|
||||
select.replaceChildren();
|
||||
for (const profile of options.profiles) {
|
||||
const option = document.createElement("option");
|
||||
option.value = profile.name;
|
||||
option.textContent = `${profile.name} — ${profile.walletAlias}`;
|
||||
select.append(option);
|
||||
}
|
||||
const restored = window.localStorage.getItem(profileStorageKey);
|
||||
if (restored !== null && Array.from(select.options).some(option => option.value === restored)) {
|
||||
select.value = restored;
|
||||
} else if (options.defaultProfileName !== null) {
|
||||
select.value = options.defaultProfileName;
|
||||
}
|
||||
selectedProfileName = select.value.length === 0 ? null : select.value;
|
||||
renderSelectedScenario();
|
||||
}
|
||||
|
||||
async function prepareProfile(): Promise<void> {
|
||||
const badge = element<HTMLElement>("#metadataProfileStatusBadge");
|
||||
const profileName = element<HTMLSelectElement>("#metadataProfileSelect").value;
|
||||
if (profileName.length === 0) {
|
||||
badge.textContent = "Aucun profil";
|
||||
badge.className = "badge text-bg-danger";
|
||||
return;
|
||||
}
|
||||
badge.textContent = "Préparation";
|
||||
badge.className = "badge text-bg-warning";
|
||||
try {
|
||||
const readiness = await invoke<DemoExecutionDevnetStoreReadinessPayload>("demo_execution_devnet_prepare_profile", { profileName });
|
||||
selectedProfileName = profileName;
|
||||
window.localStorage.setItem(profileStorageKey, profileName);
|
||||
renderJsonViewer("#metadataProfileOutput", readiness);
|
||||
badge.textContent = "Base prête";
|
||||
badge.className = "badge text-bg-success";
|
||||
renderSelectedScenario();
|
||||
} catch (caught) {
|
||||
const message = caught instanceof Error ? caught.message : String(caught);
|
||||
badge.textContent = "Erreur";
|
||||
badge.className = "badge text-bg-danger";
|
||||
frontendError("kb-app-demo-desktop.frontend.execution_metadata", message);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadScenarios(): Promise<void> {
|
||||
const badge = element<HTMLElement>("#metadataStatusBadge");
|
||||
badge.textContent = "Chargement";
|
||||
badge.className = "badge text-bg-warning";
|
||||
try {
|
||||
scenarios = await invoke<DemoExecutionMetadataScenarioPayload[]>("demo_execution_metadata_scenarios");
|
||||
fillSelect("#metadataSyntheticScenarioSelect", "synthetic");
|
||||
fillSelect("#metadataDevnetScenarioSelect", "network_simulation");
|
||||
const restored = window.localStorage.getItem(storageKey);
|
||||
for (const selector of ["#metadataSyntheticScenarioSelect", "#metadataDevnetScenarioSelect"]) {
|
||||
const select = element<HTMLSelectElement>(selector);
|
||||
if (restored !== null && Array.from(select.options).some(option => option.value === restored)) {
|
||||
select.value = restored;
|
||||
}
|
||||
}
|
||||
renderSelectedScenario();
|
||||
badge.textContent = `${scenarios.length} contrats`;
|
||||
badge.className = "badge text-bg-success";
|
||||
} catch (caught) {
|
||||
const message = caught instanceof Error ? caught.message : String(caught);
|
||||
badge.textContent = "Erreur";
|
||||
badge.className = "badge text-bg-danger";
|
||||
frontendError("kb-app-demo-desktop.frontend.execution_metadata", message);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
installFrontendConsoleBridge("kb-app-demo-desktop.frontend.execution_metadata");
|
||||
for (const selector of ["#metadataSyntheticScenarioSelect", "#metadataDevnetScenarioSelect"]) {
|
||||
element<HTMLSelectElement>(selector).addEventListener("change", renderSelectedScenario);
|
||||
}
|
||||
for (const id of ["metaplexSyntheticCollapse", "metaplexDevnetCollapse"]) {
|
||||
element<HTMLElement>(`#${id}`).addEventListener("shown.bs.collapse", renderSelectedScenario);
|
||||
}
|
||||
element<HTMLSelectElement>("#metadataProfileSelect").addEventListener("change", event => {
|
||||
const target = event.currentTarget as HTMLSelectElement;
|
||||
selectedProfileName = target.value.length === 0 ? null : target.value;
|
||||
renderSelectedScenario();
|
||||
});
|
||||
element<HTMLButtonElement>("#prepareMetadataProfileButton").addEventListener("click", () => void prepareProfile());
|
||||
element<HTMLButtonElement>("#refreshMetadataScenariosButton").addEventListener("click", () => void loadScenarios());
|
||||
void loadProfiles().catch(caught => frontendError("kb-app-demo-desktop.frontend.execution_metadata", caught instanceof Error ? caught.message : String(caught)));
|
||||
void loadScenarios();
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/frontend/ts/main.ts
|
||||
// version: 4
|
||||
// version: 6
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
import MarkdownIt from "markdown-it";
|
||||
@@ -80,6 +80,7 @@ const pipelineDemoLinks: ReadonlyArray<readonly [string, string]> = [
|
||||
["openDemoDecodeReplayLink", "open_demo_decode_replay_window"],
|
||||
["openDemoExecutionSolanaCoreLink", "open_demo_execution_solana_core_window"],
|
||||
["openDemoExecutionSplLink", "open_demo_execution_spl_window"],
|
||||
["openDemoExecutionMetadataLink", "open_demo_execution_metadata_window"],
|
||||
];
|
||||
for (const [elementId, command] of pipelineDemoLinks) {
|
||||
const link = document.querySelector<HTMLAnchorElement>(`#${elementId}`);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "kb-app-demo-desktop",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"version": "0.4.7",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
192
kb-app-demo-desktop/src/demo_execution_metadata.rs
Normal file
192
kb-app-demo-desktop/src/demo_execution_metadata.rs
Normal file
@@ -0,0 +1,192 @@
|
||||
// file: kb-app-demo-desktop/src/demo_execution_metadata.rs
|
||||
// version: 2
|
||||
|
||||
//! Thin desktop adapters for metadata execution demo scenarios.
|
||||
|
||||
/// UI-safe Metaplex scenario row.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, ts_rs::TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataScenarioPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoExecutionMetadataScenarioPayload {
|
||||
/// Stable scenario identifier.
|
||||
pub id: std::string::String,
|
||||
/// Stable asset-family code.
|
||||
pub asset_family: std::string::String,
|
||||
/// Stable execution-mode code.
|
||||
pub mode: std::string::String,
|
||||
/// Ordered operation codes.
|
||||
pub operation_codes: std::vec::Vec<std::string::String>,
|
||||
/// Whether a collection account is required.
|
||||
pub requires_collection: bool,
|
||||
/// Whether programmable authorization rules are required.
|
||||
pub requires_programmable_rules: bool,
|
||||
/// Whether postconditions are mandatory.
|
||||
pub requires_postcondition: bool,
|
||||
/// Current validation status from the canonical matrix.
|
||||
pub validation_status: std::string::String,
|
||||
/// Required evidence kinds.
|
||||
pub required_evidence: std::vec::Vec<std::string::String>,
|
||||
}
|
||||
|
||||
/// Returns the canonical Metaplex scenario inventory for the desktop panel.
|
||||
pub(crate) fn demo_execution_metadata_scenarios() -> std::result::Result<
|
||||
std::vec::Vec<crate::DemoExecutionMetadataScenarioPayload>,
|
||||
std::string::String,
|
||||
> {
|
||||
let matrix = match kb_pipeline_demo_scenarios::load_metaplex_token_metadata_validation_matrix()
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let statuses = matrix
|
||||
.scenarios
|
||||
.into_iter()
|
||||
.map(|scenario| return (scenario.id, (scenario.status, scenario.required_evidence)))
|
||||
.collect::<std::collections::BTreeMap<
|
||||
std::string::String,
|
||||
(
|
||||
kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus,
|
||||
std::vec::Vec<std::string::String>,
|
||||
),
|
||||
>>();
|
||||
let mut payloads = std::vec::Vec::new();
|
||||
for scenario in kb_pipeline_demo_scenarios::metaplex_token_metadata_synthetic_scenarios()
|
||||
.into_iter()
|
||||
.chain(kb_pipeline_demo_scenarios::metaplex_token_metadata_devnet_scenarios())
|
||||
{
|
||||
let status = statuses.get(scenario.id.as_str());
|
||||
let validation_status = match status {
|
||||
std::option::Option::Some((value, _)) => validation_status_code(*value),
|
||||
std::option::Option::None => "not_run".to_string(),
|
||||
};
|
||||
let required_evidence = match status {
|
||||
std::option::Option::Some((_, evidence)) => evidence.clone(),
|
||||
std::option::Option::None => std::vec::Vec::new(),
|
||||
};
|
||||
payloads.push(crate::DemoExecutionMetadataScenarioPayload {
|
||||
id: scenario.id,
|
||||
asset_family: asset_family_code(scenario.asset_family),
|
||||
mode: scenario_mode_code(scenario.mode),
|
||||
operation_codes: scenario.operation_codes,
|
||||
requires_collection: scenario.requires_collection,
|
||||
requires_programmable_rules: scenario.requires_programmable_rules,
|
||||
requires_postcondition: scenario.requires_postcondition,
|
||||
validation_status,
|
||||
required_evidence,
|
||||
});
|
||||
}
|
||||
return std::result::Result::Ok(payloads);
|
||||
}
|
||||
|
||||
fn asset_family_code(
|
||||
value: kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily,
|
||||
) -> std::string::String {
|
||||
return match value {
|
||||
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft => "nft".to_string(),
|
||||
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Sft => "sft".to_string(),
|
||||
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Fungible => {
|
||||
"fungible".to_string()
|
||||
},
|
||||
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Collection => {
|
||||
"collection".to_string()
|
||||
},
|
||||
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::ProgrammableNft => {
|
||||
"programmable_nft".to_string()
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn scenario_mode_code(
|
||||
value: kb_pipeline_demo_scenarios::MetaplexTokenMetadataScenarioMode,
|
||||
) -> std::string::String {
|
||||
return match value {
|
||||
kb_pipeline_demo_scenarios::MetaplexTokenMetadataScenarioMode::Synthetic => {
|
||||
"synthetic".to_string()
|
||||
},
|
||||
kb_pipeline_demo_scenarios::MetaplexTokenMetadataScenarioMode::NetworkSimulation => {
|
||||
"network_simulation".to_string()
|
||||
},
|
||||
kb_pipeline_demo_scenarios::MetaplexTokenMetadataScenarioMode::NetworkSubmission => {
|
||||
"network_submission".to_string()
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn validation_status_code(
|
||||
value: kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus,
|
||||
) -> std::string::String {
|
||||
return match value {
|
||||
kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::NotRun => {
|
||||
"not_run".to_string()
|
||||
},
|
||||
kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::SyntheticValidated => {
|
||||
"synthetic_validated".to_string()
|
||||
},
|
||||
kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::Simulated => {
|
||||
"simulated".to_string()
|
||||
},
|
||||
kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::Submitted => {
|
||||
"submitted".to_string()
|
||||
},
|
||||
kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::Confirmed => {
|
||||
"confirmed".to_string()
|
||||
},
|
||||
kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::Unavailable => {
|
||||
"unavailable".to_string()
|
||||
},
|
||||
kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::Failed => {
|
||||
"failed".to_string()
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn desktop_inventory_matches_reusable_scenarios_and_matrix() {
|
||||
let payloads = match crate::demo_execution_metadata_scenarios() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
assert_eq!(std::option::Option::Some(error), std::option::Option::None);
|
||||
return;
|
||||
},
|
||||
};
|
||||
assert_eq!(payloads.len(), 10);
|
||||
assert!(payloads.iter().all(|payload| return !payload.operation_codes.is_empty()));
|
||||
assert!(payloads.iter().all(|payload| return payload.requires_postcondition));
|
||||
assert!(
|
||||
payloads
|
||||
.iter()
|
||||
.all(|payload| return payload.validation_status != "missing_from_matrix")
|
||||
);
|
||||
assert_eq!(
|
||||
payloads
|
||||
.iter()
|
||||
.filter(|payload| return payload.mode == "network_simulation")
|
||||
.count(),
|
||||
5
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn programmable_and_collection_requirements_remain_explicit() {
|
||||
let payloads = match crate::demo_execution_metadata_scenarios() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
assert_eq!(std::option::Option::Some(error), std::option::Option::None);
|
||||
return;
|
||||
},
|
||||
};
|
||||
assert_eq!(payloads.iter().filter(|payload| return payload.requires_collection).count(), 2);
|
||||
assert_eq!(
|
||||
payloads
|
||||
.iter()
|
||||
.filter(|payload| return payload.requires_programmable_rules)
|
||||
.count(),
|
||||
2
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/lib.rs
|
||||
// version: 25
|
||||
// version: 26
|
||||
|
||||
//! Tauri desktop demo application for `khadhroony-bot3`.
|
||||
|
||||
@@ -14,6 +14,7 @@ mod demo_config;
|
||||
mod demo_core_extraction;
|
||||
mod demo_decode_replay;
|
||||
mod demo_devnet_common;
|
||||
mod demo_execution_metadata;
|
||||
mod demo_execution_solana_core;
|
||||
mod demo_execution_spl;
|
||||
mod demo_http;
|
||||
@@ -126,6 +127,10 @@ pub(crate) use self::demo_devnet_common::DemoExecutionDevnetStoreReadinessPayloa
|
||||
pub(crate) use self::demo_devnet_common::devnet_store_readiness_payload;
|
||||
/// Serializes a diagnostic value to indented JSON without panicking.
|
||||
pub(crate) use self::demo_devnet_common::pretty_json;
|
||||
/// UI-safe Metaplex scenario row.
|
||||
pub(crate) use self::demo_execution_metadata::DemoExecutionMetadataScenarioPayload;
|
||||
/// Returns the canonical Metaplex scenario inventory for the desktop panel.
|
||||
pub(crate) use self::demo_execution_metadata::demo_execution_metadata_scenarios;
|
||||
/// Request sent by the Memo v4 Devnet execution panel.
|
||||
pub(crate) use self::demo_execution_solana_core::DemoExecutionMemoRequest;
|
||||
/// UI-safe result of one Memo v4 Devnet execution orchestration.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/tauri.rs
|
||||
// version: 24
|
||||
// version: 26
|
||||
|
||||
//! Tauri runtime assembly and private command wrappers.
|
||||
|
||||
@@ -53,6 +53,8 @@ pub fn run() -> kb_core::Result<()> {
|
||||
demo_execution_solana_core_cancel,
|
||||
demo_execution_spl_validation_scenarios,
|
||||
open_demo_execution_spl_window,
|
||||
open_demo_execution_metadata_window,
|
||||
demo_execution_metadata_scenarios,
|
||||
demo_execution_spl_token_execute,
|
||||
demo_spl_token_journal,
|
||||
demo_spl_ata_derive,
|
||||
@@ -601,6 +603,26 @@ fn open_demo_execution_spl_window(
|
||||
);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn open_demo_execution_metadata_window(
|
||||
app_handle: tauri::AppHandle,
|
||||
) -> std::result::Result<(), std::string::String> {
|
||||
return open_or_focus_demo_window(
|
||||
app_handle,
|
||||
"demo_execution_metadata",
|
||||
"demo_execution_metadata.html",
|
||||
"Exécution Metadata Devnet",
|
||||
);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn demo_execution_metadata_scenarios() -> std::result::Result<
|
||||
std::vec::Vec<crate::DemoExecutionMetadataScenarioPayload>,
|
||||
std::string::String,
|
||||
> {
|
||||
return crate::demo_execution_metadata_scenarios();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn demo_execution_spl_token_execute(
|
||||
app_handle: tauri::AppHandle,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Khadhroony Bot3 Demo Desktop",
|
||||
"version": "0.1.0",
|
||||
"version": "0.4.7",
|
||||
"identifier": "com.sasedev.kb-app-demo-desktop",
|
||||
"build": {
|
||||
"beforeDevCommand": "npm run dev",
|
||||
|
||||
Reference in New Issue
Block a user