v0.2.12-pre.002-fix.001

This commit is contained in:
2026-08-27 09:01:14 +02:00
parent d1d683d101
commit 0d01017c31
13 changed files with 422 additions and 69 deletions

View File

@@ -0,0 +1,25 @@
<!-- file: crates/ksp-app-solprices-desk/frontend/fonts/README.md -->
<!-- version: 1 -->
# Fonts du splash SOL Prices Desk
SOL Prices Desk réutilise le gabarit de splash KSP déjà stabilisé dans Config Desk et Wallet Desk et conserve la police locale :
```text
DOS_Amazigh.ttf
font-family: Dos Amazigh
```
Destination :
```text
crates/ksp-app-solprices-desk/frontend/fonts/DOS_Amazigh.ttf
```
Empreinte SHA-256 de l'asset repris du gabarit :
```text
a01ff4b5a0d699db7cefcf5336bf0e379ff6fbf0d096087571a13c8782f9aa02
```
`splash.scss` déclare `Dos Amazigh` avec `@font-face` et l'applique au titre `#app-name`.

View File

@@ -1,11 +1,11 @@
<!-- file: crates/ksp-app-solprices-desk/frontend/main.html -->
<!-- version: 2 -->
<!-- version: 3 -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<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>SOL Prices Desk — Prices</title>
@@ -21,7 +21,6 @@
<span class="mx-2 fs-5 text-body-secondary" aria-hidden="true"></span>
<span id="headerViewTitle" class="fs-5 text-body text-nowrap">Prices</span>
</div>
<span id="runtimeVersionBadge" class="badge text-bg-secondary">0.2.12-pre.2</span>
</div>
</nav>
</header>
@@ -51,7 +50,7 @@
<p class="text-body-secondary mb-0">Scaffold desktop actif. Le runtime Off-chain et la table SOL/USD arrivent dans les tranches suivantes.</p>
</div>
</div>
<div class="scaffold-placeholder border rounded-3 d-flex align-items-center justify-content-center bg-body-tertiary text-center p-4">
<div class="app-placeholder">
<div>
<i class="fa-solid fa-chart-line fa-3x text-body-secondary mb-3" aria-hidden="true"></i>
<h2 class="h5">Surface prix volontairement inactive</h2>
@@ -69,7 +68,7 @@
<div class="card shadow-sm">
<div class="card-header fw-semibold">Runtime</div>
<div class="card-body">
<dl class="row mb-0">
<dl class="row mb-0 app-runtime-list">
<dt class="col-sm-5">Version</dt>
<dd id="runtimeVersion" class="col-sm-7"></dd>
<dt class="col-sm-5">Phase</dt>

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-solprices-desk/frontend/sass/_app.scss
// version: 2
// version: 3
$app-header-height: 72px;
$app-footer-height: 42px;
@@ -46,22 +46,22 @@ body {
min-width: 0;
}
.app-scrollable {
height: 100%;
max-height: 100%;
}
.app-logo {
display: block;
width: auto;
height: 42px;
}
.app-scrollable {
height: 100%;
max-height: 100%;
}
.app-shell-card {
max-width: 1320px;
}
.app-shell-status {
.app-runtime-list dd {
font-family: var(--bs-font-monospace);
}
@@ -85,10 +85,21 @@ body {
white-space: nowrap;
}
.app-overview-list dd {
font-family: var(--bs-font-monospace);
.app-placeholder {
min-height: 320px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1rem;
padding: 2rem;
border: 1px dashed var(--bs-border-color);
border-radius: var(--bs-border-radius-lg);
color: var(--bs-secondary-color);
background: var(--bs-body-bg);
text-align: center;
}
.scaffold-placeholder {
min-height: 280px;
.app-placeholder i {
font-size: 2.5rem;
}

View File

@@ -1,19 +1,19 @@
<!-- file: crates/ksp-app-solprices-desk/frontend/splash.html -->
<!-- version: 1 -->
<!-- version: 2 -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<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/splash.scss">
<title>Chargement — SOL Prices Desk</title>
<title>KSP SOL Prices Desk — Chargement</title>
</head>
<body>
<div id="splash-container" style="opacity: 0;">
<img id="splash-image" src="imgs/splash.png" alt="Chargement de KSP Sol Prices Desk">
<img id="splash-image" src="imgs/splash.png" alt="Chargement de KSP SOL Prices Desk">
<div id="app-name">SOL Prices Desk</div>
<div id="debug-info" aria-live="off" hidden></div>
<div id="messages-container" aria-live="polite"></div>

View File

@@ -1,23 +1,29 @@
// file: crates/ksp-app-solprices-desk/frontend/ts/main.ts
// version: 1
// version: 2
import "bootstrap";
import ResizeObserver from "resize-observer-polyfill";
import "simplebar";
import { getCurrentWindow } from "@tauri-apps/api/window";
import type { RuntimeStatusDto } from "./bindings/ksp_app_solprices_desk/dto_common/RuntimeStatusDto.ts";
import { frontendDebug, frontendInfo, frontendTrace, installFrontendConsoleBridge } from "./frontend_log";
import { frontendDebug, frontendInfo, frontendTrace, frontendWarn, installFrontendConsoleBridge } from "./frontend_log";
import { invokeKsp } from "./invoke";
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
installFrontendConsoleBridge("main");
const viewTitles: Record<string, string> = {
type ViewId = "prices" | "diagnostics";
const viewTitles: Record<ViewId, string> = {
prices: "Prices",
diagnostics: "Diagnostics",
};
function activateView(viewId: string): void {
if (!(viewId in viewTitles)) {
return;
}
function isViewId(value: string | undefined): value is ViewId {
return value === "prices" || value === "diagnostics";
}
function activateView(viewId: ViewId, source: "startup" | "user"): void {
document.querySelectorAll<HTMLElement>("[data-view-panel]").forEach(panel => {
panel.hidden = panel.dataset.viewPanel !== viewId;
});
@@ -28,7 +34,22 @@ function activateView(viewId: string): void {
if (header) {
header.textContent = viewTitles[viewId];
}
frontendDebug("main", "SOL Prices Desk view activated", { viewId });
document.title = `SOL Prices Desk — ${viewTitles[viewId]}`;
if (source !== "startup") {
frontendDebug("main", "SOL Prices Desk view activated", { viewId, source });
}
}
function bindNavigation(): void {
document.querySelectorAll<HTMLButtonElement>("[data-view]").forEach(button => {
button.addEventListener("click", () => {
const viewId = button.dataset.view;
if (isViewId(viewId)) {
activateView(viewId, "user");
}
});
});
frontendTrace("main", "SOL Prices Desk navigation handlers installed");
}
function renderRuntimeStatus(status: RuntimeStatusDto): void {
@@ -45,10 +66,6 @@ function renderRuntimeStatus(status: RuntimeStatusDto): void {
element.textContent = value;
}
}
const badge = document.querySelector<HTMLElement>("#runtimeVersionBadge");
if (badge) {
badge.textContent = status.applicationVersion;
}
const diagnostic = document.querySelector<HTMLElement>("#runtimeDiagnostic");
if (diagnostic) {
diagnostic.hidden = status.startupDiagnostic === null;
@@ -60,19 +77,26 @@ function renderRuntimeStatus(status: RuntimeStatusDto): void {
});
}
async function initializeMain(): Promise<void> {
frontendInfo("main", "SOL Prices Desk scaffold frontend loaded");
document.querySelectorAll<HTMLButtonElement>("[data-view]").forEach(button => {
button.addEventListener("click", () => {
const viewId = button.dataset.view;
if (viewId) {
activateView(viewId);
}
});
});
const status = await invokeKsp<RuntimeStatusDto>("main", "runtime_status");
async function loadRuntimeStatus(): Promise<void> {
const status = await invokeKsp<RuntimeStatusDto>("main", "get_runtime_status");
renderRuntimeStatus(status);
activateView("prices");
}
async function initializeMain(): Promise<void> {
const windowLabel = getCurrentWindow().label;
frontendInfo("main", "SOL Prices Desk main frontend loaded", { windowLabel });
bindNavigation();
activateView("prices", "startup");
try {
await loadRuntimeStatus();
} catch {
const diagnostic = document.querySelector<HTMLElement>("#runtimeDiagnostic");
if (diagnostic) {
diagnostic.hidden = false;
diagnostic.textContent = "Le statut runtime initial n'a pas pu être chargé.";
}
frontendWarn("main", "SOL Prices Desk startup runtime status load failed");
}
}
document.addEventListener("DOMContentLoaded", () => {

View File

@@ -1,7 +1,7 @@
{
"name": "ksp-app-solprices-desk",
"private": true,
"version": "0.2.12-pre.2",
"version": "0.2.12-pre.2.fix.1",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-solprices-desk/src/tauri.rs
// version: 1
// version: 3
//! Tauri runtime assembly for the KSP SOL prices desktop application.
@@ -72,7 +72,7 @@ fn configure_plugins(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<taur
#[allow(clippy::question_mark_used)] // Tauri generates the question-mark operator internally for async command dispatch.
fn configure_commands(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
return builder.invoke_handler(tauri::generate_handler![emit_frontend_log, runtime_status, splash_frontend_ready]);
return builder.invoke_handler(tauri::generate_handler![emit_frontend_log, get_runtime_status, splash_frontend_ready,]);
}
fn configure_setup(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
@@ -85,10 +85,20 @@ fn configure_setup(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri:
if let std::result::Result::Err(error) = main {
return std::result::Result::Err(std::boxed::Box::new(error));
}
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_SHELL, "SOL Prices Desk Tauri shell setup completed");
return std::result::Result::Ok(());
});
}
#[tauri::command]
fn get_runtime_status(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::RuntimeStatusDto, crate::CommandErrorDto> {
let result = state.runtime_status();
return match result {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
};
}
#[tauri::command]
fn emit_frontend_log(payload: crate::FrontendLogPayloadDto) -> std::result::Result<(), crate::CommandErrorDto> {
let result = crate::emit_frontend_log_event(payload);
@@ -98,15 +108,6 @@ fn emit_frontend_log(payload: crate::FrontendLogPayloadDto) -> std::result::Resu
};
}
#[tauri::command]
fn runtime_status(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::RuntimeStatusDto, crate::CommandErrorDto> {
let result = state.runtime_status();
return match result {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
};
}
#[tauri::command]
async fn splash_frontend_ready(
app: tauri::AppHandle,

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "KSP SOL Prices Desk",
"version": "0.2.12-pre.2",
"version": "0.2.12-pre.2.fix.1",
"identifier": "com.sasedev.ksp-app-solprices-desk",
"build": {
"beforeDevCommand": {

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-app-solprices-desk/tests/desktop_contract.rs
// version: 1
// version: 2
//! Desktop scaffold contract audits for SOL Prices Desk `0.2.12-pre.002`.
//! Desktop scaffold and shared-template contract audits for SOL Prices Desk `0.2.12-pre.002` and its fixes.
fn app_root() -> std::path::PathBuf {
return std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
@@ -37,7 +37,9 @@ fn pre_002_shell_uses_reserved_ports_windows_and_owned_backend_paths() {
let tauri = read_json(root.join("tauri.conf.json").as_path());
assert_eq!(tauri.pointer("/productName").and_then(serde_json::Value::as_str), std::option::Option::Some("KSP SOL Prices Desk"));
assert_eq!(tauri.pointer("/identifier").and_then(serde_json::Value::as_str), std::option::Option::Some("com.sasedev.ksp-app-solprices-desk"));
assert_eq!(tauri.pointer("/version").and_then(serde_json::Value::as_str), std::option::Option::Some("0.2.12-pre.2"));
assert_eq!(tauri.pointer("/version").and_then(serde_json::Value::as_str), std::option::Option::Some("0.2.12-pre.2.fix.1"));
let package = read_json(root.join("package.json").as_path());
assert_eq!(package.pointer("/version").and_then(serde_json::Value::as_str), std::option::Option::Some("0.2.12-pre.2.fix.1"));
assert_eq!(tauri.pointer("/build/devUrl").and_then(serde_json::Value::as_str), std::option::Option::Some("http://localhost:1434"));
assert_eq!(tauri.pointer("/build/beforeDevCommand/script").and_then(serde_json::Value::as_str), std::option::Option::Some("npm run dev"));
assert_eq!(tauri.pointer("/build/beforeDevCommand/cwd").and_then(serde_json::Value::as_str), std::option::Option::Some("."));
@@ -75,7 +77,8 @@ fn pre_002_package_is_mixed_lib_bin_and_frontend_is_scaffold_only() {
let lib_rs = read_text(root.join("src/lib.rs").as_path());
assert!(lib_rs.contains("pub use self::tauri::run"));
let tauri_rs = read_text(root.join("src/tauri.rs").as_path());
assert!(tauri_rs.contains("tauri::generate_handler![emit_frontend_log, runtime_status, splash_frontend_ready]"));
assert!(tauri_rs.contains("get_runtime_status"));
assert!(!tauri_rs.contains("fn runtime_status("));
let main_html = read_text(root.join("frontend/main.html").as_path());
assert!(main_html.contains("data-view=\"prices\""));
assert!(main_html.contains("data-view=\"diagnostics\""));
@@ -98,3 +101,50 @@ fn pre_002_packaging_contains_current_ten_config_resources_only() {
assert!(tauri_source.contains("ksp_config_lib::prepare_packaged_runtime"));
assert!(tauri_source.contains("std::env::set_current_dir(layout.runtime_root())"));
}
#[test]
fn pre_002_fix_001_reuses_the_common_ksp_desktop_template() {
let root = app_root();
let package = read_text(root.join("package.json").as_path());
for dependency in ["@fltsci/tauri-plugin-tracing", "@fortawesome/fontawesome-free", "@tauri-apps/api", "bootstrap", "resize-observer-polyfill", "simplebar"]
{
assert!(package.contains(dependency), "missing common frontend dependency {dependency}");
}
let main_typescript = read_text(root.join("frontend/ts/main.ts").as_path());
assert!(main_typescript.contains(r#"import ResizeObserver from "resize-observer-polyfill""#));
assert!(main_typescript.contains(".ResizeObserver = ResizeObserver"));
assert!(main_typescript.contains("getCurrentWindow().label"));
assert!(main_typescript.contains(r#""get_runtime_status""#));
let main_html = read_text(root.join("frontend/main.html").as_path());
assert!(main_html.contains(r#"<footer class="app-footer bg-dark text-light">"#));
assert!(main_html.contains(r#"class="app-placeholder""#));
assert!(main_html.contains(r#"class="row mb-0 app-runtime-list""#));
assert!(!main_html.contains("runtimeVersionBadge"));
let splash_html = read_text(root.join("frontend/splash.html").as_path());
assert!(splash_html.contains("<title>KSP SOL Prices Desk — Chargement</title>"));
let splash_scss = read_text(root.join("frontend/sass/splash.scss").as_path());
assert!(splash_scss.contains("DOS_Amazigh.ttf"));
assert!(splash_scss.contains(r#"font-family: "Dos Amazigh", sans-serif"#));
let font_manifest = read_text(root.join("frontend/fonts/README.md").as_path());
assert!(font_manifest.contains("a01ff4b5a0d699db7cefcf5336bf0e379ff6fbf0d096087571a13c8782f9aa02"));
let font = std::fs::read(root.join("frontend/fonts/DOS_Amazigh.ttf"));
let config_font = std::fs::read(root.join("../ksp-app-config-desk/frontend/fonts/DOS_Amazigh.ttf"));
let wallet_font = std::fs::read(root.join("../ksp-app-wallet-desk/frontend/fonts/DOS_Amazigh.ttf"));
assert!(font.is_ok());
assert!(config_font.is_ok());
assert!(wallet_font.is_ok());
if let (std::result::Result::Ok(font), std::result::Result::Ok(config_font), std::result::Result::Ok(wallet_font)) = (font, config_font, wallet_font) {
assert_eq!(font, config_font);
assert_eq!(font, wallet_font);
}
for relative in ["_bootswatch.scss", "_fontawesome.scss", "_simplebar.scss", "_variables.scss", "main.scss", "splash.scss"] {
let sol = read_text(root.join("frontend/sass").join(relative).as_path());
let config = read_text(root.join("../ksp-app-config-desk/frontend/sass").join(relative).as_path());
let wallet = read_text(root.join("../ksp-app-wallet-desk/frontend/sass").join(relative).as_path());
let sol_body = sol.lines().skip(3).collect::<std::vec::Vec<_>>().join("\n");
let config_body = config.lines().skip(3).collect::<std::vec::Vec<_>>().join("\n");
let wallet_body = wallet.lines().skip(3).collect::<std::vec::Vec<_>>().join("\n");
assert_eq!(sol_body, config_body, "SOL Prices Desk diverges from Config Desk template in {relative}");
assert_eq!(sol_body, wallet_body, "SOL Prices Desk diverges from Wallet Desk template in {relative}");
}
}