v0.1.0-pre.061

This commit is contained in:
2026-07-28 18:41:30 +02:00
parent c7bad46f50
commit f40fb78817
527 changed files with 4598 additions and 4114 deletions

View File

@@ -1,12 +1,12 @@
<!-- file: kb-app-demo-desktop/frontend/demo_backfill.html -->
<!-- version: 6 -->
<!-- version: 7 -->
<!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/favicon.png">
<link rel="icon" href="imgs/logo.png">
<link rel="stylesheet" href="sass/main.scss">
<title>Khadhroony Bot3 — Backfill HTTP</title>
</head>
@@ -163,14 +163,14 @@
<textarea class="form-control font-monospace app-log-output mb-4" id="backfillLogOutput" readonly="">Aucune campagne exécutée.</textarea>
<div class="d-flex justify-content-between align-items-center mb-2">
<h2 class="h5 mb-0">Résumé JSON</h2>
<button class="btn btn-sm btn-outline-primary" id="copyBackfillSummaryButton" type="button">Copier</button>
</div>
<div class="app-json-viewer" id="backfillSummaryOutput"></div>
<div class="app-json-viewer" id="backfillSummaryOutput">{"status":"idle","message":"Aucune campagne exécutée."}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<footer class="app-footer bg-dark text-light">

View File

@@ -1,12 +1,12 @@
<!-- file: kb-app-demo-desktop/frontend/demo_config.html -->
<!-- version: 4 -->
<!-- version: 5 -->
<!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/favicon.png">
<link rel="icon" href="imgs/logo.png">
<link rel="stylesheet" href="sass/main.scss">
<title>Khadhroony Bot3 — Configuration</title>
</head>
@@ -47,7 +47,7 @@
<div class="card shadow-sm border-0 h-100">
<div class="card-body">
<h2 class="h5 card-title mb-3">Configuration active</h2>
<div id="activeProfileJson" class="app-json-viewer" class="app-config-box app-json-viewer mb-0">Chargement...</div>
<div id="activeProfileJson" class="app-config-box app-json-viewer mb-0">Chargement...</div>
</div>
</div>
</div>
@@ -56,7 +56,7 @@
<div class="card shadow-sm border-0 h-100">
<div class="card-body">
<h2 class="h5 card-title mb-3">Configuration complète</h2>
<div id="fullConfigJson" class="app-json-viewer" class="app-config-box app-json-viewer mb-0">Chargement...</div>
<div id="fullConfigJson" class="app-config-box app-json-viewer mb-0">Chargement...</div>
</div>
</div>
</div>
@@ -65,7 +65,7 @@
<div class="card shadow-sm border-0">
<div class="card-body">
<h2 class="h5 card-title mb-3">Schéma JSON embarqué</h2>
<div id="schemaJson" class="app-json-viewer" class="app-config-box app-json-viewer mb-0">Chargement...</div>
<div id="schemaJson" class="app-config-box app-json-viewer mb-0">Chargement...</div>
</div>
</div>
</div>

View File

@@ -1,12 +1,12 @@
<!-- file: kb-app-demo-desktop/frontend/demo_core_extraction.html -->
<!-- version: 8 -->
<!-- version: 10 -->
<!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/favicon.png">
<link rel="icon" href="imgs/logo.png">
<link rel="stylesheet" href="sass/main.scss">
<title>Khadhroony Bot3 — Extraction canonical vers core</title>
</head>
@@ -122,7 +122,8 @@
<div class="accordion-body">
<p class="text-body-secondary">Sélectionne les transactions raw ayant déjà au moins une instruction core résolue pour ce programme. Ce mode sert aux replays après changement de version ou de hash.</p>
<label class="form-label" for="coreExtractionProgramIdInput">Program ID</label>
<input class="form-control font-monospace mb-3" id="coreExtractionProgramIdInput" />
<input class="form-control font-monospace mb-3" id="coreExtractionProgramIdInput" list="coreExtractionProgramIdOptions" />
<datalist id="coreExtractionProgramIdOptions"></datalist>
<button class="btn btn-primary core-extraction-start-button" data-core-extraction-mode="program_id" type="button">Extraire par programme</button>
</div>
</div>
@@ -141,7 +142,7 @@
</div>
<div class="col-12 col-xl-5">
<h3 class="h6">Résumé JSON</h3>
<textarea class="form-control font-monospace" data-json-viewer="true" id="coreExtractionSummaryOutput" readonly="" style="height: 28rem; overflow-y: auto;">Aucun résultat.</textarea>
<textarea class="form-control font-monospace" data-json-viewer="true" id="coreExtractionSummaryOutput" readonly="" style="height: 28rem; overflow-y: auto;">{"status":"idle","message":"Aucun résultat."}</textarea>
</div>
</div>
</div>

View File

@@ -1,12 +1,12 @@
<!-- file: kb_app_demo/frontend/demo_decode_replay.html -->
<!-- version: 6 -->
<!-- version: 7 -->
<!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/favicon.png">
<link rel="icon" href="imgs/logo.png">
<link rel="stylesheet" href="sass/main.scss">
<title>Khadhroony Bot3 — Décodage et matérialisation</title>
</head>
@@ -117,9 +117,9 @@
<h2 class="h5">Journal</h2>
<textarea id="decodeReplayLogOutput" class="form-control font-monospace mb-3" rows="14" readonly>Aucune campagne exécutée.</textarea>
<h2 class="h5">Résumé</h2>
<textarea id="decodeReplaySummaryOutput" class="form-control font-monospace mb-3" rows="15" readonly data-json-viewer="true">Aucun résultat.</textarea>
<textarea id="decodeReplaySummaryOutput" class="form-control font-monospace mb-3" rows="15" readonly data-json-viewer="true">{"status":"idle","message":"Aucun résultat."}</textarea>
<h2 class="h5">Diagnostics</h2>
<textarea id="decodeReplayDiagnosticsOutput" class="form-control font-monospace" rows="18" readonly data-json-viewer="true">Diagnostics non chargés.</textarea>
<textarea id="decodeReplayDiagnosticsOutput" class="form-control font-monospace" rows="18" readonly data-json-viewer="true">{"status":"idle","message":"Diagnostics non chargés."}</textarea>
</div>
</div>
</div>
@@ -139,7 +139,7 @@
</div>
<p id="annotationJournalStatus" class="text-body-secondary mb-3">Annotations non chargées.</p>
<div class="table-responsive">
<table class="table table-sm table-hover align-middle mb-0">
<table id="annotationJournalTable" class="table table-sm table-hover align-middle mb-0 w-100">
<thead>
<tr>
<th>Slot</th>

View File

@@ -1,12 +1,12 @@
<!-- file: kb-app-demo-desktop/frontend/demo_execution_solana_core.html -->
<!-- version: 9 -->
<!-- version: 10 -->
<!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/favicon.png">
<link rel="icon" href="imgs/logo.png">
<link rel="stylesheet" href="sass/main.scss">
<title>Khadhroony Bot3 — Exécution Solana Core Devnet</title>
</head>
@@ -72,14 +72,14 @@
<div class="col-12 col-xl-7">
<div class="card shadow-sm border-0">
<div class="card-body">
<h2 class="h5 mb-3">Résumé</h2><textarea class="form-control font-monospace" data-json-viewer="true" id="executionSummaryOutput" readonly="" rows="14">Aucun résultat.</textarea>
<h2 class="h5 mb-3">Résumé</h2><textarea class="form-control font-monospace" data-json-viewer="true" id="executionSummaryOutput" readonly="" rows="14">{"status":"idle","message":"Aucun résultat."}</textarea>
</div>
</div>
<div class="card shadow-sm border-0 mt-4">
<div class="card-body">
<h2 class="h5">Plan exact</h2><textarea class="form-control font-monospace" data-json-viewer="true" id="executionPlanOutput" readonly="" rows="12">Aucun plan.</textarea>
<h2 class="h5 mt-3">Simulation exacte</h2><textarea class="form-control font-monospace" data-json-viewer="true" id="executionSimulationOutput" readonly="" rows="12">Aucune simulation.</textarea>
<h2 class="h5 mt-3">Confirmation et replay</h2><textarea class="form-control font-monospace" data-json-viewer="true" id="executionDiagnosticsOutput" readonly="" rows="12">Aucun diagnostic.</textarea>
<h2 class="h5">Plan exact</h2><textarea class="form-control font-monospace" data-json-viewer="true" id="executionPlanOutput" readonly="" rows="12">{"status":"idle","message":"Aucun plan."}</textarea>
<h2 class="h5 mt-3">Simulation exacte</h2><textarea class="form-control font-monospace" data-json-viewer="true" id="executionSimulationOutput" readonly="" rows="12">{"status":"idle","message":"Aucune simulation."}</textarea>
<h2 class="h5 mt-3">Confirmation et replay</h2><textarea class="form-control font-monospace" data-json-viewer="true" id="executionDiagnosticsOutput" readonly="" rows="12">{"status":"idle","message":"Aucun diagnostic."}</textarea>
</div>
</div>
</div>
@@ -87,6 +87,7 @@
</div>
</div>
</div>
</div>
</main>
<footer class="app-footer bg-dark text-light">

View File

