feat(desktop): signature updater Tauri + wizard persistant + protocole E2E (#77)
CI / lint (push) Successful in 1m11s
CI / security (push) Successful in 43s
CI / test (push) Successful in 2m22s
CI / build (push) Successful in 1m41s
CI / e2e (push) Successful in 10m50s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
CI / lint (push) Successful in 1m11s
CI / security (push) Successful in 43s
CI / test (push) Successful in 2m22s
CI / build (push) Successful in 1m41s
CI / e2e (push) Successful in 10m50s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
- Signature des mises a jour Tauri : paire de cles minisign generee, cle publique dans tauri.conf.json, createUpdaterArtifacts actif, secrets CI exposes (repli build non signe si secret absent). - Wizard 1er lancement : etat persistant wizard_done cote Rust (get_wizard_state/complete_wizard) pour ne plus reafficher la banniere, retro-compatible. - Protocole des 6 tests E2E manuels : docs/DESKTOP_E2E_CHECKLIST.md. - CI : desktop.test.mjs ajoute au job lint. - Docs : ROADMAP, fiche desktop-tauri, CHANGELOG, README desktop, guide releases. - fix(build): retirer les libs WeasyPrint inutiles du stage builder Docker.
This commit is contained in:
@@ -38,7 +38,7 @@ jobs:
|
||||
- name: Frontend unit tests
|
||||
run: node tests/frontend/unit.test.mjs
|
||||
|
||||
- name: Frontend JSDOM tests (PaneManager + Excalidraw + Plugins + AI + SW + Collab + Mobile + Semantic)
|
||||
- name: Frontend JSDOM tests (PaneManager + Excalidraw + Plugins + AI + SW + Collab + Mobile + Semantic + Desktop)
|
||||
run: |
|
||||
cd tests/frontend
|
||||
if [ -d node_modules ]; then
|
||||
@@ -50,6 +50,7 @@ jobs:
|
||||
node collab.test.mjs
|
||||
node mobile-editor.test.mjs
|
||||
node semantic-search.test.mjs
|
||||
node desktop.test.mjs
|
||||
else
|
||||
echo "tests/frontend/node_modules missing - installing jsdom"
|
||||
npm install --no-audit --no-fund --silent
|
||||
@@ -61,6 +62,7 @@ jobs:
|
||||
node collab.test.mjs
|
||||
node mobile-editor.test.mjs
|
||||
node semantic-search.test.mjs
|
||||
node desktop.test.mjs
|
||||
fi
|
||||
|
||||
# ── Tests ─────────────────────────────────────────────────────────
|
||||
|
||||
@@ -37,13 +37,27 @@ jobs:
|
||||
|
||||
- name: Build MSI
|
||||
working-directory: desktop
|
||||
run: cargo tauri build --bundles msi
|
||||
shell: powershell
|
||||
env:
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
run: |
|
||||
if ([string]::IsNullOrEmpty($env:TAURI_SIGNING_PRIVATE_KEY)) {
|
||||
Write-Host "TAURI_SIGNING_PRIVATE_KEY absent - build sans artefacts de mise a jour."
|
||||
cargo tauri build --bundles msi --config '{"bundle":{"createUpdaterArtifacts":false}}'
|
||||
} else {
|
||||
Write-Host "Signature des artefacts de mise a jour activee."
|
||||
cargo tauri build --bundles msi
|
||||
}
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
|
||||
- name: Upload MSI artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: obsigate-windows-msi
|
||||
path: desktop/target/release/bundle/msi/*.msi
|
||||
path: |
|
||||
desktop/target/release/bundle/msi/*.msi
|
||||
desktop/target/release/bundle/msi/*.msi.sig
|
||||
retention-days: 30
|
||||
|
||||
- name: Publish to Gitea Release
|
||||
@@ -82,11 +96,31 @@ jobs:
|
||||
|
||||
- name: Build AppImage
|
||||
working-directory: desktop
|
||||
run: cargo tauri build --bundles appimage
|
||||
env:
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
run: |
|
||||
if [ -z "$TAURI_SIGNING_PRIVATE_KEY" ]; then
|
||||
echo "TAURI_SIGNING_PRIVATE_KEY absent - build sans artefacts de mise a jour."
|
||||
cargo tauri build --bundles appimage --config '{"bundle":{"createUpdaterArtifacts":false}}'
|
||||
else
|
||||
echo "Signature des artefacts de mise a jour activee."
|
||||
cargo tauri build --bundles appimage
|
||||
fi
|
||||
|
||||
- name: Build deb
|
||||
working-directory: desktop
|
||||
run: cargo tauri build --bundles deb
|
||||
env:
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
run: |
|
||||
if [ -z "$TAURI_SIGNING_PRIVATE_KEY" ]; then
|
||||
echo "TAURI_SIGNING_PRIVATE_KEY absent - build sans artefacts de mise a jour."
|
||||
cargo tauri build --bundles deb --config '{"bundle":{"createUpdaterArtifacts":false}}'
|
||||
else
|
||||
echo "Signature des artefacts de mise a jour activee."
|
||||
cargo tauri build --bundles deb
|
||||
fi
|
||||
|
||||
- name: Upload Linux artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -94,5 +128,7 @@ jobs:
|
||||
name: obsigate-linux
|
||||
path: |
|
||||
desktop/target/release/bundle/appimage/*.AppImage
|
||||
desktop/target/release/bundle/appimage/*.AppImage.sig
|
||||
desktop/target/release/bundle/deb/*.deb
|
||||
desktop/target/release/bundle/deb/*.deb.sig
|
||||
retention-days: 30
|
||||
|
||||
@@ -31,3 +31,7 @@ desktop/backend/
|
||||
desktop/frontend/
|
||||
backend/VERSION
|
||||
|
||||
# Tauri updater signing keys (private key — never commit)
|
||||
desktop/*.key
|
||||
desktop/*.key.pub
|
||||
|
||||
|
||||
@@ -14,6 +14,24 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
### Ajouté
|
||||
|
||||
- **#77 Desktop — Signature des mises à jour Tauri** — la paire de clés `minisign`
|
||||
de l'updater est générée et sa clé publique est embarquée dans
|
||||
`desktop/tauri.conf.json` (`plugins.updater.pubkey`, remplace le placeholder) ;
|
||||
`bundle.createUpdaterArtifacts: true` produit les fichiers `.sig` par artefact.
|
||||
Le workflow `.gitea/workflows/desktop-build.yml` expose les secrets
|
||||
`TAURI_SIGNING_PRIVATE_KEY` / `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` aux builds
|
||||
Windows et Linux, avec repli automatique en build **non signé** si le secret est
|
||||
absent (CI toujours verte). Procédure complète :
|
||||
[docs/DEVELOPMENT_AND_RELEASES.md](./docs/DEVELOPMENT_AND_RELEASES.md#2bis-signature-des-mises-à-jour-updater-tauri).
|
||||
Reste à produire le manifeste `latest.json`.
|
||||
|
||||
- **#77 Desktop — Protocole de tests E2E manuels** — nouveau
|
||||
[`docs/DESKTOP_E2E_CHECKLIST.md`](./docs/DESKTOP_E2E_CHECKLIST.md) : prérequis,
|
||||
étapes et résultat attendu pour les 6 scénarios OS non automatisables
|
||||
(installation, tray, notifications natives, association `.md`, auto-update,
|
||||
désinstallation), avec emplacements des logs/config et procédure de signature
|
||||
de l'updater Tauri.
|
||||
|
||||
- **#70 Recherche sémantique — Embeddings vectoriels** — la recherche comprend désormais le
|
||||
**sens** de la requête en plus des mots-clés. **Embeddings** : chaque document est découpé en
|
||||
chunks de 512 mots (recouvrement 64) puis vectorisé (384 dim) via `all-MiniLM-L6-v2`
|
||||
@@ -156,6 +174,24 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
### Corrigé
|
||||
|
||||
- **#77 Desktop — bannière de premier lancement réaffichée à chaque démarrage** —
|
||||
l'état du wizard « Choisissez votre vault » n'était mémorisé que dans le
|
||||
`localStorage` de la webview et le paramètre `hasVaultPath` n'était jamais
|
||||
renseigné. Ajout d'un booléen persistant `wizard_done` dans la config desktop
|
||||
(`desktop/src/main.rs`, commandes `get_wizard_state` / `complete_wizard`,
|
||||
rétro-compatible via `#[serde(default)]`) : la bannière ne réapparaît plus après
|
||||
un choix de dossier ou un clic « Plus tard », même si le stockage webview est
|
||||
vidé. Tests Rust (`desktop/src/main.rs`) et JSDOM
|
||||
(`tests/frontend/desktop.test.mjs`) ajoutés.
|
||||
|
||||
- **Build Docker — « No space left » lors de `apt-get install`** — le stage *builder*
|
||||
installait inutilement `libpango-1.0-0`, `libpangocairo-1.0-0` et `shared-mime-info`
|
||||
(libs runtime de WeasyPrint, sans usage à la compilation), dupliquant les téléchargements
|
||||
apt. Ces paquets sont retirés du builder et `apt-get clean` est ajouté dans les deux
|
||||
stages pour purger les `.deb` conservés par défaut sous Debian trixie, réduisant
|
||||
l'empreinte disque et le temps de build. En complément, purge du cache de build
|
||||
(`docker builder prune`) ayant libéré l'espace nécessaire.
|
||||
|
||||
- **BUG-005 — Chargement mobile incomplet derrière Cloudflare (`og.dracodev.net`)** —
|
||||
le service worker utilisait une stratégie **cache-first avec un nom de cache fixe**
|
||||
(`obsigate-v1`) et un précache de chemins erronés (`/frontend/js/…`) : le Cache Storage
|
||||
|
||||
+3
-1
@@ -3,7 +3,8 @@
|
||||
FROM python:3.11-slim AS builder
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends gcc libffi-dev libc6-dev libpango-1.0-0 libpangocairo-1.0-0 shared-mime-info \
|
||||
&& apt-get install -y --no-install-recommends gcc libffi-dev libc6-dev \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /build
|
||||
@@ -25,6 +26,7 @@ COPY --from=builder /install /usr/local
|
||||
# WeasyPrint runtime dependencies
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends libpango-1.0-0 libpangocairo-1.0-0 shared-mime-info \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy application code
|
||||
|
||||
@@ -12,6 +12,7 @@ Application desktop native pour [ObsiGate](https://git.dracodev.net/Projets/Obsi
|
||||
- [Configuration des vaults](#configuration-des-vaults)
|
||||
- [Architecture](#architecture)
|
||||
- [Fonctionnalités natives](#fonctionnalites-natives)
|
||||
- [Signature de code Windows](#signature-de-code-windows)
|
||||
- [Dépannage](#depannage)
|
||||
|
||||
---
|
||||
@@ -49,6 +50,11 @@ sudo apt install libwebkit2gtk-4.1-0 libgtk-3-0 libayatana-appindicator3-1
|
||||
:: Mêmes options, inclut le raccourci
|
||||
```
|
||||
|
||||
> **Binaires non signés** : les releases ne sont pas signées avec un certificat
|
||||
> Windows. Au premier lancement, SmartScreen affiche « Windows a protégé votre
|
||||
> PC » → cliquer **Informations complémentaires → Exécuter quand même**.
|
||||
> Voir [Signature de code](#signature-de-code-windows) pour les alternatives.
|
||||
|
||||
### macOS
|
||||
|
||||
> Non supporté pour le moment (priorité Linux/Windows).
|
||||
@@ -260,6 +266,58 @@ Arrêt (tray → Quitter ou Ctrl+C) :
|
||||
|
||||
---
|
||||
|
||||
## Signature de code Windows
|
||||
|
||||
La signature de code est **optionnelle** : sans elle, l'application fonctionne,
|
||||
mais SmartScreen affiche un avertissement au premier lancement. Les binaires
|
||||
ObsiGate sont actuellement distribués **non signés**.
|
||||
|
||||
### Signer localement
|
||||
|
||||
Le script `scripts/sign-windows.ps1` signe le binaire et les installeurs après un
|
||||
`cargo tauri build`. Il lit les identifiants depuis l'environnement (jamais
|
||||
commités) et est un **no-op explicite** si aucun certificat n'est fourni :
|
||||
|
||||
```powershell
|
||||
$env:OBSIGATE_SIGN_CERT_PFX = "C:\certs\obsigate.pfx"
|
||||
$env:OBSIGATE_SIGN_CERT_PASSWORD = "..."
|
||||
$env:OBSIGATE_SIGN_TIMESTAMP_URL = "http://timestamp.digicert.com"
|
||||
.\scripts\sign-windows.ps1
|
||||
```
|
||||
|
||||
### Alternatives au certificat
|
||||
|
||||
| Option | Coût indicatif | Effet SmartScreen |
|
||||
|---|---|---|
|
||||
| **Livrer non signé** (statu quo) | 0 € | Avertissement → « Exécuter quand même » |
|
||||
| **SignPath.io** (projet open source) | Gratuit si éligible OSS | Réputation gérée par le service |
|
||||
| **Certum Open Source Code Signing** | ~70-100 €/an | Réputation progressive |
|
||||
| **Certificat OV** | ~150-400 €/an | Avertit tant que la réputation n'est pas établie |
|
||||
| **Certificat EV** | ~300-700 €/an + token USB/HSM | Réputation **immédiate** |
|
||||
| **Azure Trusted Signing** | ~10 $/mois | Bonne réputation, signature cloud |
|
||||
| **Certificat auto-signé** | 0 € | Inutile en distribution publique |
|
||||
|
||||
### Signature de l'auto-update (gratuite)
|
||||
|
||||
Indépendante de la signature Windows, la signature des mises à jour Tauri repose
|
||||
sur une paire de clés que vous générez vous-même :
|
||||
|
||||
```bash
|
||||
cargo tauri signer generate -w ~/.tauri/obsigate.key
|
||||
```
|
||||
|
||||
- La **clé publique** est déjà renseignée dans `plugins.updater.pubkey`
|
||||
(`tauri.conf.json`).
|
||||
- La **clé privée** doit être exposée au build via `TAURI_SIGNING_PRIVATE_KEY`
|
||||
(jamais commitée) ; dans le CI, via les secrets Gitea
|
||||
`TAURI_SIGNING_PRIVATE_KEY` / `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`.
|
||||
- Le CLI produit des `.sig` par artefact (`*.msi.sig`, `*.AppImage.sig`, …).
|
||||
|
||||
> Le manifeste `latest.json` consommé par l'updater n'est pas généré par le CLI :
|
||||
> voir [DEVELOPMENT_AND_RELEASES §2bis](../docs/DEVELOPMENT_AND_RELEASES.md#2bis-signature-des-mises-à-jour-updater-tauri).
|
||||
|
||||
---
|
||||
|
||||
## Dépannage
|
||||
|
||||
### Le backend ne démarre pas
|
||||
|
||||
@@ -78,6 +78,11 @@ struct AppConfig {
|
||||
window_y: Option<f64>,
|
||||
window_width: Option<f64>,
|
||||
window_height: Option<f64>,
|
||||
/// True once the user has picked a vault or dismissed the first-run wizard.
|
||||
/// `#[serde(default)]` keeps existing config files (written before this
|
||||
/// field existed) parseable instead of falling back to a full reset.
|
||||
#[serde(default)]
|
||||
wizard_done: bool,
|
||||
}
|
||||
|
||||
impl Default for AppConfig {
|
||||
@@ -90,6 +95,7 @@ impl Default for AppConfig {
|
||||
window_y: None,
|
||||
window_width: Some(1200.0),
|
||||
window_height: Some(800.0),
|
||||
wizard_done: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -286,6 +292,7 @@ fn get_config() -> AppConfig {
|
||||
fn save_vault_path(path: String) -> Result<(), String> {
|
||||
let mut config = load_config();
|
||||
config.vault_path = Some(path);
|
||||
config.wizard_done = true;
|
||||
save_config(&config);
|
||||
info!("Vault path saved: {}", config.vault_path.as_deref().unwrap_or("none"));
|
||||
Ok(())
|
||||
@@ -305,6 +312,7 @@ async fn pick_vault_folder(app: tauri::AppHandle) -> Result<String, String> {
|
||||
let p_str = p.to_string();
|
||||
let mut config = load_config();
|
||||
config.vault_path = Some(p_str.clone());
|
||||
config.wizard_done = true;
|
||||
save_config(&config);
|
||||
Ok(p_str)
|
||||
}
|
||||
@@ -312,6 +320,20 @@ async fn pick_vault_folder(app: tauri::AppHandle) -> Result<String, String> {
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_wizard_state() -> bool {
|
||||
load_config().wizard_done
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn complete_wizard() -> Result<(), String> {
|
||||
let mut config = load_config();
|
||||
config.wizard_done = true;
|
||||
save_config(&config);
|
||||
info!("First-run wizard marked as completed");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_system_theme(app: tauri::AppHandle) -> String {
|
||||
use tauri::Theme;
|
||||
@@ -564,6 +586,8 @@ fn main() {
|
||||
get_config,
|
||||
save_vault_path,
|
||||
get_vault_path,
|
||||
get_wizard_state,
|
||||
complete_wizard,
|
||||
pick_vault_folder,
|
||||
get_system_theme,
|
||||
restart_backend,
|
||||
@@ -904,6 +928,7 @@ mod tests {
|
||||
assert!(c.dirs.is_empty());
|
||||
assert_eq!(c.window_width, Some(1200.0));
|
||||
assert_eq!(c.window_height, Some(800.0));
|
||||
assert!(!c.wizard_done);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -919,6 +944,7 @@ mod tests {
|
||||
window_y: Some(200.0),
|
||||
window_width: Some(1400.0),
|
||||
window_height: Some(900.0),
|
||||
wizard_done: true,
|
||||
};
|
||||
let loaded = roundtrip_config(&c);
|
||||
assert_eq!(loaded.vault_path, Some("/test/vault".into()));
|
||||
@@ -927,6 +953,33 @@ mod tests {
|
||||
assert_eq!(loaded.vaults[1].path, "/v/work");
|
||||
assert_eq!(loaded.dirs.len(), 1);
|
||||
assert_eq!(loaded.window_x, Some(100.0));
|
||||
assert!(loaded.wizard_done);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_without_wizard_done_parses() {
|
||||
// Backward compatibility: config files written before `wizard_done`
|
||||
// existed must still load (instead of resetting to defaults).
|
||||
let json = r#"{
|
||||
"vault_path": "/old/vault",
|
||||
"vaults": [{"name": "Old", "path": "/old"}],
|
||||
"dirs": [],
|
||||
"window_x": null,
|
||||
"window_y": null,
|
||||
"window_width": 1000.0,
|
||||
"window_height": 700.0
|
||||
}"#;
|
||||
let c: AppConfig = serde_json::from_str(json).unwrap();
|
||||
assert_eq!(c.vaults.len(), 1);
|
||||
assert_eq!(c.vaults[0].name, "Old");
|
||||
assert!(!c.wizard_done);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wizard_done_roundtrip() {
|
||||
let mut c = AppConfig::default();
|
||||
c.wizard_done = true;
|
||||
assert!(roundtrip_config(&c).wizard_done);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"createUpdaterArtifacts": true,
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
@@ -83,7 +84,7 @@
|
||||
"endpoints": [
|
||||
"https://git.dracodev.net/api/v1/repos/Projets/ObsiGate/releases/latest"
|
||||
],
|
||||
"pubkey": "OBSIGATE_UPDATE_PUBKEY_PLACEHOLDER",
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDcwQjU2MDM4QUVEREY3NApSV1IwMysyS0ExWUxCK1RQMUgrYnplQWlpYWU2SHVYajVIUHhzNEFKLzZ2Z2puZW9pSUQ2RE8rUAo=",
|
||||
"windows": {
|
||||
"installMode": "passive"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
# ObsiGate Desktop — Protocole de tests E2E manuels
|
||||
|
||||
> **Rôle :** valider les 6 scénarios de bout en bout du desktop Tauri (#77) qui ne
|
||||
> peuvent pas être automatisés (interactions OS : installeur, tray, notifications,
|
||||
> association de fichiers, auto-update, désinstallation).
|
||||
> **Statut :** protocole documenté — à exécuter manuellement par un humain.
|
||||
> **Références :** [feature desktop-tauri](./features/desktop-tauri.md) ·
|
||||
> [Roadmap](./ROADMAP.md) · [Build & releases](./DEVELOPMENT_AND_RELEASES.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. Prérequis
|
||||
|
||||
- Un **build release** de l'application :
|
||||
- Windows : `desktop\build-windows.bat` → `ObsiGate_x.y.z_x64.msi` / `-setup.exe`
|
||||
- Linux : `desktop/build-linux.sh` → `obsigate_x.y.z_amd64.deb` / `.AppImage`
|
||||
- Une **machine vierge ou un compte utilisateur propre** (pas d'installation
|
||||
précédente) pour les tests d'installation/désinstallation.
|
||||
- Le **mot de passe admin** affiché au premier lancement (ou `OBSIGATE_ADMIN_PASSWORD`
|
||||
défini avant le lancement).
|
||||
- Accès en écriture aux logs : `%APPDATA%\ObsiGate\logs\backend.log` (Windows) ou
|
||||
`~/.config/obsigate/logs/backend.log` (Linux).
|
||||
|
||||
## 2. Comment remplir ce protocole
|
||||
|
||||
Pour chaque test : noter la **version testée** (`menu Aide → À propos`), le
|
||||
**commit** du build, la **date**, puis cocher `✅ OK` ou `❌ Échec` et joindre les
|
||||
logs/ captures en cas d'échec. Ne cocher un test qu'après avoir observé le
|
||||
**résultat attendu**.
|
||||
|
||||
| Test | Version | Commit | Date | Résultat |
|
||||
|---|---|---|---|---|
|
||||
| T1 — Installation | | | | ⬜ |
|
||||
| T2 — Tray icon | | | | ⬜ |
|
||||
| T3 — Notifications natives | | | | ⬜ |
|
||||
| T4 — Association `.md` | | | | ⬜ |
|
||||
| T5 — Auto-update | | | | ⬜ |
|
||||
| T6 — Désinstallation | | | | ⬜ |
|
||||
|
||||
---
|
||||
|
||||
## T1 — Installation → premier lancement → ouverture d'un fichier
|
||||
|
||||
**Objectif :** l'installeur installe l'app et le premier lancement démarre le
|
||||
backend + affiche l'interface.
|
||||
|
||||
1. Lancer l'installeur (`.msi` ou `.deb`/`.AppImage`).
|
||||
2. Installer dans le chemin par défaut → vérifier la création du raccourci
|
||||
(bureau / menu Démarrer / menu applications).
|
||||
3. Lancer ObsiGate depuis le raccourci.
|
||||
4. Observer l'écran de démarrage (« ObsiGate démarre… ») puis l'interface.
|
||||
5. Au premier lancement, cliquer **« Choisir mon dossier »** dans la bannière du
|
||||
wizard et sélectionner un dossier vault.
|
||||
6. Ouvrir un fichier `.md` depuis l'arborescence.
|
||||
|
||||
**Résultat attendu :**
|
||||
- Aucun terminal / console visible.
|
||||
- Le backend répond (`http://127.0.0.1:17890/api/health` → 200) en ~2 s.
|
||||
- Le fichier s'ouvre dans le viewer.
|
||||
- La bannière du wizard **ne réapparaît pas** au lancement suivant.
|
||||
|
||||
**Échec si :** écran blanc, backend non démarré, wizard qui revient à chaque
|
||||
lancement.
|
||||
|
||||
---
|
||||
|
||||
## T2 — Tray icon → réduire → restaurer
|
||||
|
||||
**Objectif :** le tray fonctionne et contrôle la fenêtre.
|
||||
|
||||
1. Vérifier la présence de l'icône ObsiGate dans la zone de notification.
|
||||
2. Clic **gauche** sur l'icône → la fenêtre se cache.
|
||||
3. Clic **gauche** à nouveau → la fenêtre réapparaît et prend le focus.
|
||||
4. Clic **droit** → menu (Ouvrir ObsiGate / À propos / Quitter).
|
||||
5. Fermer la fenêtre avec le **X** → elle se réduit dans le tray, le backend
|
||||
continue de tourner (l'icône reste).
|
||||
|
||||
**Résultat attendu :** toggle visible/caché immédiat, menu contextuel complet,
|
||||
fermeture par X = réduction (pas d'arrêt du backend).
|
||||
|
||||
---
|
||||
|
||||
## T3 — Notifications natives (fichier modifié → popup OS)
|
||||
|
||||
**Objectif :** les notifications natives remplacent le push web.
|
||||
|
||||
1. Activer les notifications dans les préférences (par vault si proposé).
|
||||
2. Modifier un fichier surveillé (édition externe dans le vault, ou édition
|
||||
in-app avec le watcher actif).
|
||||
3. Observer la notification du système d'exploitation.
|
||||
|
||||
**Résultat attendu :** popup OS affichant le nom du fichier/vault et l'action ;
|
||||
un clic ouvre le fichier concerné. La notification apparaît même si la fenêtre
|
||||
ObsiGate est réduite.
|
||||
|
||||
> Note : sur Windows, vérifier que les notifications ne sont pas bloquées dans
|
||||
> *Paramètres → Système → Notifications*.
|
||||
|
||||
---
|
||||
|
||||
## T4 — Association `.md` → double-clic → ouvre dans ObsiGate
|
||||
|
||||
**Objectif :** l'association de fichiers ouvre l'app.
|
||||
|
||||
1. Vérifier que `.md` est associé à « ObsiGate Markdown » (Windows :
|
||||
*Paramètres → Applications par défaut* ; Linux : `xdg-mime query default text/markdown`).
|
||||
2. **Fermer** complètement ObsiGate (tray → Quitter).
|
||||
3. Double-cliquer sur un fichier `.md` dans l'explorateur / gestionnaire de fichiers.
|
||||
4. Observer le lancement d'ObsiGate et l'ouverture du fichier.
|
||||
|
||||
**Résultat attendu :** ObsiGate démarre et ouvre le fichier (ou le vault
|
||||
contenant le fichier). Un second double-clic alors que l'app tourne **focus la
|
||||
fenêtre existante** (single-instance) au lieu de lancer un doublon.
|
||||
|
||||
---
|
||||
|
||||
## T5 — Auto-update → nouvelle version → installation
|
||||
|
||||
**Objectif :** l'updater détecte et installe une nouvelle version.
|
||||
|
||||
1. S'assurer qu'une **release plus récente** existe sur Gitea (avec les artefacts
|
||||
et le manifeste de mise à jour signé).
|
||||
2. Lancer la version N.
|
||||
3. Déclencher la vérification de mise à jour (menu ou au démarrage selon l'UI).
|
||||
4. Accepter la mise à jour → l'app télécharge, vérifie la signature et installe.
|
||||
5. Relancer → vérifier la version affichée (`À propos`).
|
||||
|
||||
**Résultat attendu :** détection de la version N+1, téléchargement, installation
|
||||
sans intervention manuelle, version mise à jour après redémarrage.
|
||||
|
||||
**Prérequis bloquant :** la `pubkey` de l'updater dans `desktop/tauri.conf.json`
|
||||
ne doit **pas** être le placeholder `OBSIGATE_UPDATE_PUBKEY_PLACEHOLDER`, et la
|
||||
clé privée correspondante doit être fournie au build
|
||||
(`TAURI_SIGNING_PRIVATE_KEY`). Voir §4.
|
||||
|
||||
---
|
||||
|
||||
## T6 — Désinstallation propre
|
||||
|
||||
**Objectif :** la désinstallation ne laisse aucun processus ni résidu gênant.
|
||||
|
||||
1. Fermer ObsiGate (tray → Quitter) pour éviter un processus orphelin.
|
||||
2. Désinstaller via le panneau de configuration (Windows) ou `dpkg -r obsigate`
|
||||
/ supprimer l'AppImage (Linux).
|
||||
3. Vérifier qu'aucun processus `obsigate-desktop` / Python backend ne tourne
|
||||
encore.
|
||||
4. Vérifier les résidus : raccourcis supprimés, entrée « Applications par
|
||||
défaut » retirée.
|
||||
5. (Optionnel) Vérifier le comportement des données utilisateur
|
||||
(`%APPDATA%\ObsiGate` / `~/.config/obsigate`) : conservées ou supprimées selon
|
||||
le choix documenté.
|
||||
|
||||
**Résultat attendu :** désinstallation sans erreur, aucun processus résiduel,
|
||||
aucun raccourci cassé.
|
||||
|
||||
---
|
||||
|
||||
## 3. Emplacements utiles
|
||||
|
||||
| Élément | Windows | Linux |
|
||||
|---|---|---|
|
||||
| Config | `%APPDATA%\ObsiGate\config.json` | `~/.config/obsigate/config.json` |
|
||||
| Logs backend | `%APPDATA%\ObsiGate\logs\backend.log` | `~/.config/obsigate/logs/backend.log` |
|
||||
| Données (index, comptes) | `%APPDATA%\ObsiGate\data\` | `~/.config/obsigate/data\` |
|
||||
|
||||
## 4. Signature de code & auto-update
|
||||
|
||||
- **Signature Windows (optionnelle, hors périmètre de ce protocole) :** sans
|
||||
certificat, SmartScreen affiche un avertissement au premier lancement
|
||||
(*Informations complémentaires → Exécuter quand même*). Le script
|
||||
`desktop/scripts/sign-windows.ps1` signe automatiquement si
|
||||
`OBSIGATE_SIGN_CERT_PFX` est défini ; sinon il est un no-op explicite.
|
||||
Alternatives détaillées dans le [README desktop](../desktop/README.md).
|
||||
- **Signature de l'updater Tauri (gratuite, distincte de la signature Windows) :**
|
||||
générer une paire de clés, renseigner la `pubkey` dans
|
||||
`desktop/tauri.conf.json` et exposer la clé privée au build via
|
||||
`TAURI_SIGNING_PRIVATE_KEY`. La clé privée **ne doit jamais être commitée**.
|
||||
|
||||
```bash
|
||||
cargo tauri signer generate -w ~/.tauri/obsigate.key
|
||||
# → copier la clé publique affichée dans plugins.updater.pubkey
|
||||
```
|
||||
|
||||
## 5. Clôture
|
||||
|
||||
Une fois les 6 tests exécutés et OK, reporter le résultat dans
|
||||
[`docs/features/desktop-tauri.md`](./features/desktop-tauri.md) (section F) et
|
||||
mettre à jour le statut du #77 dans la [Roadmap](./ROADMAP.md).
|
||||
@@ -57,6 +57,90 @@ cargo tauri dev
|
||||
|
||||
---
|
||||
|
||||
## 2bis. Signature des mises à jour (updater Tauri)
|
||||
|
||||
La signature de l'auto-update Tauri est **indépendante** de la signature de code
|
||||
Windows et **gratuite**. Elle garantit qu'une mise à jour téléchargée provient bien
|
||||
de vous. Elle repose sur une paire de clés `minisign` :
|
||||
|
||||
- La **clé publique** est embarquée dans `desktop/tauri.conf.json`
|
||||
(`plugins.updater.pubkey`).
|
||||
- La **clé privée** signe les artefacts au build. Elle ne doit **jamais** être
|
||||
commitée (ignorée par `.gitignore`).
|
||||
|
||||
### A. Générer la paire de clés (une seule fois)
|
||||
|
||||
```bash
|
||||
cd desktop
|
||||
cargo tauri signer generate -w obsigate-updater.key
|
||||
# La clé publique s'affiche et est écrite dans obsigate-updater.key.pub
|
||||
```
|
||||
|
||||
> ⚠️ Conservez la clé privée en lieu sûr (gestionnaire de secrets). Si vous la
|
||||
> perdez, les mises à jour ne pourront plus être signées. Pour la protéger par mot
|
||||
> de passe : ajoutez `-p "<mot de passe>"`.
|
||||
|
||||
Copiez le contenu de `obsigate-updater.key.pub` dans
|
||||
`desktop/tauri.conf.json` → `plugins.updater.pubkey`.
|
||||
|
||||
### B. Build local signé
|
||||
|
||||
```powershell
|
||||
# Windows PowerShell
|
||||
$env:TAURI_SIGNING_PRIVATE_KEY = Get-Content -Raw .\obsigate-updater.key
|
||||
# $env:TAURI_SIGNING_PRIVATE_KEY_PASSWORD = "<mot de passe>" # si la clé en a un
|
||||
cargo tauri build --bundles nsis,msi
|
||||
```
|
||||
|
||||
```bash
|
||||
# Linux / Bash
|
||||
export TAURI_SIGNING_PRIVATE_KEY="$(cat obsigate-updater.key)"
|
||||
# export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="<mot de passe>"
|
||||
cargo tauri build --bundles appimage,deb
|
||||
```
|
||||
|
||||
Le CLI produit des fichiers `.sig` à côté de chaque artefact
|
||||
(`*.exe.sig`, `*.msi.sig`, `*.AppImage.sig`, `*.deb.sig`).
|
||||
|
||||
> Sans clé définie, `createUpdaterArtifacts` est actif et le build échoue : dans
|
||||
> le CI, l'étape désactive automatiquement les artefacts de mise à jour si le
|
||||
> secret est absent.
|
||||
|
||||
### C. Secrets CI (Gitea)
|
||||
|
||||
Dans **Dépôt → Paramètres → Actions → Secrets**, créez :
|
||||
|
||||
| Secret | Valeur |
|
||||
|---|---|
|
||||
| `TAURI_SIGNING_PRIVATE_KEY` | contenu **intégral** du fichier `.key` |
|
||||
| `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` | mot de passe de la clé (vide si aucun) |
|
||||
|
||||
Le workflow `.gitea/workflows/desktop-build.yml` les expose aux étapes de build ;
|
||||
les fichiers `.sig` sont uploadés comme artefacts.
|
||||
|
||||
### D. Manifeste de mise à jour (`latest.json`)
|
||||
|
||||
Le CLI Tauri génère les `.sig` mais **pas** le manifeste JSON consommé par
|
||||
l'updater. L'endpoint configuré
|
||||
(`.../releases/latest`) doit servir un document de la forme :
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "2.3.0",
|
||||
"notes": "…",
|
||||
"pub_date": "2026-09-12T00:00:00Z",
|
||||
"platforms": {
|
||||
"windows-x86_64": { "signature": "<contenu .msi.sig>", "url": "<URL du .msi>" },
|
||||
"linux-x86_64": { "signature": "<contenu .AppImage.sig>", "url": "<URL .AppImage>" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Sans ce manifeste, l'updater ne détecte aucune mise à jour (la signature, elle,
|
||||
est déjà opérationnelle).
|
||||
|
||||
---
|
||||
|
||||
## 3. Commit, Push & Tagging Git
|
||||
|
||||
Une fois les modifications testées et validées :
|
||||
|
||||
+6
-6
@@ -33,12 +33,12 @@
|
||||
### 77. Application Desktop native — Tauri (Windows / Linux / macOS)
|
||||
|
||||
- **Effort :** 8-12 jours | **Impact :** 🟡 | **Framework :** Tauri v2 (Rust + Webview)
|
||||
- **Statut :** livré (A→F) — projet Tauri, backend Python embarqué, fonctionnalités natives, build CI, UX, 16 tests Rust. Détail complet : [features/desktop-tauri.md](./features/desktop-tauri.md)
|
||||
- **Statut :** livré (A→F) — projet Tauri, backend Python embarqué, fonctionnalités natives, jumplist vaults, bannière 1er lancement, build CI, UX, 24 tests Rust. Détail complet : [features/desktop-tauri.md](./features/desktop-tauri.md)
|
||||
- **Reste à faire :**
|
||||
- [ ] Signature de code Windows (optionnel mais recommandé)
|
||||
- [ ] Wizard « Choisissez votre vault » au 1er lancement (optionnel)
|
||||
- [ ] Jumplist vaults récents dans le menu Démarrer (optionnel)
|
||||
- [ ] 6 tests E2E **manuels** : installation, tray icon, notifications natives, association `.md`, auto-update, désinstallation
|
||||
- [x] **Signature de l'updater Tauri** (gratuit) : paire de clés générée, `pubkey` renseignée, `createUpdaterArtifacts` activé, secrets CI câblés
|
||||
- [ ] Générer le manifeste `latest.json` pour que l'updater détecte les mises à jour
|
||||
- [ ] **Signature de code Windows** : non retenue (pas de certificat) — alternatives : livrer non signé, SignPath.io (OSS gratuit), Certum OSS, Azure Trusted Signing, certificat EV
|
||||
- [ ] Exécuter les 6 tests E2E **manuels** — protocole documenté : [DESKTOP_E2E_CHECKLIST.md](./DESKTOP_E2E_CHECKLIST.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
| Priorité | Items | Effort total estimé |
|
||||
|---|---|---|
|
||||
| ✅ Complété | #1 → #59, #61–72, #74–76, #78–80 | ~103 jours réalisés |
|
||||
| 🔵 P2 restant | #77 Desktop : signature code (optionnel), wizard 1er lancement (optionnel), 6 tests E2E **manuels** | ~1-2 jours |
|
||||
| 🔵 P2 restant | #77 Desktop : manifeste `latest.json`, signature de code (non retenue), 6 tests E2E **manuels** ([protocole](./DESKTOP_E2E_CHECKLIST.md)) | ~0,5-1 jour |
|
||||
| ⚪ P4 restant | #73 Sync (6-8j) | 6-8 jours |
|
||||
| **Total restant** | **2 items + finitions** | **~7-10 jours** |
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# #77 — Application Desktop native — Tauri (Windows / Linux / macOS)
|
||||
|
||||
> **Statut :** 🔵 En cours — livré : A/B/C/D/E/F ; reste signature de code (optionnel), wizard 1er lancement (optionnel), 6 tests E2E manuels
|
||||
> **Statut :** 🔵 En cours — livré : A/B/C/D/E/F ; reste la signature de code Windows (optionnelle, non retenue) et l'exécution des 6 tests E2E **manuels** ([protocole](../DESKTOP_E2E_CHECKLIST.md))
|
||||
> **Effort :** 8-12 jours | **Impact :** 🟡 | **Framework :** Tauri v2 (Rust + Webview)
|
||||
> **Références :** [Roadmap](../ROADMAP.md) · [Guide de build & releases](../DEVELOPMENT_AND_RELEASES.md)
|
||||
|
||||
@@ -57,7 +57,8 @@
|
||||
- [x] **Build Windows local** : `cargo build --release` vérifié (rustc 1.94.1) — `cargo tauri build --bundles msi` prêt
|
||||
- [x] **Build Linux local** : workflow CI couvre `.deb`, `.rpm`, `.AppImage`
|
||||
- [x] **Auto-update** : `tauri-plugin-updater` configuré → vérifie `https://git.dracodev.net/api/v1/repos/Projets/ObsiGate/releases/latest`
|
||||
- [ ] **Signature de code** : configurer le certificat (optionnel mais recommandé pour Windows)
|
||||
- [x] **Signature de l'updater Tauri** (gratuite, ≠ signature Windows) : paire de clés `minisign` générée, clé publique dans `plugins.updater.pubkey`, `bundle.createUpdaterArtifacts: true`, secrets Gitea `TAURI_SIGNING_PRIVATE_KEY` / `_PASSWORD` exposés au CI (build non signé en repli si le secret est absent). Reste : générer le manifeste `latest.json` pour que l'updater détecte les mises à jour ([guide](../DEVELOPMENT_AND_RELEASES.md#2bis-signature-des-mises-à-jour-updater-tauri))
|
||||
- [ ] **Signature de code Windows** : non retenue (pas de certificat) — alternatives : livrer non signé, SignPath.io (OSS gratuit), Certum Open Source, Azure Trusted Signing, certificat EV
|
||||
- [x] **Page de release** : README desktop existe (`desktop/README.md`)
|
||||
|
||||
## E. Expérience utilisateur (1 jour) — ✅ COMPLÉTÉ
|
||||
@@ -65,19 +66,18 @@
|
||||
- [x] Gestion des erreurs : backend crash → `showBackendCrashBanner()` appelé par le monitor loop toutes les 5s
|
||||
- [x] Sauvegarde des préférences desktop : position/taille fenêtre sauvées dans `%APPDATA%/ObsiGate/config.json` au close + restauration au startup
|
||||
- [x] Première expérience : config par défaut auto-créée au premier lancement (vault `~/voute_obsidian`, dir `~USERPROFILE`)
|
||||
- [ ] Wizard interactif « Choisissez votre vault » au premier lancement (optionnel — config auto suffisante)
|
||||
- [ ] Jumplist vaults récents dans le menu Démarrer (optionnel)
|
||||
- [x] Bannière de premier lancement « Choisissez votre vault » (`frontend/js/desktop.js`) — non bloquante ; l'état est persisté côté Rust (`wizard_done` dans `config.json`) pour ne pas réapparaître après un choix ou un clic « Plus tard », même si le `localStorage` de la webview est vidé
|
||||
- [x] Jumplist vaults récents dans le menu Démarrer (`desktop/src/jumplist.rs`, Windows)
|
||||
|
||||
## F. Tests (1 jour) — ✅ COMPLÉTÉ
|
||||
- [x] 16 tests Rust unitaires : config roundtrip, JSON parsing (empty/partial/corrupted), vault dedup, dir remove, backend URL, paths, branding, edge cases
|
||||
- [x] 24 tests Rust unitaires : config roundtrip, JSON parsing (empty/partial/corrupted/legacy), `wizard_done`, vault dedup, dir remove, backend URL, paths, branding, jumplist args, edge cases
|
||||
- [x] Build debug + release vérifié (rustc 1.94.1, tauri-cli 2.11.4)
|
||||
- [x] CI desktop workflow existant (desktop-build.yml)
|
||||
- [ ] Test E2E : installation → premier lancement → wizard vault → ouverture fichier (manuel)
|
||||
- [ ] Test E2E : tray icon → réduire → restaurer (manuel)
|
||||
- [ ] Test E2E : notifications natives → fichier modifié → popup OS (manuel)
|
||||
- [ ] Test E2E : association `.md` → double-clic → ouvre dans ObsiGate (manuel)
|
||||
- [ ] Test E2E : auto-update → nouvelle version → install (manuel)
|
||||
- [ ] Test E2E : désinstallation propre (manuel)
|
||||
- [ ] **6 tests E2E manuels** — protocole détaillé (prérequis, étapes, résultat
|
||||
attendu) dans [docs/DESKTOP_E2E_CHECKLIST.md](../DESKTOP_E2E_CHECKLIST.md) :
|
||||
installation → 1er lancement → wizard → ouverture fichier ; tray ; notifications
|
||||
natives ; association `.md` ; auto-update ; désinstallation propre. À exécuter
|
||||
et cocher par un humain sur un build release.
|
||||
|
||||
- **Prérequis techniques :**
|
||||
- Rust ≥ 1.75 (stable) — installé via `rustup`
|
||||
|
||||
+11
-4
@@ -142,8 +142,11 @@ export function shouldShowWizard(opts) {
|
||||
return isDesktop && !dismissed && !hasVaultPath;
|
||||
}
|
||||
|
||||
export function dismissWizard() {
|
||||
export async function dismissWizard() {
|
||||
try { localStorage.setItem(WIZARD_DISMISSED_KEY, '1'); } catch (e) { /* ignore */ }
|
||||
// Persist on the Rust side too, so clearing the webview's localStorage does
|
||||
// not resurrect the wizard on the next launch.
|
||||
await invoke('complete_wizard');
|
||||
}
|
||||
|
||||
export function getWizardDismissedKey() { return WIZARD_DISMISSED_KEY; }
|
||||
@@ -173,9 +176,13 @@ export async function pickAndAddVault() {
|
||||
// the user's Obsidian vault folder. Non-blocking; the default vault created
|
||||
// by the Rust shell remains available if the user dismisses it.
|
||||
|
||||
export function initDesktopWizard() {
|
||||
export async function initDesktopWizard() {
|
||||
if (typeof window === 'undefined' || typeof document === 'undefined') return;
|
||||
if (!shouldShowWizard({})) return;
|
||||
|
||||
// The authoritative "already done" signal lives in the desktop config
|
||||
// (survives localStorage clears). Defaults to false when not on desktop.
|
||||
const wizardDone = (await invoke('get_wizard_state')) === true;
|
||||
if (!shouldShowWizard({ hasVaultPath: wizardDone })) return;
|
||||
|
||||
// Defer so the app shell is fully rendered before injecting the banner.
|
||||
setTimeout(function () {
|
||||
@@ -236,7 +243,7 @@ export async function initDesktopIntegration() {
|
||||
} catch (e) { /* ignore */ }
|
||||
|
||||
// First-run vault wizard.
|
||||
initDesktopWizard();
|
||||
await initDesktopWizard();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -126,11 +126,30 @@ ok("shouldShowWizard: web → false",
|
||||
desktop.shouldShowWizard({ isDesktop: false, dismissed: false, hasVaultPath: false }) === false);
|
||||
|
||||
// dismissWizard persists the flag
|
||||
desktop.dismissWizard();
|
||||
localStorage.clear();
|
||||
await desktop.dismissWizard();
|
||||
ok("dismissWizard persists flag", localStorage.getItem(desktop.getWizardDismissedKey()) === "1");
|
||||
ok("shouldShowWizard reflects dismissed flag via localStorage",
|
||||
desktop.shouldShowWizard({ isDesktop: true, hasVaultPath: false }) === false);
|
||||
|
||||
// dismissWizard also persists on the Rust side (survives localStorage clear)
|
||||
{
|
||||
let called = null;
|
||||
window.__TAURI__ = { core: { invoke: async (cmd) => { called = cmd; return undefined; } } };
|
||||
localStorage.clear();
|
||||
await desktop.dismissWizard();
|
||||
ok("dismissWizard invokes complete_wizard", called === "complete_wizard");
|
||||
}
|
||||
|
||||
// initDesktopWizard must NOT inject the banner when Rust reports wizard done
|
||||
{
|
||||
localStorage.clear();
|
||||
window.__TAURI__ = { core: { invoke: async (cmd) => (cmd === "get_wizard_state" ? true : undefined) } };
|
||||
await desktop.initDesktopWizard();
|
||||
ok("initDesktopWizard skips banner when wizard_done is true",
|
||||
document.getElementById("obsigate-desktop-wizard") === null);
|
||||
}
|
||||
|
||||
// ── crash banner ────────────────────────────────────────────────────────────
|
||||
|
||||
console.log("crash banner");
|
||||
|
||||
Reference in New Issue
Block a user