@@ -1,12 +1,12 @@
<!-- file: kb-app-demo-desktop/frontend/demo_execution_spl.html -->
<!-- version: 11 -->
<!-- version: 14 -->
<!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/favicon.png">
<link rel="icon" href="imgs/logo.png">
<link rel="stylesheet" href="sass/main.scss">
<title>Khadhroony Bot3 — Exécution SPL Devnet</title>
</head>
@@ -101,7 +101,7 @@
<div class="accordion-body">
<div class="mb-3">
<label class="form-label" for="memoMessageInput">Annotation UTF-8 exacte</label>
<textarea class="form-control" id="memoMessageInput" maxlength="566" rows="5">khadhroony-bot2 Memo v4 validation</textarea>
<textarea class="form-control" id="memoMessageInput" maxlength="566" rows="5">khadhroony-bot3 Memo v4 validation</textarea>
<div class="form-text">566 octets UTF-8 maximum. La matérialisation `transaction_annotation` et le second replay didempotence sont obligatoires après envoi.</div>
</div>
<div class="form-check form-switch mb-3">
@@ -168,7 +168,7 @@
<label class="form-label" for="ataTokenProgramSelect">Token Program cible</label>
<select class="form-select" id="ataTokenProgramSelect">
<option value="classic">SPL Token classique</option>
<option value="token2022">Token-2022</option>
<option value="token_2022">Token-2022</option>
</select>
</div>
<div class="col-12 col-md-6">
@@ -205,31 +205,31 @@
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="splToken2022DecoderHeading"><button aria-controls="splToken2022DecoderCollapse" aria-expanded="false" class="accordion-button collapsed" data-bs-target="#splToken2022DecoderCollapse" data-bs-toggle="collapse" type="button"><span class="d-flex flex-wrap align-items-center justify-content-between gap-2 w-100 me-2"><span>Exécution spl_token2022 — scénarios publics</span><span class="badge text-bg-success">Exécutable</span></span></button></h2>
<h2 class="accordion-header" id="splToken2022DecoderHeading"><button aria-controls="splToken2022DecoderCollapse" aria-expanded="false" class="accordion-button collapsed" data-bs-target="#splToken2022DecoderCollapse" data-bs-toggle="collapse" type="button"><span class="d-flex flex-wrap align-items-center justify-content-between gap-2 w-100 me-2"><span>Exécution spl_token_2022 — scénarios publics</span><span class="badge text-bg-success">Exécutable</span></span></button></h2>
<div aria-labelledby="splToken2022DecoderHeading" class="accordion-collapse collapse" data-bs-parent="#decoderExecutionAccordion" id="splToken2022DecoderCollapse">
<div class="accordion-body">
<div class="alert alert-info">Chaque opération est simulée sur le message exact avant toute signature. Les scénarios sont indépendants et réutilisent la fixture chargée dans les deux terminaux.</div>
<div class="d-grid mb-3"><button class="btn btn-outline-secondary" id="loadToken2022FixtureButton" type="button">Charger la fixture Token-2022</button></div>
<div class="form-text mb-3" id="token2022FixtureHelp">Aucune fixture chargée.</div>
<div class="mb-3"><label class="form-label" for="token2022ScenarioSelect">Scénario</label><select class="form-select" id="token2022ScenarioSelect">
<option value="token2022_mint_to_checked">MintToChecked</option>
<option value="token2022_transfer_checked">TransferChecked</option>
<option value="token2022_approve_checked">ApproveChecked</option>
<option value="token2022_revoke">Revoke</option>
<option value="token2022_burn_checked">BurnChecked</option>
<option value="token2022_freeze_account">FreezeAccount</option>
<option value="token2022_thaw_account">ThawAccount</option>
<option value="token2022_close_destination">CloseAccount</option>
<div class="form-text mb-3" id="token_2022FixtureHelp">Aucune fixture chargée.</div>
<div class="mb-3"><label class="form-label" for="token_2022ScenarioSelect">Scénario</label><select class="form-select" id="token_2022ScenarioSelect">
<option value="token_2022_mint_to_checked">MintToChecked</option>
<option value="token_2022_transfer_checked">TransferChecked</option>
<option value="token_2022_approve_checked">ApproveChecked</option>
<option value="token_2022_revoke">Revoke</option>
<option value="token_2022_burn_checked">BurnChecked</option>
<option value="token_2022_freeze_account">FreezeAccount</option>
<option value="token_2022_thaw_account">ThawAccount</option>
<option value="token_2022_close_destination">CloseAccount</option>
</select></div>
<div class="mb-3"><label class="form-label" for="token2022SourceInput">Compte source / compte cible</label><input class="form-control font-monospace" id="token2022SourceInput" type="text" /></div>
<div class="mb-3"><label class="form-label" for="token2022MintInput">Mint Token-2022</label><input class="form-control font-monospace" id="token2022MintInput" type="text" /></div>
<div class="mb-3"><label class="form-label" for="token2022DestinationInput">Compte destination / destination lamports</label><input class="form-control font-monospace" id="token2022DestinationInput" type="text" /></div>
<div class="mb-3"><label class="form-label" for="token2022DelegateInput">Delegate</label><input class="form-control font-monospace" id="token2022DelegateInput" type="text" /></div>
<div class="mb-3"><label class="form-label" for="token2022AuthorityInput">Autorité</label><input class="form-control font-monospace" id="token2022AuthorityInput" type="text" /></div>
<div class="mb-3"><label class="form-label" for="token2022FreezeAuthorityInput">Freeze authority</label><input class="form-control font-monospace" id="token2022FreezeAuthorityInput" type="text" /></div>
<div class="mb-3"><label class="form-label" for="token_2022SourceInput">Compte source / compte cible</label><input class="form-control font-monospace" id="token_2022SourceInput" type="text" /></div>
<div class="mb-3"><label class="form-label" for="token_2022MintInput">Mint Token-2022</label><input class="form-control font-monospace" id="token_2022MintInput" type="text" /></div>
<div class="mb-3"><label class="form-label" for="token_2022DestinationInput">Compte destination / destination lamports</label><input class="form-control font-monospace" id="token_2022DestinationInput" type="text" /></div>
<div class="mb-3"><label class="form-label" for="token_2022DelegateInput">Delegate</label><input class="form-control font-monospace" id="token_2022DelegateInput" type="text" /></div>
<div class="mb-3"><label class="form-label" for="token_2022AuthorityInput">Autorité</label><input class="form-control font-monospace" id="token_2022AuthorityInput" type="text" /></div>
<div class="mb-3"><label class="form-label" for="token_2022FreezeAuthorityInput">Freeze authority</label><input class="form-control font-monospace" id="token_2022FreezeAuthorityInput" type="text" /></div>
<div class="row g-3 mb-3">
<div class="col-8"><label class="form-label" for="token2022AmountInput">Montant brut</label><input class="form-control" id="token2022AmountInput" type="text" value="1" /></div>
<div class="col-4"><label class="form-label" for="token2022DecimalsInput">Décimales</label><input class="form-control" id="token2022DecimalsInput" max="255" min="0" type="number" value="9" /></div>
<div class="col-8"><label class="form-label" for="token_2022AmountInput">Montant brut</label><input class="form-control" id="token_2022AmountInput" type="text" value="1" /></div>
<div class="col-4"><label class="form-label" for="token_2022DecimalsInput">Décimales</label><input class="form-control" id="token_2022DecimalsInput" max="255" min="0" type="number" value="9" /></div>
</div>
<div class="d-grid gap-2 d-md-flex">
<button class="btn btn-outline-primary flex-fill" id="simulateToken2022Button" type="button">Préflight et simuler</button>
@@ -269,7 +269,7 @@
</h2>
<div class="accordion-collapse collapse" data-bs-parent="#executionDetailsAccordion" id="executionPlanCollapse">
<div class="accordion-body">
<textarea class="form-control font-monospace" data-json-viewer="true" id="executionPlanOutput" readonly="" rows="16">Aucun plan.</textarea>
<textarea class="form-control font-monospace" data-json-viewer="true" id="executionPlanOutput" readonly="" rows="16">{"status":"idle","message":"Aucun plan."}</textarea>
</div>
</div>
</div>
@@ -279,7 +279,7 @@
</h2>
<div class="accordion-collapse collapse" data-bs-parent="#executionDetailsAccordion" id="executionSimulationCollapse">
<div class="accordion-body">
<textarea class="form-control font-monospace" data-json-viewer="true" id="executionSimulationOutput" readonly="" rows="16">Aucune simulation.</textarea>
<textarea class="form-control font-monospace" data-json-viewer="true" id="executionSimulationOutput" readonly="" rows="16">{"status":"idle","message":"Aucune simulation."}</textarea>
</div>
</div>
</div>
@@ -289,7 +289,7 @@
</h2>
<div class="accordion-collapse collapse" data-bs-parent="#executionDetailsAccordion" id="executionDiagnosticsCollapse">
<div class="accordion-body">
<textarea class="form-control font-monospace" data-json-viewer="true" id="executionDiagnosticsOutput" readonly="" rows="16">Aucun diagnostic.</textarea>
<textarea class="form-control font-monospace" data-json-viewer="true" id="executionDiagnosticsOutput" readonly="" rows="16">{"status":"idle","message":"Aucun diagnostic."}</textarea>
</div>
</div>
</div>
@@ -411,7 +411,7 @@
<div class="card shadow-sm border-0 mt-4">
<div class="card-body">
<h2 class="h5 mb-3">Résumé</h2>
<textarea class="form-control font-monospace" data-json-viewer="true" id="executionSummaryOutput" readonly="" rows="14">Aucun résultat.</textarea>
<textarea class="form-control font-monospace" data-json-viewer="true" id="executionSummaryOutput" readonly="" rows="14">{"status":"idle","message":"Aucun résultat."}</textarea>
</div>
</div>
<div class="card shadow-sm border-0 mt-4">
@@ -427,6 +427,7 @@
</div>
</div>
</div>
</main>
<footer class="app-footer bg-dark text-light">

View File

@@ -1,12 +1,12 @@
<!-- file: kb-app-demo-desktop/frontend/demo_http.html -->
<!-- version: 6 -->
<!-- version: 7 -->
<!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/favicon.png">
<link rel="icon" href="imgs/logo.png">
<link rel="stylesheet" href="sass/main.scss">
<title>Khadhroony Bot3 — HTTP JSON-RPC</title>
</head>
@@ -61,23 +61,17 @@
<div class="col-12 col-xl-7">
<div class="card shadow-sm border-0 mb-4">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-2">
<h2 class="h5 card-title mb-0">Endpoints HTTP</h2>
<button id="copyHttpPoolButton" class="btn btn-sm btn-outline-secondary">Copier</button>
</div>
<textarea id="httpPoolOutput" class="form-control font-monospace" rows="10" readonly>Chargement...</textarea>
<h2 class="h5 card-title">Endpoints HTTP</h2>
<div id="httpPoolOutput" class="app-json-viewer">{"status":"loading","message":"Chargement des endpoints HTTP."}</div>
</div>
</div>
<div class="card shadow-sm border-0">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-2">
<h2 class="h5 card-title mb-0">Résultat</h2>
<div class="d-flex gap-2">
<button id="copyHttpResultButton" class="btn btn-sm btn-outline-secondary" type="button">Copier</button>
<button id="clearHttpResultButton" class="btn btn-sm btn-outline-secondary" type="button">Effacer</button>
</div>
<button id="clearHttpResultButton" class="btn btn-sm btn-outline-secondary" type="button">Effacer</button>
</div>
<textarea id="httpResultOutput" class="form-control font-monospace" style="height: 24rem; overflow-y: auto;" readonly>Aucune requête exécutée.</textarea>
<div id="httpResultOutput" class="app-json-viewer">{"status":"idle","message":"Aucune requête exécutée."}</div>
</div>
</div>
</div>

View File

@@ -1,12 +1,12 @@
<!-- file: kb_app_demo/frontend/demo_sql_diag.html -->
<!-- version: 3 -->
<!-- version: 4 -->
<!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/favicon.png">
<link rel="icon" href="imgs/logo.png">
<link rel="stylesheet" href="sass/main.scss">
<title>Khadhroony Bot3 — SQL diagnostics</title>
</head>
@@ -64,7 +64,7 @@
<div class="card-body">
<h2 class="h5 card-title mb-3">Tables raw/core connues</h2>
<div class="table-responsive app-table-full-width">
<table class="table table-sm align-middle mb-0">
<table class="table table-sm align-middle mb-0" id="sqlDiagTable">
<thead>
<tr>
<th>Table</th>
@@ -88,7 +88,7 @@
<div class="card shadow-sm border-0">
<div class="card-body">
<h2 class="h5 card-title mb-3">Payload JSON</h2>
<div id="sqlDiagJson" class="app-config-box app-json-viewer mb-0">Chargement...</div>
<div id="sqlDiagJson" class="app-config-box app-json-viewer mb-0">{"status":"loading","message":"Chargement des données SQL."}</div>
</div>
</div>
</div>

View File

@@ -1,12 +1,12 @@
<!-- file: kb_app_demo/frontend/demo_sql_pg_core.html -->
<!-- version: 4 -->
<!-- version: 5 -->
<!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/favicon.png">
<link rel="icon" href="imgs/logo.png">
<link rel="stylesheet" href="sass/main.scss">
<title>Khadhroony Bot3 — PostgreSQL core store</title>
</head>
@@ -51,7 +51,7 @@
<div class="card-body">
<h2 class="h5 card-title mb-3">Tables core</h2>
<div class="table-responsive app-table-full-width">
<table class="table table-sm align-middle mb-0">
<table class="table table-sm align-middle mb-0" id="sqlCoreTable">
<thead>
<tr>
<th>Table</th>
@@ -75,7 +75,7 @@
<div class="card shadow-sm border-0">
<div class="card-body">
<h2 class="h5 card-title mb-3">Payload JSON</h2>
<div id="sqlCoreJson" class="app-config-box app-json-viewer mb-0">Chargement...</div>
<div id="sqlCoreJson" class="app-config-box app-json-viewer mb-0">{"status":"loading","message":"Chargement des données SQL."}</div>
</div>
</div>
</div>

View File

@@ -1,12 +1,12 @@
<!-- file: kb_app_demo/frontend/demo_sql_pg_raw.html -->
<!-- version: 5 -->
<!-- version: 6 -->
<!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/favicon.png">
<link rel="icon" href="imgs/logo.png">
<link rel="stylesheet" href="sass/main.scss">
<title>Khadhroony Bot3 — PostgreSQL canonical acquisition</title>
</head>
@@ -51,7 +51,7 @@
<div class="card-body">
<h2 class="h5 card-title mb-3">Transactions canoniques et observations</h2>
<div class="table-responsive app-table-full-width">
<table class="table table-sm align-middle mb-0">
<table class="table table-sm align-middle mb-0" id="sqlRawTable">
<thead>
<tr>
<th>Table</th>
@@ -75,7 +75,7 @@
<div class="card shadow-sm border-0">
<div class="card-body">
<h2 class="h5 card-title mb-3">Payload JSON</h2>
<div id="sqlRawJson" class="app-config-box app-json-viewer mb-0">Chargement...</div>
<div id="sqlRawJson" class="app-config-box app-json-viewer mb-0">{"status":"loading","message":"Chargement des données SQL."}</div>
</div>
</div>
</div>

View File

@@ -1,12 +1,12 @@
<!-- file: kb_app_demo/frontend/demo_sql_replay_candidates.html -->
<!-- version: 8 -->
<!-- version: 9 -->
<!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/favicon.png">
<link rel="icon" href="imgs/logo.png">
<link rel="stylesheet" href="sass/main.scss">
<title>Khadhroony Bot3 — Candidats de replay SQL</title>
</head>
@@ -431,6 +431,7 @@
</div>
</div>
</div>
</div>
</main>
<footer class="app-footer bg-dark text-light">

View File

@@ -1,12 +1,12 @@
<!-- file: kb-app-demo-desktop/frontend/demo_ws.html -->
<!-- version: 8 -->
<!-- version: 9 -->
<!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/favicon.png">
<link rel="icon" href="imgs/logo.png">
<link rel="stylesheet" href="sass/main.scss">
<title>Khadhroony Bot3 — WebSocket standard</title>
</head>
@@ -73,16 +73,13 @@
<h2 class="h5 card-title mb-0">Statut</h2>
<span id="wsStatusBadge" class="badge text-bg-secondary">Déconnecté</span>
</div>
<textarea id="wsStatusOutput" class="form-control font-monospace" rows="6" readonly>connecté.</textarea>
<div id="wsStatusOutput" class="app-json-viewer">{"connected":false,"subscriptionCount":0,"subscriptions":[]}</div>
</div>
</div>
<div class="card shadow-sm border-0 mb-4">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-2">
<h2 class="h5 card-title mb-0">Endpoints WebSocket</h2>
<button id="copyWsPoolButton" class="btn btn-sm btn-outline-secondary">Copier</button>
</div>
<textarea id="wsPoolOutput" class="form-control font-monospace" rows="8" readonly>Chargement...</textarea>
<h2 class="h5 card-title">Endpoints WebSocket</h2>
<div id="wsPoolOutput" class="app-json-viewer">{"status":"loading","message":"Chargement des endpoints WebSocket."}</div>
</div>
</div>
<div class="card shadow-sm border-0">

View File

@@ -1,12 +1,12 @@
<!-- file: kb-app-demo-desktop/frontend/main.html -->
<!-- version: 8 -->
<!-- version: 9 -->
<!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/favicon.png">
<link rel="icon" href="imgs/logo.png">
<link rel="stylesheet" href="sass/main.scss">
<title>Khadhroony Bot3 — Main Window</title>
</head>

View File

@@ -339,7 +339,7 @@ video#vbackground:not([playsinline]) {
white-space: normal;
}
.app-json-viewer andypf-json-viewer {
.app-json-viewer .app-json-viewer-element {
display: block;
min-width: 100%;
}
@@ -394,7 +394,7 @@ video#vbackground:not([playsinline]) {
background: var(--bs-body-bg);
}
.app-json-viewer > andypf-json-viewer {
.app-json-viewer > .app-json-viewer-element {
display: block;
min-width: 0;
}

View File

@@ -1,5 +1,5 @@
// file: kb_demo_app/frontend/sass/_bootswatch.scss
// version: 1
// file: kb-app-demo-desktop/frontend/sass/_bootswatch.scss
// version: 2
// Pulse 5.3.8
// Bootswatch

View File

@@ -1,5 +1,5 @@
// file: kb_demo_app/frontend/sass/_fontawesome.scss
// version: 1
// file: kb-app-demo-desktop/frontend/sass/_fontawesome.scss
// version: 2
//@use '@fortawesome/fontawesome-free/scss/variables' with (
// // customizing $font-path - make sure it points to where your webfonts are stored in your project

View File

@@ -1,5 +1,5 @@
// file: kb_demo_app/frontend/sass/_simplebar.scss
// version: 1
// file: kb-app-demo-desktop/frontend/sass/_simplebar.scss
// version: 2
/* Rtl support */
[data-simplebar] {

View File

@@ -1,5 +1,5 @@
// file: kb_demo_app/frontend/sass/_variables.scss
// version: 1
// file: kb-app-demo-desktop/frontend/sass/_variables.scss
// version: 2
// Pulse 5.3.8
// Bootswatch

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/frontend/sass/main.scss
// version: 1
// version: 2
@import "bootstrap/scss/functions";
@import "variables";

View File

@@ -1,5 +1,5 @@
// file: kb_app_demo/frontend/sass/splash.scss
// version: 4
// file: kb-app-demo-desktop/frontend/sass/splash.scss
// version: 5
@font-face {
font-family: 'Dos Amazigh';

View File

@@ -252,19 +252,17 @@ function renderBackfillSummary(value: unknown): void {
}
container.textContent = "";
const viewer = document.createElement("andypf-json-viewer");
viewer.classList.add("app-json-viewer-element");
viewer.setAttribute("indent", "2");
viewer.setAttribute("expanded", "1");
viewer.setAttribute("theme", "default-light");
viewer.setAttribute("show-data-types", "true");
viewer.setAttribute("show-toolbar", "false");
viewer.setAttribute("show-copy", "true");
viewer.setAttribute("data", JSON.stringify(value ?? { status: "Aucun résultat" }, null, 2));
viewer.setAttribute("data", JSON.stringify(value ?? { status: "idle", message: "Aucune campagne exécutée." }, null, 2));
container.appendChild(viewer);
}
async function copyText(value: string): Promise<void> {
await navigator.clipboard.writeText(value);
}
document.addEventListener("DOMContentLoaded", () => {
installFrontendConsoleBridge("kb-app-demo-desktop.frontend.backfill");
@@ -298,9 +296,5 @@ document.addEventListener("DOMContentLoaded", () => {
frontendError("kb-app-demo-desktop.frontend.backfill", `Options loading failed: ${message}`);
});
document.querySelector<HTMLButtonElement>("#copyBackfillSummaryButton")?.addEventListener("click", async () => {
const value = document.querySelector("#backfillSummaryOutput andypf-json-viewer")?.getAttribute("data") ?? "";
await copyText(value);
});
renderBackfillSummary(null);
});

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/frontend/ts/demo_core_extraction.ts
// version: 5
// version: 6
import * as bootstrap from "bootstrap";
import "simplebar";
@@ -143,6 +143,13 @@ async function loadOptions(): Promise<void> {
element<HTMLElement>("#coreExtractionVersionBadge").textContent = `Version ${options.processorVersion}`;
element<HTMLInputElement>("#coreExtractionLimitInput").value = String(options.defaultLimit);
element<HTMLInputElement>("#coreExtractionConcurrencyInput").value = String(options.defaultMaxConcurrentExtractions);
const programOptions = element<HTMLDataListElement>("#coreExtractionProgramIdOptions");
programOptions.replaceChildren(...options.programs.map(program => {
const option = document.createElement("option");
option.value = program.programId;
option.label = program.code;
return option;
}));
setRunning(options.running);
}

View File

@@ -1,10 +1,12 @@
// file: kb-app-demo-desktop/frontend/ts/demo_decode_replay.ts
// version: 6
// version: 7
import * as bootstrap from "bootstrap";
import "simplebar";
import ResizeObserver from "resize-observer-polyfill";
import { invoke } from "@tauri-apps/api/core";
import DataTable, { type Api } from "datatables.net-bs5";
import "datatables.net-bs5/css/dataTables.bootstrap5.css";
import { listen } from "@tauri-apps/api/event";
import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./frontend_log.ts";
import type { DemoDecodeDiagnosticsPayload } from "./bindings/kb_app_demo_desktop/demo_decode_replay/DemoDecodeDiagnosticsPayload.ts";
@@ -224,7 +226,11 @@ function shortened(value: string, visible: number): string {
return value.length > visible ? `${value.slice(0, visible)}` : value;
}
let annotationJournalTable: Api<DemoTransactionAnnotationRow> | null = null;
function renderAnnotations(rows: DemoTransactionAnnotationRow[]): void {
annotationJournalTable?.destroy();
annotationJournalTable = null;
const body = element<HTMLTableSectionElement>("#annotationJournalBody");
body.replaceChildren();
for (const row of rows) {
@@ -241,6 +247,21 @@ function renderAnnotations(rows: DemoTransactionAnnotationRow[]): void {
);
body.append(tableRow);
}
annotationJournalTable = new DataTable<DemoTransactionAnnotationRow>("#annotationJournalTable", {
autoWidth: false,
pageLength: 25,
lengthMenu: [10, 25, 50, 100],
order: [[0, "desc"]],
scrollX: true,
language: {
emptyTable: "Aucune annotation.",
info: "_START_ à _END_ sur _TOTAL_ annotations",
infoEmpty: "0 annotation",
lengthMenu: "Afficher _MENU_ annotations",
search: "Filtrer :",
zeroRecords: "Aucune annotation correspondante.",
},
});
element<HTMLElement>("#annotationJournalStatus").textContent = `${rows.length} annotation(s) committed chargée(s).`;
}

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/frontend/ts/demo_execution_spl.ts
// version: 3
// version: 4
import * as bootstrap from "bootstrap";
import "simplebar";
@@ -25,9 +25,9 @@ import type { DemoSplAtaDerivationPayload } from "./bindings/kb_app_demo_desktop
import type { DemoSplAtaDerivationRequest } from "./bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaDerivationRequest.ts";
import type { DemoSplAtaJournalRequest } from "./bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaJournalRequest.ts";
import type { DemoSplAtaJournalRow } from "./bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaJournalRow.ts";
import type { DemoExecutionSplToken2022Request } from "./bindings/kb_app_demo_desktop/demo_spl_token2022/DemoExecutionSplToken2022Request.ts";
import type { DemoExecutionSplToken2022SummaryPayload } from "./bindings/kb_app_demo_desktop/demo_spl_token2022/DemoExecutionSplToken2022SummaryPayload.ts";
import type { DemoSplToken2022FixturePayload } from "./bindings/kb_app_demo_desktop/demo_spl_token2022/DemoSplToken2022FixturePayload.ts";
import type { DemoExecutionSplToken2022Request } from "./bindings/kb_app_demo_desktop/demo_spl_token_2022/DemoExecutionSplToken2022Request.ts";
import type { DemoExecutionSplToken2022SummaryPayload } from "./bindings/kb_app_demo_desktop/demo_spl_token_2022/DemoExecutionSplToken2022SummaryPayload.ts";
import type { DemoSplToken2022FixturePayload } from "./bindings/kb_app_demo_desktop/demo_spl_token_2022/DemoSplToken2022FixturePayload.ts";
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
@@ -37,7 +37,7 @@ const logLines: string[] = [];
const maximumLogLines = 1200;
let running = false;
let profileOptions: DemoExecutionSolanaCoreProfileOption[] = [];
let token2022Fixture: DemoSplToken2022FixturePayload | null = null;
let token_2022Fixture: DemoSplToken2022FixturePayload | null = null;
function element<T extends HTMLElement>(selector: string): T {
const value = document.querySelector<T>(selector);
@@ -297,10 +297,18 @@ async function executeAta(submit: boolean): Promise<void> {
if (running) {
return;
}
if (submit && !element<HTMLInputElement>("#executionOperatorConfirmedInput").checked) {
const message = "La confirmation opérateur est obligatoire avant signature ATA.";
appendLog({
timestamp: new Date().toISOString(),
level: "error",
stage: "ata_validation",
message,
});
frontendError("kb-app-demo-desktop.frontend.execution_spl", message);
return;
}
try {
if (submit && !element<HTMLInputElement>("#executionOperatorConfirmedInput").checked) {
throw new Error("La confirmation opérateur est obligatoire avant signature ATA.");
}
const derivation = await deriveAta();
const request: DemoExecutionSplAtaRequest = {
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
@@ -558,86 +566,86 @@ function renderTokenJournal(rows: DemoSplTokenJournalRow[]): void {
function token2022ScenarioAmount(fixture: DemoSplToken2022FixturePayload, scenarioId: string): string {
if (scenarioId === "token2022_mint_to_checked") {
function token_2022ScenarioAmount(fixture: DemoSplToken2022FixturePayload, scenarioId: string): string {
if (scenarioId === "token_2022_mint_to_checked") {
return "1";
}
if (scenarioId === "token2022_approve_checked") {
if (scenarioId === "token_2022_approve_checked") {
return "2";
}
if (scenarioId === "token2022_burn_checked") {
if (scenarioId === "token_2022_burn_checked") {
return "1";
}
return fixture.defaultAmountRaw;
}
function applyToken2022Fixture(): void {
if (!token2022Fixture) {
if (!token_2022Fixture) {
return;
}
const scenarioId = element<HTMLSelectElement>("#token2022ScenarioSelect").value;
element<HTMLInputElement>("#token2022SourceInput").value = scenarioId === "token2022_close_destination"
? token2022Fixture.closeAccount
: token2022Fixture.source;
element<HTMLInputElement>("#token2022MintInput").value = token2022Fixture.mint;
element<HTMLInputElement>("#token2022DestinationInput").value = scenarioId === "token2022_close_destination"
? token2022Fixture.authority
: token2022Fixture.destination;
element<HTMLInputElement>("#token2022DelegateInput").value = token2022Fixture.delegate;
element<HTMLInputElement>("#token2022AuthorityInput").value = token2022Fixture.authority;
element<HTMLInputElement>("#token2022FreezeAuthorityInput").value = token2022Fixture.freezeAuthority;
element<HTMLInputElement>("#token2022AmountInput").value = token2022ScenarioAmount(token2022Fixture, scenarioId);
element<HTMLInputElement>("#token2022DecimalsInput").value = String(token2022Fixture.decimals);
const scenarioId = element<HTMLSelectElement>("#token_2022ScenarioSelect").value;
element<HTMLInputElement>("#token_2022SourceInput").value = scenarioId === "token_2022_close_destination"
? token_2022Fixture.closeAccount
: token_2022Fixture.source;
element<HTMLInputElement>("#token_2022MintInput").value = token_2022Fixture.mint;
element<HTMLInputElement>("#token_2022DestinationInput").value = scenarioId === "token_2022_close_destination"
? token_2022Fixture.authority
: token_2022Fixture.destination;
element<HTMLInputElement>("#token_2022DelegateInput").value = token_2022Fixture.delegate;
element<HTMLInputElement>("#token_2022AuthorityInput").value = token_2022Fixture.authority;
element<HTMLInputElement>("#token_2022FreezeAuthorityInput").value = token_2022Fixture.freezeAuthority;
element<HTMLInputElement>("#token_2022AmountInput").value = token_2022ScenarioAmount(token_2022Fixture, scenarioId);
element<HTMLInputElement>("#token_2022DecimalsInput").value = String(token_2022Fixture.decimals);
}
async function loadToken2022Fixture(): Promise<void> {
try {
token2022Fixture = await invoke<DemoSplToken2022FixturePayload>("demo_spl_token2022_fixture", {
token_2022Fixture = await invoke<DemoSplToken2022FixturePayload>("demo_spl_token_2022_fixture", {
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
});
applyToken2022Fixture();
element<HTMLElement>("#token2022FixtureHelp").textContent = `Fixture: ${token2022Fixture.fixturePath} — program=${token2022Fixture.programId}`;
element<HTMLElement>("#token_2022FixtureHelp").textContent = `Fixture: ${token_2022Fixture.fixturePath} — program=${token_2022Fixture.programId}`;
appendLog({
timestamp: new Date().toISOString(),
level: "info",
stage: "token2022_fixture",
message: `fixture chargée: mint=${token2022Fixture.mint}, source=${token2022Fixture.source}, destination=${token2022Fixture.destination}`,
stage: "token_2022_fixture",
message: `fixture chargée: mint=${token_2022Fixture.mint}, source=${token_2022Fixture.source}, destination=${token_2022Fixture.destination}`,
});
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
token2022Fixture = null;
element<HTMLElement>("#token2022FixtureHelp").textContent = message;
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token2022_fixture", message });
token_2022Fixture = null;
element<HTMLElement>("#token_2022FixtureHelp").textContent = message;
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token_2022_fixture", message });
}
}
function buildToken2022Request(submit: boolean): DemoExecutionSplToken2022Request {
const scenarioId = element<HTMLSelectElement>("#token2022ScenarioSelect").value;
const source = element<HTMLInputElement>("#token2022SourceInput").value.trim();
const mint = element<HTMLInputElement>("#token2022MintInput").value.trim();
const destination = element<HTMLInputElement>("#token2022DestinationInput").value.trim();
const delegate = element<HTMLInputElement>("#token2022DelegateInput").value.trim();
const authority = element<HTMLInputElement>("#token2022AuthorityInput").value.trim();
const freezeAuthority = element<HTMLInputElement>("#token2022FreezeAuthorityInput").value.trim();
const scenarioId = element<HTMLSelectElement>("#token_2022ScenarioSelect").value;
const source = element<HTMLInputElement>("#token_2022SourceInput").value.trim();
const mint = element<HTMLInputElement>("#token_2022MintInput").value.trim();
const destination = element<HTMLInputElement>("#token_2022DestinationInput").value.trim();
const delegate = element<HTMLInputElement>("#token_2022DelegateInput").value.trim();
const authority = element<HTMLInputElement>("#token_2022AuthorityInput").value.trim();
const freezeAuthority = element<HTMLInputElement>("#token_2022FreezeAuthorityInput").value.trim();
if (source.length === 0) {
throw new Error("Le compte source ou cible Token-2022 est obligatoire.");
}
if (scenarioId !== "token2022_revoke" && scenarioId !== "token2022_close_destination" && mint.length === 0) {
if (scenarioId !== "token_2022_revoke" && scenarioId !== "token_2022_close_destination" && mint.length === 0) {
throw new Error("Le mint Token-2022 est obligatoire pour ce scénario.");
}
if (scenarioId === "token2022_transfer_checked" && destination.length === 0) {
if (scenarioId === "token_2022_transfer_checked" && destination.length === 0) {
throw new Error("Le compte destination Token-2022 est obligatoire.");
}
if (scenarioId === "token2022_close_destination" && destination.length === 0) {
if (scenarioId === "token_2022_close_destination" && destination.length === 0) {
throw new Error("La destination lamports est obligatoire pour CloseAccount.");
}
if (scenarioId === "token2022_approve_checked" && delegate.length === 0) {
if (scenarioId === "token_2022_approve_checked" && delegate.length === 0) {
throw new Error("Le delegate est obligatoire pour ApproveChecked.");
}
if (["token2022_freeze_account", "token2022_thaw_account"].includes(scenarioId) && freezeAuthority.length === 0) {
if (["token_2022_freeze_account", "token_2022_thaw_account"].includes(scenarioId) && freezeAuthority.length === 0) {
throw new Error("La freeze authority est obligatoire pour FreezeAccount et ThawAccount.");
}
if (!["token2022_freeze_account", "token2022_thaw_account"].includes(scenarioId) && authority.length === 0) {
if (!["token_2022_freeze_account", "token_2022_thaw_account"].includes(scenarioId) && authority.length === 0) {
throw new Error("Lautorité Token-2022 est obligatoire.");
}
return {
@@ -649,8 +657,8 @@ function buildToken2022Request(submit: boolean): DemoExecutionSplToken2022Reques
delegate,
authority,
freezeAuthority,
amountRaw: element<HTMLInputElement>("#token2022AmountInput").value.trim(),
decimals: integerValue("#token2022DecimalsInput"),
amountRaw: element<HTMLInputElement>("#token_2022AmountInput").value.trim(),
decimals: integerValue("#token_2022DecimalsInput"),
submit,
operatorConfirmed: element<HTMLInputElement>("#executionOperatorConfirmedInput").checked,
forcePostValidationReplay: element<HTMLInputElement>("#executionForceReplayInput").checked,
@@ -692,12 +700,12 @@ async function executeToken2022(submit: boolean): Promise<void> {
if (submit && !request.operatorConfirmed) {
const message = "La confirmation opérateur est obligatoire avant signature Token-2022.";
element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify({ warning: message }, null, 2);
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token2022_failed", message });
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token_2022_failed", message });
return;
}
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token2022_failed", message });
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token_2022_failed", message });
return;
}
setRunning(true);
@@ -705,23 +713,23 @@ async function executeToken2022(submit: boolean): Promise<void> {
appendLog({
timestamp: new Date().toISOString(),
level: "info",
stage: submit ? "token2022_submit" : "token2022_simulation",
stage: submit ? "token_2022_submit" : "token_2022_simulation",
message: `${request.scenarioId}, source=${request.source}`,
});
try {
const summary = await invoke<DemoExecutionSplToken2022SummaryPayload>("demo_execution_spl_token2022_execute", { request });
const summary = await invoke<DemoExecutionSplToken2022SummaryPayload>("demo_execution_spl_token_2022_execute", { request });
displayToken2022Summary(summary);
appendLog({
timestamp: new Date().toISOString(),
level: summary.simulationSuccess ? "info" : "error",
stage: "token2022_completed",
stage: "token_2022_completed",
message: `scenario=${summary.scenarioId}, simulation=${summary.simulationSuccess}, confirmation=${summary.confirmationStatus ?? "none"}, materializations=${summary.materializationCount}, idempotence=${summary.idempotenceValidated}`,
signature: summary.transactionSignature,
});
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify({ error: message }, null, 2);
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token2022_failed", message });
appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "token_2022_failed", message });
frontendError("kb-app-demo-desktop.frontend.demo_execution_spl", `Token-2022 execution failed: ${message}`);
} finally {
setRunning(false);
@@ -841,7 +849,7 @@ document.addEventListener("DOMContentLoaded", () => {
element<HTMLButtonElement>("#loadToken2022FixtureButton").addEventListener("click", () => {
void loadToken2022Fixture();
});
element<HTMLSelectElement>("#token2022ScenarioSelect").addEventListener("change", applyToken2022Fixture);
element<HTMLSelectElement>("#token_2022ScenarioSelect").addEventListener("change", applyToken2022Fixture);
element<HTMLButtonElement>("#simulateToken2022Button").addEventListener("click", () => {
void executeToken2022(false);
});

View File

@@ -1,11 +1,12 @@
// file: kb-app-demo-desktop/frontend/ts/demo_http.ts
// version: 5
// version: 7
import * as bootstrap from "bootstrap";
import "simplebar";
import ResizeObserver from "resize-observer-polyfill";
import { invoke } from "@tauri-apps/api/core";
import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./frontend_log.ts";
import { renderJsonViewer } from "./json_viewer.ts";
import type { DemoHttpExecutionPayload } from "./bindings/kb_app_demo_desktop/demo_http/DemoHttpExecutionPayload.ts";
import type { DemoHttpMethodOption } from "./bindings/kb_app_demo_desktop/demo_http/DemoHttpMethodOption.ts";
import type { DemoHttpOptionsPayload } from "./bindings/kb_app_demo_desktop/demo_http/DemoHttpOptionsPayload.ts";
@@ -17,35 +18,27 @@ import type { DemoHttpRoleOption } from "./bindings/kb_app_demo_desktop/demo_htt
let roleOptions: DemoHttpRoleOption[] = [];
let methodOptions: DemoHttpMethodOption[] = [];
let httpResultBuffer = "Aucune requête exécutée.";
function textInputValue(selector: string): string {
const element = document.querySelector<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(selector);
return element ? element.value.trim() : "";
}
function writeTextarea(selector: string, value: string): void {
const element = document.querySelector<HTMLTextAreaElement>(selector);
if (element) {
element.value = value;
function parseJsonValue(value: string): unknown {
try {
return JSON.parse(value) as unknown;
} catch {
return { raw: value };
}
}
function writeHttpResult(value: string): void {
httpResultBuffer = value;
writeTextarea("#httpResultOutput", httpResultBuffer);
function writeHttpResult(value: unknown): void {
renderJsonViewer("#httpResultOutput", value);
}
function clearHttpResult(): void {
httpResultBuffer = "";
writeTextarea("#httpResultOutput", httpResultBuffer);
}
function jsonText(value: unknown): string {
if (typeof value === "string") {
return value;
}
return JSON.stringify(value, null, 2);
writeHttpResult({ status: "idle", message: "Aucune requête exécutée." });
}
function roleSupportsMethod(role: DemoHttpRoleOption, method: DemoHttpMethodOption): boolean {
@@ -99,8 +92,8 @@ function refreshMethodFields(): void {
}
}
function formatHttpExecutionPayload(payload: DemoHttpExecutionPayload): string {
const header = {
function formatHttpExecutionPayload(payload: DemoHttpExecutionPayload): unknown {
return {
endpointName: payload.endpointName,
provider: payload.provider,
endpointUrl: payload.endpointUrl,
@@ -108,16 +101,8 @@ function formatHttpExecutionPayload(payload: DemoHttpExecutionPayload): string {
method: payload.method,
requestKind: payload.requestKind,
methodClass: payload.methodClass,
response: parseJsonValue(payload.responseJson),
};
return `${JSON.stringify(header, null, 2)}\n\n--- response ---\n${payload.responseJson}`;
}
async function copyTextarea(selector: string): Promise<void> {
const element = document.querySelector<HTMLTextAreaElement>(selector);
if (!element) {
return;
}
await navigator.clipboard.writeText(element.value);
}
async function refreshHttpOptions(): Promise<void> {
@@ -132,7 +117,7 @@ async function refreshHttpOptions(): Promise<void> {
refreshMethodList();
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
writeHttpResult(`Erreur options HTTP : ${message}`);
writeHttpResult({ status: "error", stage: "options", message });
frontendError("kb-app-demo-desktop.frontend.demo_http", `HTTP options loading failed: ${message}`);
}
}
@@ -140,10 +125,10 @@ async function refreshHttpOptions(): Promise<void> {
async function refreshHttpPool(): Promise<void> {
try {
const snapshots = await invoke("demo_http_list_pool_clients");
writeTextarea("#httpPoolOutput", jsonText(snapshots));
renderJsonViewer("#httpPoolOutput", snapshots);
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
writeTextarea("#httpPoolOutput", `Erreur : ${message}`);
renderJsonViewer("#httpPoolOutput", { status: "error", message });
frontendError("kb-app-demo-desktop.frontend.demo_http", `HTTP pool refresh failed: ${message}`);
}
}
@@ -156,14 +141,14 @@ async function executeHttpRequest(): Promise<void> {
configJson: textInputValue("#httpConfigInput") || null,
paramsJson: textInputValue("#httpParamsInput") || null,
};
writeHttpResult("Exécution en cours...");
writeHttpResult({ status: "running", message: "Exécution HTTP en cours." });
try {
const response = await invoke<DemoHttpExecutionPayload>("demo_http_execute_request", { request });
writeHttpResult(formatHttpExecutionPayload(response));
frontendDebug("kb-app-demo-desktop.frontend.demo_http", `HTTP request completed: ${response.method}`);
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
writeHttpResult(`Erreur : ${message}`);
writeHttpResult({ status: "error", message });
frontendError("kb-app-demo-desktop.frontend.demo_http", `HTTP request failed: ${message}`);
}
}
@@ -171,6 +156,8 @@ async function executeHttpRequest(): Promise<void> {
document.addEventListener("DOMContentLoaded", () => {
installFrontendConsoleBridge("kb-app-demo-desktop.frontend.demo_http");
frontendDebug("kb-app-demo-desktop.frontend.demo_http", "HTTP demo window loaded");
renderJsonViewer("#httpPoolOutput", { status: "loading", message: "Chargement des endpoints HTTP." });
clearHttpResult();
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
Array.from(tooltipTriggerList).map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
document.querySelector<HTMLSelectElement>("#httpRoleSelect")?.addEventListener("change", refreshMethodList);
@@ -181,12 +168,6 @@ document.addEventListener("DOMContentLoaded", () => {
document.querySelector<HTMLButtonElement>("#refreshHttpPoolButton")?.addEventListener("click", () => {
void refreshHttpPool();
});
document.querySelector<HTMLButtonElement>("#copyHttpPoolButton")?.addEventListener("click", () => {
void copyTextarea("#httpPoolOutput");
});
document.querySelector<HTMLButtonElement>("#copyHttpResultButton")?.addEventListener("click", () => {
void copyTextarea("#httpResultOutput");
});
document.querySelector<HTMLButtonElement>("#clearHttpResultButton")?.addEventListener("click", clearHttpResult);
void refreshHttpOptions();
void refreshHttpPool();

View File

@@ -26,7 +26,7 @@ async function refreshDiagnostics(): Promise<void> {
setText("#sqlDiagHealth", payload.healthStatus);
setText("#sqlDiagMigration", payload.migrationStatus);
setText("#sqlDiagStatus", "OK");
renderTables("#sqlDiagTableBody", payload.tables);
renderTables("#sqlDiagTable", "#sqlDiagTableBody", payload.tables);
latestPayload = payload;
renderJsonViewer("#sqlDiagJson", payload);
frontendDebug("kb_app_demo.frontend.demo_sql_diag", "SQL diagnostics refreshed");

View File

@@ -19,7 +19,7 @@ async function refreshCore(): Promise<void> {
setText("#sqlCoreProfile", payload.activeProfileName);
setText("#sqlCoreDsn", payload.maskedDsn);
setText("#sqlCoreStatus", "OK");
renderTables("#sqlCoreTableBody", payload.tables);
renderTables("#sqlCoreTable", "#sqlCoreTableBody", payload.tables);
renderJsonViewer("#sqlCoreJson", payload);
frontendDebug("kb_app_demo.frontend.demo_sql_pg_core", "PostgreSQL core diagnostics refreshed");
} catch (caughtError) {

View File

@@ -19,7 +19,7 @@ async function refreshRaw(): Promise<void> {
setText("#sqlRawProfile", payload.activeProfileName);
setText("#sqlRawDsn", payload.maskedDsn);
setText("#sqlRawStatus", "OK");
renderTables("#sqlRawTableBody", payload.tables);
renderTables("#sqlRawTable", "#sqlRawTableBody", payload.tables);
renderJsonViewer("#sqlRawJson", payload);
frontendDebug("kb_app_demo.frontend.demo_sql_pg_raw", "PostgreSQL raw diagnostics refreshed");
} catch (caughtError) {

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/frontend/ts/demo_sql_replay_candidates.ts
// version: 7
// version: 8
import * as bootstrap from "bootstrap";
import "simplebar";

View File

@@ -1,9 +1,13 @@
// file: kb-app-demo-desktop/frontend/ts/demo_sql_tables.ts
// version: 2
// version: 4
import "@andypf/json-viewer";
import DataTable, { type Api } from "datatables.net-bs5";
import "datatables.net-bs5/css/dataTables.bootstrap5.css";
import { jsonText, renderJsonViewer } from "./json_viewer.ts";
import type { DemoSqlTableSnapshot } from "./bindings/kb_app_demo_desktop/demo_sql/DemoSqlTableSnapshot";
const tableInstances = new Map<string, Api>();
export function setText(selector: string, value: string): void {
const element = document.querySelector<HTMLElement>(selector);
if (element) {
@@ -11,8 +15,13 @@ export function setText(selector: string, value: string): void {
}
}
export function renderTables(selector: string, tables: DemoSqlTableSnapshot[]): void {
const tbody = document.querySelector<HTMLTableSectionElement>(selector);
export function renderTables(tableSelector: string, bodySelector: string, tables: DemoSqlTableSnapshot[]): void {
const existing = tableInstances.get(tableSelector);
if (existing) {
existing.destroy();
tableInstances.delete(tableSelector);
}
const tbody = document.querySelector<HTMLTableSectionElement>(bodySelector);
if (!tbody) {
return;
}
@@ -29,32 +38,28 @@ export function renderTables(selector: string, tables: DemoSqlTableSnapshot[]):
row.appendChild(cell(table.latestCreatedAt ?? "—", false));
tbody.appendChild(row);
}
}
export function jsonText(value: unknown): string {
return JSON.stringify(value, null, 2);
}
export function renderJsonViewer(selector: string, value: unknown): void {
const container = document.querySelector<HTMLElement>(selector);
if (!container) {
const tableElement = document.querySelector<HTMLTableElement>(tableSelector);
if (!tableElement) {
return;
}
const viewer = document.createElement("andypf-json-viewer");
viewer.setAttribute("indent", "2");
viewer.setAttribute("expanded", "1");
viewer.setAttribute("theme", "default-light");
viewer.setAttribute("show-data-types", "true");
viewer.setAttribute("show-toolbar", "false");
viewer.setAttribute("expand-icon-type", "arrow");
viewer.setAttribute("show-copy", "true");
viewer.setAttribute("show-size", "true");
viewer.setAttribute("expand-empty", "false");
viewer.setAttribute("data", jsonText(value));
container.textContent = "";
container.appendChild(viewer);
tableInstances.set(tableSelector, new DataTable(tableElement, {
scrollX: true,
pageLength: 25,
lengthMenu: [10, 25, 50, 100],
order: [[0, "asc"]],
language: {
emptyTable: "Aucune table disponible",
info: "Tables _START_ à _END_ sur _TOTAL_",
infoEmpty: "Aucune table",
lengthMenu: "Afficher _MENU_ tables",
search: "Rechercher :",
zeroRecords: "Aucune table correspondante",
},
}));
}
export { jsonText, renderJsonViewer };
function cell(value: string, code: boolean): HTMLTableCellElement {
const td = document.createElement("td");
if (code) {

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/frontend/ts/demo_ws.ts
// version: 7
// version: 8
import * as bootstrap from "bootstrap";
import "simplebar";
@@ -7,6 +7,7 @@ import ResizeObserver from "resize-observer-polyfill";
import {invoke} from "@tauri-apps/api/core";
import {listen} from "@tauri-apps/api/event";
import {frontendDebug, frontendError, installFrontendConsoleBridge} from "./frontend_log.ts";
import {renderJsonViewer} from "./json_viewer.ts";
import type {DemoWsExecutionPayload} from "./bindings/kb_app_demo_desktop/demo_ws/DemoWsExecutionPayload.ts";
import type {DemoWsMessagePayload} from "./bindings/kb_app_demo_desktop/demo_ws/DemoWsMessagePayload.ts";
import type {DemoWsMethodOption} from "./bindings/kb_app_demo_desktop/demo_ws/DemoWsMethodOption.ts";
@@ -81,13 +82,6 @@ function clearWsMessages(): void {
writeWsMessages("");
}
function jsonText(value: unknown): string {
if (typeof value === "string") {
return value;
}
return JSON.stringify(value, null, 2);
}
function roleSupportsMethod(role: DemoWsRoleOption, method: DemoWsMethodOption): boolean {
return role.requestKinds.includes("*") || role.requestKinds.includes(method.requestKind);
}
@@ -176,7 +170,7 @@ function setConnectedUi(status: DemoWsStatusPayload): void {
disconnectButton.disabled = !status.connected;
}
refreshSubscriptionSelect(status);
writeTextarea("#wsStatusOutput", jsonText(status));
renderJsonViewer("#wsStatusOutput", status);
}
function formatWsExecutionPayload(payload: DemoWsExecutionPayload): string {
@@ -221,10 +215,10 @@ async function refreshWsOptions(): Promise<void> {
async function refreshWsPool(): Promise<void> {
try {
const snapshots = await invoke("demo_ws_list_pool_clients");
writeTextarea("#wsPoolOutput", jsonText(snapshots));
renderJsonViewer("#wsPoolOutput", snapshots);
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
writeTextarea("#wsPoolOutput", `Erreur : ${message}`);
renderJsonViewer("#wsPoolOutput", {status: "error", message});
frontendError("kb-app-demo-desktop.frontend.demo_ws", `WebSocket pool refresh failed: ${message}`);
}
}
@@ -235,7 +229,7 @@ async function refreshWsStatus(): Promise<void> {
setConnectedUi(status);
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
writeTextarea("#wsStatusOutput", `Erreur statut WebSocket : ${message}`);
renderJsonViewer("#wsStatusOutput", {status: "error", message});
frontendError("kb-app-demo-desktop.frontend.demo_ws", `WebSocket status failed: ${message}`);
}
}
@@ -330,9 +324,6 @@ document.addEventListener("DOMContentLoaded", () => {
document.querySelector<HTMLButtonElement>("#refreshWsPoolButton")?.addEventListener("click", () => {
void refreshWsPool();
});
document.querySelector<HTMLButtonElement>("#copyWsPoolButton")?.addEventListener("click", () => {
void copyTextarea("#wsPoolOutput");
});
document.querySelector<HTMLButtonElement>("#copyWsResultButton")?.addEventListener("click", () => {
void copyTextarea("#wsResultOutput");
});

View File

@@ -98,6 +98,7 @@ function upgradeJsonTextareas(): void {
const container = document.createElement("div");
container.className = "app-json-viewer";
const viewer = document.createElement("andypf-json-viewer");
viewer.classList.add("app-json-viewer-element");
viewer.setAttribute("indent", "2");
viewer.setAttribute("expanded", "1");
viewer.setAttribute("theme", "default-light");
@@ -108,7 +109,7 @@ function upgradeJsonTextareas(): void {
let currentValue = textarea.value;
const render = (value: string): void => {
currentValue = value;
let parsed: unknown = value;
let parsed: unknown;
try {
parsed = JSON.parse(value) as unknown;
} catch {

View File

@@ -0,0 +1,28 @@
// file: kb-app-demo-desktop/frontend/ts/json_viewer.ts
// version: 2
import "@andypf/json-viewer";
export function jsonText(value: unknown): string {
return JSON.stringify(value, null, 2);
}
export function renderJsonViewer(selector: string, value: unknown): void {
const container = document.querySelector<HTMLElement>(selector);
if (!container) {
return;
}
const viewer = document.createElement("andypf-json-viewer");
viewer.classList.add("app-json-viewer-element");
viewer.setAttribute("indent", "2");
viewer.setAttribute("expanded", "1");
viewer.setAttribute("theme", "default-light");
viewer.setAttribute("show-data-types", "true");
viewer.setAttribute("show-toolbar", "false");
viewer.setAttribute("expand-icon-type", "arrow");
viewer.setAttribute("show-copy", "true");
viewer.setAttribute("show-size", "true");
viewer.setAttribute("expand-empty", "false");
viewer.setAttribute("data", jsonText(value));
container.replaceChildren(viewer);
}

View File

@@ -178,7 +178,7 @@ pub(crate) struct DemoBackfillSummaryPayload {
/// Number of source attempts.
#[ts(type = "number")]
pub(crate) attempts: u64,
/// Whether the operator cancelled the campaign.
/// Whether the operator canceled the campaign.
pub(crate) cancelled: bool,
/// Optional cursor for continuing an older-history scan.
pub(crate) resume_before_signature: std::option::Option<std::string::String>,

View File

@@ -1,11 +1,25 @@
// file: kb-app-demo-desktop/src/demo_core_extraction.rs
// version: 10
// version: 11
//! Tauri commands and UI payloads for canonical transaction to core extraction.
use tauri::Emitter; // rust-rules: trait-import
use ts_rs::TS; // rust-rules: derive-import
/// One registered program offered as a non-blocking core extraction suggestion.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_core_extraction/DemoCoreExtractionProgramOption.ts"
)]
pub(crate) struct DemoCoreExtractionProgramOption {
/// Stable registry code.
pub(crate) code: std::string::String,
/// Canonical Solana program id.
pub(crate) program_id: std::string::String,
}
/// Initial options shown by the core extraction demo.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
@@ -22,6 +36,8 @@ pub(crate) struct DemoCoreExtractionOptionsPayload {
pub(crate) default_max_concurrent_extractions: u32,
/// Whether one extraction campaign is currently running.
pub(crate) running: bool,
/// Registered program ids offered without restricting free text entry.
pub(crate) programs: std::vec::Vec<crate::DemoCoreExtractionProgramOption>,
}
/// UI request for one bounded core extraction campaign.
@@ -166,6 +182,15 @@ pub(crate) fn demo_core_extraction_options_payload(
default_limit: 100,
default_max_concurrent_extractions: 4,
running: state.demo_core_extraction_running().load(std::sync::atomic::Ordering::Acquire),
programs: kb_program_ids::registered_program_ids()
.iter()
.map(|entry| {
return crate::DemoCoreExtractionProgramOption {
code: entry.code().to_string(),
program_id: entry.program_id().to_string(),
};
})
.collect(),
};
}

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_decode_replay.rs
// version: 30
// version: 32
//! Tauri commands and UI payloads for contextual instruction decode replay.
@@ -143,9 +143,12 @@ pub(crate) struct DemoDecodeProcessorSummaryPayload {
/// Unsupported input count.
#[ts(type = "number")]
pub(crate) unsupported: u64,
/// Failed input count.
/// Explicit decoder failure count.
#[ts(type = "number")]
pub(crate) failed: u64,
/// Orchestration, storage or other processing error count.
#[ts(type = "number")]
pub(crate) processing_errors: u64,
/// Materialized output count.
#[ts(type = "number")]
pub(crate) materialized_outputs: u64,
@@ -181,9 +184,12 @@ pub(crate) struct DemoDecodeReplaySummaryPayload {
/// Number never started after cancellation.
#[ts(type = "number")]
pub(crate) not_started: u64,
/// Number of failed contextual inputs.
/// Number of inputs returning an explicit decoder failure.
#[ts(type = "number")]
pub(crate) failed_inputs: u64,
/// Number of inputs interrupted by orchestration, storage or processing errors.
#[ts(type = "number")]
pub(crate) processing_error_inputs: u64,
/// Whether cancellation was observed.
pub(crate) cancelled: bool,
/// Per-processor counters.
@@ -544,6 +550,7 @@ pub(crate) fn demo_decode_replay_summary_payload(
unmatched: summary.unmatched,
not_started: summary.not_started,
failed_inputs: summary.failed_inputs,
processing_error_inputs: summary.processing_error_inputs,
cancelled: summary.cancelled,
processors: summary
.processors
@@ -558,6 +565,7 @@ pub(crate) fn demo_decode_replay_summary_payload(
ignored: processor.ignored,
unsupported: processor.unsupported,
failed: processor.failed,
processing_errors: processor.processing_errors,
materialized_outputs: processor.materialized_outputs,
materialization_refused: processor.materialization_refused,
};
@@ -733,7 +741,7 @@ mod tests {
}
#[test]
fn native_memo_token2022_elgamal_classic_token_and_ata_decoders_are_registered() {
fn native_memo_token_2022_elgamal_classic_token_and_ata_decoders_are_registered() {
let decoders = crate::available_decoders();
assert_eq!(decoders.len(), 7);
let mut names = decoders
@@ -748,7 +756,7 @@ mod tests {
"spl_elgamal_registry".to_string(),
"spl_memo".to_string(),
"spl_token".to_string(),
"spl_token2022".to_string(),
"spl_token_2022".to_string(),
];
expected_names.sort();
assert_eq!(names, expected_names);

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_execution_solana_core.rs
// version: 5
// version: 6
//! Tauri adapter for bounded Solana Core execution on Devnet.
@@ -187,9 +187,12 @@ pub(crate) struct DemoExecutionSolanaCoreSummaryPayload {
/// Contextual decode inputs completed after submission.
#[ts(type = "number | null")]
pub(crate) decode_completed: std::option::Option<u64>,
/// Contextual decode input failures after submission.
/// Explicit contextual decoder failures after submission.
#[ts(type = "number | null")]
pub(crate) decode_failed_inputs: std::option::Option<u64>,
/// Contextual decode processing or storage errors after submission.
#[ts(type = "number | null")]
pub(crate) decode_processing_error_inputs: std::option::Option<u64>,
/// Pretty JSON representation of the exact execution plan.
pub(crate) plan_json: std::string::String,
/// Pretty JSON representation of the simulation result.
@@ -421,6 +424,8 @@ pub(crate) fn demo_execution_solana_core_summary_payload(
let decode_completed = summary.decode_replay.as_ref().map(|value| return value.completed);
let decode_failed_inputs =
summary.decode_replay.as_ref().map(|value| return value.failed_inputs);
let decode_processing_error_inputs =
summary.decode_replay.as_ref().map(|value| return value.processing_error_inputs);
let diagnostics_value = serde_json::json!({
"airdropConfirmation": summary.airdrop_confirmation.as_ref(),
"confirmation": summary.confirmation.as_ref(),
@@ -449,6 +454,7 @@ pub(crate) fn demo_execution_solana_core_summary_payload(
"completed": value.completed,
"unmatched": value.unmatched,
"failedInputs": value.failed_inputs,
"processingErrorInputs": value.processing_error_inputs,
"cancelled": value.cancelled
});
})
@@ -477,6 +483,7 @@ pub(crate) fn demo_execution_solana_core_summary_payload(
core_extracted,
decode_completed,
decode_failed_inputs,
decode_processing_error_inputs,
plan_json,
simulation_json,
diagnostics_json,
@@ -507,9 +514,11 @@ pub(crate) fn demo_execution_solana_core_memo_summary_payload(
summary.post_execution.as_ref().is_some_and(|value| return value.materialized);
let idempotence_validated = summary.idempotence_replay.as_ref().is_some_and(|replay| {
return replay.failed_inputs == 0
&& replay.processing_error_inputs == 0
&& !replay.cancelled
&& replay.processors.iter().all(|processor| {
return processor.failed == 0
&& processor.processing_errors == 0
&& processor.materialized_outputs == 0
&& processor.materialization_refused == 0;
})
@@ -546,6 +555,7 @@ pub(crate) fn demo_execution_solana_core_memo_summary_payload(
"selected": value.selected,
"completed": value.completed,
"failedInputs": value.failed_inputs,
"processingErrorInputs": value.processing_error_inputs,
"processors": value.processors.iter().map(|processor| {
return serde_json::json!({
"name": processor.processor_name,
@@ -553,6 +563,7 @@ pub(crate) fn demo_execution_solana_core_memo_summary_payload(
"decoded": processor.decoded,
"skipped": processor.skipped,
"failed": processor.failed,
"processingErrors": processor.processing_errors,
"materializedOutputs": processor.materialized_outputs,
"materializationRefused": processor.materialization_refused
});
@@ -565,6 +576,7 @@ pub(crate) fn demo_execution_solana_core_memo_summary_payload(
"selected": value.selected,
"completed": value.completed,
"failedInputs": value.failed_inputs,
"processingErrorInputs": value.processing_error_inputs,
"processors": value.processors.iter().map(|processor| {
return serde_json::json!({
"name": processor.processor_name,
@@ -572,6 +584,7 @@ pub(crate) fn demo_execution_solana_core_memo_summary_payload(
"decoded": processor.decoded,
"skipped": processor.skipped,
"failed": processor.failed,
"processingErrors": processor.processing_errors,
"materializedOutputs": processor.materialized_outputs,
"materializationRefused": processor.materialization_refused
});

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_spl_ata.rs
// version: 4
// version: 7
//! Thin Tauri adapter for ATA execution, derivation and lifecycle journal reads.
@@ -19,7 +19,7 @@ pub(crate) struct DemoExecutionSplAtaRequest {
pub(crate) wallet_owner: std::string::String,
/// Mint account.
pub(crate) mint: std::string::String,
/// `classic` or `token2022`.
/// `classic` or `token_2022`.
pub(crate) token_program: std::string::String,
/// `create` or `create_idempotent`.
pub(crate) mode: std::string::String,
@@ -43,7 +43,7 @@ pub(crate) struct DemoSplAtaDerivationRequest {
pub(crate) profile_name: std::string::String,
/// Mint account.
pub(crate) mint: std::string::String,
/// `classic` or `token2022`.
/// `classic` or `token_2022`.
pub(crate) token_program: std::string::String,
}
@@ -185,8 +185,8 @@ pub(crate) fn parse_token_program(
) -> std::result::Result<kb_lib::ExSplAssociatedTokenProgram, std::string::String> {
return match value.trim() {
"classic" => std::result::Result::Ok(kb_lib::ExSplAssociatedTokenProgram::Classic),
"token2022" => std::result::Result::Ok(kb_lib::ExSplAssociatedTokenProgram::Token2022),
_ => std::result::Result::Err("Token Program must be classic or token2022".to_string()),
"token_2022" => std::result::Result::Ok(kb_lib::ExSplAssociatedTokenProgram::Token2022),
_ => std::result::Result::Err("Token Program must be classic or token_2022".to_string()),
};
}
@@ -247,8 +247,10 @@ pub(crate) fn demo_spl_ata_summary_payload(
});
let idempotence_validated = summary.idempotence_replay.as_ref().is_some_and(|replay| {
return replay.failed_inputs == 0
&& replay.processing_error_inputs == 0
&& replay.processors.iter().all(|processor| {
return processor.failed == 0
&& processor.processing_errors == 0
&& processor.materialized_outputs == 0
&& processor.materialization_refused == 0;
});
@@ -289,6 +291,7 @@ pub(crate) fn demo_spl_ata_summary_payload(
"completed": value.completed,
"unmatched": value.unmatched,
"failedInputs": value.failed_inputs,
"processingErrorInputs": value.processing_error_inputs,
"cancelled": value.cancelled
});
}),
@@ -299,6 +302,7 @@ pub(crate) fn demo_spl_ata_summary_payload(
"completed": value.completed,
"unmatched": value.unmatched,
"failedInputs": value.failed_inputs,
"processingErrorInputs": value.processing_error_inputs,
"cancelled": value.cancelled,
"materializedOutputs": value.processors.iter().map(|processor| {
return processor.materialized_outputs;
@@ -397,15 +401,15 @@ pub(crate) fn journal_row(row: kb_store::MaterializedEventQueryRow) -> crate::De
#[cfg(test)]
mod tests {
#[test]
fn classic_and_token2022_derivations_are_distinct_and_canonical() {
fn classic_and_token_2022_derivations_are_distinct_and_canonical() {
let wallet = kb_program_ids::SYSTEM_PROGRAM_ID;
let mint = "So11111111111111111111111111111111111111112";
let classic = crate::derive_ata(wallet, mint, kb_program_ids::SPL_TOKEN_PROGRAM_ID)
.unwrap_or_else(|error| panic!("classic derivation failed: {error}"));
let token2022 = crate::derive_ata(wallet, mint, kb_program_ids::SPL_TOKEN2022_PROGRAM_ID)
let token_2022 = crate::derive_ata(wallet, mint, kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID)
.unwrap_or_else(|error| panic!("Token-2022 derivation failed: {error}"));
assert_eq!(classic, "aqxoAhCwpy3oB1BpNw9hL1HdLYLgPpbPjzxDrrQj3Fs");
assert_eq!(token2022, "2sZUUBGq1i6aE47ZoxCaCW89jmYm2EXLPPmNMgMDXHMS");
assert_eq!(token_2022, "2sZUUBGq1i6aE47ZoxCaCW89jmYm2EXLPPmNMgMDXHMS");
}
#[test]

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_spl_token.rs
// version: 2
// version: 3
//! Thin Tauri adapter for classic SPL Token execution and materialized journals.
@@ -172,9 +172,11 @@ pub(crate) fn demo_spl_token_summary_payload(
let decode_replayed = post_execution.is_some_and(|value| return value.decode_replayed);
let idempotence_validated = summary.idempotence_replay.as_ref().is_some_and(|replay| {
return replay.failed_inputs == 0
&& replay.processing_error_inputs == 0
&& !replay.cancelled
&& replay.processors.iter().all(|processor| {
return processor.failed == 0
&& processor.processing_errors == 0
&& processor.materialized_outputs == 0
&& processor.materialization_refused == 0;
});
@@ -248,6 +250,7 @@ fn replay_diagnostics(summary: &kb_pipeline::DecodeReplaySummary) -> serde_json:
"selected": summary.selected,
"completed": summary.completed,
"failedInputs": summary.failed_inputs,
"processingErrorInputs": summary.processing_error_inputs,
"cancelled": summary.cancelled,
"processors": summary.processors.iter().map(|processor| {
return serde_json::json!({
@@ -256,6 +259,7 @@ fn replay_diagnostics(summary: &kb_pipeline::DecodeReplaySummary) -> serde_json:
"decoded": processor.decoded,
"skipped": processor.skipped,
"failed": processor.failed,
"processingErrors": processor.processing_errors,
"materializedOutputs": processor.materialized_outputs,
"materializationRefused": processor.materialization_refused
});

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_spl_token2022.rs
// version: 4
// file: kb-app-demo-desktop/src/demo_spl_token_2022.rs
// version: 6
//! Thin Tauri adapter for independent public Token-2022 Devnet validation scenarios.
@@ -10,7 +10,7 @@ use ts_rs::TS; // rust-rules: derive-import
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_token2022/DemoSplToken2022FixturePayload.ts"
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_token_2022/DemoSplToken2022FixturePayload.ts"
)]
pub(crate) struct DemoSplToken2022FixturePayload {
/// Fixture file used by the application.
@@ -42,7 +42,7 @@ pub(crate) struct DemoSplToken2022FixturePayload {
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_token2022/DemoExecutionSplToken2022Request.ts"
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_token_2022/DemoExecutionSplToken2022Request.ts"
)]
pub(crate) struct DemoExecutionSplToken2022Request {
/// Selected Devnet profile.
@@ -78,7 +78,7 @@ pub(crate) struct DemoExecutionSplToken2022Request {
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_token2022/DemoExecutionSplToken2022SummaryPayload.ts"
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_token_2022/DemoExecutionSplToken2022SummaryPayload.ts"
)]
pub(crate) struct DemoExecutionSplToken2022SummaryPayload {
/// Stable scenario identifier.
@@ -137,14 +137,14 @@ pub(crate) fn operation_from_request(
multisig_signers: std::vec::Vec::new(),
};
let value = match request.scenario_id.trim() {
"token2022_mint_to_checked" => kb_lib::ExSplTokenSingleOperation::MintToChecked {
"token_2022_mint_to_checked" => kb_lib::ExSplTokenSingleOperation::MintToChecked {
mint: kb_lib::MdPubkey(request.mint.trim().to_string()),
destination: kb_lib::MdPubkey(request.source.trim().to_string()),
authority,
amount: kb_lib::ExSplTokenAmount(request.amount_raw.trim().to_string()),
decimals: request.decimals,
},
"token2022_transfer_checked" => kb_lib::ExSplTokenSingleOperation::TransferChecked {
"token_2022_transfer_checked" => kb_lib::ExSplTokenSingleOperation::TransferChecked {
source: kb_lib::MdPubkey(request.source.trim().to_string()),
mint: kb_lib::MdPubkey(request.mint.trim().to_string()),
destination: kb_lib::MdPubkey(request.destination.trim().to_string()),
@@ -152,7 +152,7 @@ pub(crate) fn operation_from_request(
amount: kb_lib::ExSplTokenAmount(request.amount_raw.trim().to_string()),
decimals: request.decimals,
},
"token2022_approve_checked" => kb_lib::ExSplTokenSingleOperation::ApproveChecked {
"token_2022_approve_checked" => kb_lib::ExSplTokenSingleOperation::ApproveChecked {
source: kb_lib::MdPubkey(request.source.trim().to_string()),
mint: kb_lib::MdPubkey(request.mint.trim().to_string()),
delegate: kb_lib::MdPubkey(request.delegate.trim().to_string()),
@@ -160,28 +160,28 @@ pub(crate) fn operation_from_request(
amount: kb_lib::ExSplTokenAmount(request.amount_raw.trim().to_string()),
decimals: request.decimals,
},
"token2022_revoke" => kb_lib::ExSplTokenSingleOperation::Revoke {
"token_2022_revoke" => kb_lib::ExSplTokenSingleOperation::Revoke {
source: kb_lib::MdPubkey(request.source.trim().to_string()),
authority,
},
"token2022_burn_checked" => kb_lib::ExSplTokenSingleOperation::BurnChecked {
"token_2022_burn_checked" => kb_lib::ExSplTokenSingleOperation::BurnChecked {
source: kb_lib::MdPubkey(request.source.trim().to_string()),
mint: kb_lib::MdPubkey(request.mint.trim().to_string()),
authority,
amount: kb_lib::ExSplTokenAmount(request.amount_raw.trim().to_string()),
decimals: request.decimals,
},
"token2022_freeze_account" => kb_lib::ExSplTokenSingleOperation::FreezeAccount {
"token_2022_freeze_account" => kb_lib::ExSplTokenSingleOperation::FreezeAccount {
account: kb_lib::MdPubkey(request.source.trim().to_string()),
mint: kb_lib::MdPubkey(request.mint.trim().to_string()),
authority: freeze_authority,
},
"token2022_thaw_account" => kb_lib::ExSplTokenSingleOperation::ThawAccount {
"token_2022_thaw_account" => kb_lib::ExSplTokenSingleOperation::ThawAccount {
account: kb_lib::MdPubkey(request.source.trim().to_string()),
mint: kb_lib::MdPubkey(request.mint.trim().to_string()),
authority: freeze_authority,
},
"token2022_close_destination" => kb_lib::ExSplTokenSingleOperation::CloseAccount {
"token_2022_close_destination" => kb_lib::ExSplTokenSingleOperation::CloseAccount {
account: kb_lib::MdPubkey(request.source.trim().to_string()),
destination: kb_lib::MdPubkey(request.destination.trim().to_string()),
authority,
@@ -197,7 +197,7 @@ pub(crate) fn operation_from_request(
});
}
pub(crate) fn demo_spl_token2022_summary_payload(
pub(crate) fn demo_spl_token_2022_summary_payload(
scenario_id: std::string::String,
operation: std::string::String,
summary: kb_pipeline_demo_scenarios::DevnetSplToken2022ExecutionSummary,
@@ -216,9 +216,11 @@ pub(crate) fn demo_spl_token2022_summary_payload(
let decode_replayed = post_execution.is_some_and(|value| return value.decode_replayed);
let idempotence_validated = summary.idempotence_replay.as_ref().is_some_and(|replay| {
return replay.failed_inputs == 0
&& replay.processing_error_inputs == 0
&& !replay.cancelled
&& replay.processors.iter().all(|processor| {
return processor.failed == 0
&& processor.processing_errors == 0
&& processor.materialized_outputs == 0
&& processor.materialization_refused == 0;
});
@@ -340,6 +342,7 @@ fn decode_replay_summary_json(summary: &kb_pipeline::DecodeReplaySummary) -> ser
"ignored": processor.ignored,
"unsupported": processor.unsupported,
"failed": processor.failed,
"processingErrors": processor.processing_errors,
"materializedOutputs": processor.materialized_outputs,
"materializationRefused": processor.materialization_refused
});
@@ -354,6 +357,7 @@ fn decode_replay_summary_json(summary: &kb_pipeline::DecodeReplaySummary) -> ser
"unmatched": summary.unmatched,
"notStarted": summary.not_started,
"failedInputs": summary.failed_inputs,
"processingErrorInputs": summary.processing_error_inputs,
"cancelled": summary.cancelled,
"processors": processors,
"startedAt": summary.started_at,
@@ -384,14 +388,14 @@ mod tests {
#[test]
fn every_public_scenario_builds_one_typed_operation() {
for scenario in [
"token2022_mint_to_checked",
"token2022_transfer_checked",
"token2022_approve_checked",
"token2022_revoke",
"token2022_burn_checked",
"token2022_freeze_account",
"token2022_thaw_account",
"token2022_close_destination",
"token_2022_mint_to_checked",
"token_2022_transfer_checked",
"token_2022_approve_checked",
"token_2022_revoke",
"token_2022_burn_checked",
"token_2022_freeze_account",
"token_2022_thaw_account",
"token_2022_close_destination",
] {
assert!(crate::operation_from_request(&request(scenario)).is_ok());
}

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_sql_common.rs
// version: 10
// version: 11
//! Shared SQL demo helpers and serializable payloads.
@@ -138,7 +138,10 @@ pub(crate) async fn initialize_postgres_schema_for_startup(
tracing::debug!(target: crate::TRACING_TARGET, backend = profile.database.backend.as_str(), "database backend is not postgres; skip SQL schema initialization");
return;
}
let options_result = postgres_store_options_from_profile(profile, false);
let options_result = postgres_store_options_from_profile(
profile,
profile.database.postgres.auto_initialize_schema,
);
let mut options = match options_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_ws.rs
// version: 16
// version: 17
//! Standard Solana WebSocket demo commands backed by `kb_onchain_transport::WsSession`.
@@ -922,7 +922,7 @@ mod tests {
);
let program = super::build_standard_ws_request(
"programSubscribe",
std::option::Option::Some(kb_program_ids::SPL_TOKEN2022_PROGRAM_ID.to_string()),
std::option::Option::Some(kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string()),
std::option::Option::None,
std::option::Option::Some(r#"{"encoding":"jsonParsed"}"#.to_string()),
);

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/lib.rs
// version: 11
// version: 13
//! Tauri desktop demo application for `khadhroony-bot3`.
@@ -18,7 +18,7 @@ mod demo_execution_spl;
mod demo_http;
mod demo_spl_ata;
mod demo_spl_token;
mod demo_spl_token2022;
mod demo_spl_token_2022;
mod demo_sql_common;
mod demo_sql_diag;
mod demo_sql_pg_core;
@@ -66,6 +66,8 @@ pub(crate) use self::demo_core_extraction::DemoCoreExtractionObserver;
/// Internal demo core extraction item.
pub(crate) use self::demo_core_extraction::DemoCoreExtractionOptionsPayload;
/// Internal demo core extraction item.
pub(crate) use self::demo_core_extraction::DemoCoreExtractionProgramOption;
/// Internal demo core extraction item.
pub(crate) use self::demo_core_extraction::DemoCoreExtractionProgressPayload;
/// Internal demo core extraction item.
pub(crate) use self::demo_core_extraction::DemoCoreExtractionRequest;
@@ -95,7 +97,7 @@ pub(crate) use self::demo_decode_replay::DemoDecodeReplayDecoderOption;
pub(crate) use self::demo_decode_replay::DemoDecodeReplayObserver;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoDecodeReplayOptionsPayload;
/// One known program suggested by the free-form Program ID field.
/// Internal demo decode replay program option.
pub(crate) use self::demo_decode_replay::DemoDecodeReplayProgramOption;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoDecodeReplayProgressPayload;
@@ -220,17 +222,17 @@ pub(crate) use self::demo_spl_token::payload_matches;
/// Loads a bounded, typed journal of committed classic SPL Token projections.
pub(crate) use self::demo_spl_token::validate_journal_request;
/// Request for one independent public Token-2022 Devnet scenario.
pub(crate) use self::demo_spl_token2022::DemoExecutionSplToken2022Request;
pub(crate) use self::demo_spl_token_2022::DemoExecutionSplToken2022Request;
/// UI-safe result of one public Token-2022 Devnet orchestration.
pub(crate) use self::demo_spl_token2022::DemoExecutionSplToken2022SummaryPayload;
pub(crate) use self::demo_spl_token_2022::DemoExecutionSplToken2022SummaryPayload;
/// Public values loaded from one persisted Token-2022 validation fixture.
pub(crate) use self::demo_spl_token2022::DemoSplToken2022FixturePayload;
pub(crate) use self::demo_spl_token_2022::DemoSplToken2022FixturePayload;
/// ????
pub(crate) use self::demo_spl_token2022::demo_spl_token2022_summary_payload;
pub(crate) use self::demo_spl_token_2022::demo_spl_token_2022_summary_payload;
/// ????
pub(crate) use self::demo_spl_token2022::operation_from_request;
pub(crate) use self::demo_spl_token_2022::operation_from_request;
/// ????
pub(crate) use self::demo_spl_token2022::parse_fixture;
pub(crate) use self::demo_spl_token_2022::parse_fixture;
/// UI-safe SQL table diagnostic snapshot.
pub(crate) use self::demo_sql_common::DemoSqlTableSnapshot;
/// Connects to the configured PostgreSQL store.

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/tauri.rs
// version: 15
// version: 17
//! Tauri runtime assembly and private command wrappers.
@@ -76,8 +76,8 @@ pub fn run() -> kb_core::Result<()> {
demo_execution_spl_validation_scenarios,
demo_execution_spl_token_execute,
demo_spl_token_journal,
demo_execution_spl_token2022_execute,
demo_spl_token2022_fixture,
demo_execution_spl_token_2022_execute,
demo_spl_token_2022_fixture,
demo_spl_ata_derive,
demo_execution_spl_ata_execute,
demo_spl_ata_journal,
@@ -424,6 +424,7 @@ async fn demo_decode_replay_execute(
unmatched = summary.unmatched,
not_started = summary.not_started,
failed_inputs = summary.failed_inputs,
processing_error_inputs = summary.processing_error_inputs,
cancelled = summary.cancelled,
processors = ?summary.processors,
"contextual decode replay command completed"
@@ -1326,13 +1327,13 @@ fn demo_execution_spl_validation_scenarios()
label: scenario.label,
family: match scenario.family {
kb_pipeline_demo_scenarios::DevnetSplValidationFamily::Token2022Public => {
"token2022_public".to_string()
"token_2022_public".to_string()
},
kb_pipeline_demo_scenarios::DevnetSplValidationFamily::ElGamalRegistry => {
"elgamal_registry".to_string()
},
kb_pipeline_demo_scenarios::DevnetSplValidationFamily::Token2022Confidential => {
"token2022_confidential".to_string()
"token_2022_confidential".to_string()
},
},
operation_code: scenario.operation_code,
@@ -1712,7 +1713,7 @@ async fn demo_spl_ata_journal(
/// Executes one public Token-2022 simulation or explicitly authorized Devnet submission.
#[tauri::command]
async fn demo_execution_spl_token2022_execute(
async fn demo_execution_spl_token_2022_execute(
app_handle: tauri::AppHandle,
state: tauri::State<'_, crate::AppState>,
request: crate::DemoExecutionSplToken2022Request,
@@ -1775,7 +1776,7 @@ async fn demo_execution_spl_token2022_execute(
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
};
let workspace_root = crate::workspace_root_dir();
let summary = match kb_pipeline_demo_scenarios::execute_devnet_spl_token2022(
let summary = match kb_pipeline_demo_scenarios::execute_devnet_spl_token_2022(
&http_pool,
&store,
&profile,
@@ -1790,7 +1791,7 @@ async fn demo_execution_spl_token2022_execute(
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
return std::result::Result::Ok(crate::demo_spl_token2022_summary_payload(
return std::result::Result::Ok(crate::demo_spl_token_2022_summary_payload(
request.scenario_id,
operation_code,
summary,
@@ -1799,7 +1800,7 @@ async fn demo_execution_spl_token2022_execute(
/// Loads public Token-2022 fixture values without reading private key bytes.
#[tauri::command]
fn demo_spl_token2022_fixture(
fn demo_spl_token_2022_fixture(
state: tauri::State<'_, crate::AppState>,
profile_name: std::string::String,
) -> std::result::Result<crate::DemoSplToken2022FixturePayload, std::string::String> {
@@ -1813,7 +1814,7 @@ fn demo_spl_token2022_fixture(
} else {
crate::workspace_root_dir().join(configured)
};
let fixture_path = wallet_dir.join("spl_token2022_validation").join("fixture.env");
let fixture_path = wallet_dir.join("spl_token_2022_validation").join("fixture.env");
let contents = match std::fs::read_to_string(&fixture_path) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/vite.config.ts
// version: 6
// version: 7
import { defineConfig, normalizePath } from "vite";
import { NodePackageImporter } from "sass-embedded";
@@ -40,7 +40,7 @@ export default defineConfig(() => ({
entryFileNames: 'js/[name]-[hash].js',
chunkFileNames: 'js/chunks/[name]-[hash].js',
assetFileNames: (assetInfo) => {
const originalName = assetInfo.name ?? '';
const originalName = assetInfo.names[0] ?? '';
const ext = originalName.substring(originalName.lastIndexOf('.') + 1).toLowerCase();
if (ext === 'js') {
return 'js/[name]-[hash][extname]';