diff --git a/.gitea/workflows/desktop-build.yml b/.gitea/workflows/desktop-build.yml
index 828e4e7..41bc784 100644
--- a/.gitea/workflows/desktop-build.yml
+++ b/.gitea/workflows/desktop-build.yml
@@ -29,6 +29,12 @@ jobs:
cd desktop/python-embed
./python.exe -m pip install --no-cache-dir -r ../../backend/requirements.txt
+ - name: Stage backend + frontend for bundle
+ working-directory: desktop
+ run: |
+ xcopy /E /I /Q /Y "..\backend" "backend"
+ xcopy /E /I /Q /Y "..\frontend" "frontend"
+
- name: Build MSI
working-directory: desktop
run: cargo tauri build --bundles msi
@@ -68,6 +74,12 @@ jobs:
source desktop/python-embed/venv/bin/activate
pip install -r backend/requirements.txt
+ - name: Stage backend + frontend for bundle
+ working-directory: desktop
+ run: |
+ cp -r ../backend backend
+ cp -r ../frontend frontend
+
- name: Build AppImage
working-directory: desktop
run: cargo tauri build --bundles appimage
diff --git a/README.fr.md b/README.fr.md
index dfa8370..ee58737 100644
--- a/README.fr.md
+++ b/README.fr.md
@@ -1,99 +1,810 @@
-# ObsiGate — Gestionnaire de connaissances personnel
+# ObsiGate
-**ObsiGate** est une application web moderne de gestion de notes Markdown, conçue pour
-remplacer des outils comme Obsidian avec une expérience auto-hébergée complète.
+> **Version française** — ce document est le miroir synchronisé de [README.md](README.md) (référence complète). Dernière synchronisation : juin 2026.
-## ✨ Fonctionnalités principales
+**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.
-- **📝 Éditeur Markdown** avancé avec aperçu en temps réel
-- **🤖 IA intégrée** : DeepSeek, OpenRouter, Gemini — complétion, réécriture, traduction
-- **🔍 Recherche full-text** avec TF-IDF et filtres avancés
-- **📊 Dashboard** avec statistiques, fichiers récents, et contenu partagé
-- **🏷️ Tags** : organisation et filtrage par tags
-- **🗺️ Vue Graphe** : visualisation des liens entre fichiers
-- **🔄 Synchronisation** en temps réel via SSE (Server-Sent Events)
-- **🔗 Webhooks** : notifications automatiques vers des services externes
-- **💾 Sauvegardes** automatiques et restauration
-- **🎨 15 thèmes** avec modes clair et sombre
-- **🌐 Internationalisation** : anglais et français (sélecteur de langue intégré)
-- **📱 PWA** : installation en tant qu'application native
-- **🔒 Authentification** JWT avec tokens refresh
-
-## 🚀 Installation rapide
-
-### Docker (recommandé)
-```bash
-git clone https://git.dracodev.net/Projets/ObsiGate.git
-cd ObsiGate
-cp .env.example .env
-# Configurer DEEPSEEK_API_KEY dans .env
-docker compose up -d --build
-```
-
-### Accès
-- **Interface web** : http://localhost:2020
-- **Utilisateur par défaut** : admin / admin (à changer immédiatement)
-
-## 📋 Prérequis
-
-- Docker & Docker Compose
-- Clé API DeepSeek, OpenRouter, ou Gemini (optionnelle, pour l'IA)
-- Python 3.11+ (pour le développement)
-
-## 🛠️ Développement
-
-```bash
-pip install -r requirements.txt
-python backend/main.py
-# Dans un autre terminal :
-cd frontend && python -m http.server 8080
-```
-
-## 📁 Structure du projet
+[]()
+[](https://opensource.org/licenses/MIT)
+[](https://www.docker.com/)
+[](https://www.python.org/)
+[](https://git.dracodev.net/Projets/ObsiGate/actions)
```
-ObsiGate/
- backend/ # API FastAPI (Python)
- frontend/ # Interface utilisateur (HTML/CSS/JS vanilla)
- locales/ # Traductions (en.json, fr.json)
- docker/ # Configuration Docker
- docs/ # Documentation et roadmap
+┌─────────────────────────────────────────────────────────┐
+│ [🔍 Recherche...] [☀/🌙 Thème] ObsiGate │
+├──────────────┬──────────────────────────────────────────┤
+│ SIDEBAR │ CONTENT AREA │
+│ ▼ Recettes │ 📄 Titre du fichier │
+│ 📁 Soupes │ Tags: #recette #rapide │
+│ 📄 Pizza │ [Contenu Markdown rendu] │
+│ ▼ IT │ │
+│ 📁 Docker │ │
+│ Tags Cloud │ │
+└──────────────┴──────────────────────────────────────────┘
```
-## 🌍 Internationalisation
-
-ObsiGate supporte l'anglais et le français. Le sélecteur de langue se trouve dans
-**Paramètres > Langue**. La traduction couvre :
-
-- ✅ Interface utilisateur complète
-- ✅ Guide d'utilisation (18 sections)
-- ✅ Messages système et toasts
-- ✅ Descriptions des thèmes et commandes
-
-## 📖 Documentation
-
-- [README.md](README.md) — Documentation complète en anglais
-- [docs/ROADMAP.md](docs/ROADMAP.md) — Feuille de route du projet
-- [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) — Guide de contribution
-- [docs/TROUBLESHOOTING.md](TROUBLESHOOTING.md) — Dépannage
-
-## 🔧 Configuration
-
-Variables d'environnement principales (`.env`) :
-
-| Variable | Description |
-|----------|-------------|
-| `DEEPSEEK_API_KEY` | Clé API pour l'IA DeepSeek |
-| `OPENROUTER_API_KEY` | Clé API pour OpenRouter |
-| `GEMINI_API_KEY` | Clé API pour Gemini |
-| `ADMIN_PASSWORD` | Mot de passe administrateur |
-| `JWT_SECRET` | Secret pour les tokens JWT |
-| `WEBHOOK_SECRET` | Secret pour la signature des webhooks |
-
-## 📝 Licence
-
-Ce projet est sous licence propriétaire. Voir le fichier [LICENSE](LICENSE) pour plus de détails.
-
---
-**ObsiGate** — Votre savoir, votre serveur. 🏰
+## 📋 Table des matières
+
+- [Fonctionnalités](#fonctionnalites)
+- [Prérequis](#prerequis)
+- [Installation rapide](#installation-rapide)
+- [Configuration détaillée](#configuration-detaillee)
+- [Variables d'environnement](#variables-denvironnement)
+- [🔒 Authentification](#authentification)
+- [Ajouter une nouvelle vault](#ajouter-une-nouvelle-vault)
+- [Build & déploiement avec build.sh](#build-deploiement-avec-buildsh)
+- [Rendu d'images Obsidian](#rendu-dimages-obsidian)
+- [Desktop (Tauri) — Application native](#desktop-tauri-application-native)
+- [Utilisation](#utilisation)
+- [API](#api)
+- [Recherche avancée](#recherche-avancee)
+- [Dépannage](#depannage)
+- [Performance](#performance)
+- [Sécurité](#securite)
+- [Stack technique](#stack-technique)
+- [Architecture](#architecture)
+- [Développement](#developpement)
+- [Licence](#licence)
+- [Changelog](#changelog)
+
+---
+
+## ✨ Fonctionnalités
+
+- **🤖 AI Editor intégré** — Éditeur CodeMirror 6 avec toolbar IA : amélioration, correction, traduction, génération, réécriture personnalisée, toolbox (liste, tableau, frontmatter, canvas) — multi-provider DeepSeek/OpenRouter/Gemini
+- **🗺️ Vue graphe interactive** — Canvas force-directed avec Barnes-Hut O(n log n), filtres (tag, type), profondeur, mode focus, historique de navigation ←→↑, export PNG, aperçu au survol (Ctrl+click)
+- **🗂️ Multi-vault** : Visualisez plusieurs vaults Obsidian simultanément
+- **🌳 Navigation arborescente** : Parcourez vos dossiers et fichiers dans la sidebar
+- **🔍 Recherche avancée** : Moteur TF-IDF avec stemming français, normalisation des accents, snippets surlignés, facettes, pagination et tri
+- **💡 Autocomplétion intelligente** : Suggestions de fichiers, tags et historique avec navigation clavier
+- **🧩 Syntaxe de requête** : Opérateurs `tag:`, `#`, `vault:`, `title:`, `path:`, `ext:` avec chips visuels
+- **📜 Historique de recherche** : Persisté en localStorage (max 50 entrées, LIFO, dédupliqué)
+- **🏷️ Tag cloud** : Filtrage par tags extraits des frontmatters YAML
+- **🔗 Wikilinks** : Les `[[liens internes]]` Obsidian sont cliquables
+- **🖼️ Images Obsidian** : Support complet des syntaxes d'images Obsidian avec résolution intelligente
+- **🎨 Syntax highlight** : Coloration syntaxique des blocs de code
+- **🌓 Thème clair/sombre** : Toggle persisté en localStorage
+- **📡 Synchronisation temps réel** : Surveillance automatique des fichiers via watchdog avec mise à jour incrémentale de l'index
+- **📡 Server-Sent Events** : Notifications SSE pour les changements d'index avec reconnexion automatique
+- **➕ Gestion dynamique des vaults** : Ajout/suppression de vaults via API sans redémarrage
+- **🖥️ Application desktop native** : Tauri (Rust) + backend Python embarqué, sans Docker ni navigateur
+- **🐳 Docker multi-platform** : linux/amd64, linux/arm64, linux/arm/v7, linux/386
+- **🔒 Authentification** : JWT + Argon2id, sessions persistantes, contrôle d'accès par vault
+- **🛡️ Sécurité** : Rate limiting, audit log, backup automatique, redaction de secrets, headers CSP, protection path traversal, utilisateur non-root
+- **⚡ Performance** : Compression GZip, Cache-Control immutable, index inversé incrémental, search sans I/O disque
+- **❤️ Healthcheck** : Endpoint `/api/health` intégré pour Docker et monitoring
+
+---
+
+## 🚀 Prérequis
+
+### Système requis
+- **Docker** >= 20.10
+- **docker-compose** >= 2.0
+- **Espace disque** : ~200MB pour l'image Docker
+
+### Systèmes supportés
+- Linux (Ubuntu, Debian, CentOS, etc.)
+- macOS (Intel et Apple Silicon)
+- Windows (avec Docker Desktop)
+- NAS compatibles Docker (Synology, QNAP, etc.)
+
+---
+
+## ⚡ Installation rapide
+
+### 1. Cloner le dépôt
+
+```bash
+git clone https://git.dracodev.net/Projets/ObsiGate.git
+cd ObsiGate
+```
+
+### 2. Configurer vos vaults et vos secrets
+
+Éditez `docker-compose.yml` pour ajouter vos vaults Obsidian :
+
+```yaml
+volumes:
+ - /chemin/absolu/vers/votre/vault:/vaults/NomDeVotreVault:ro
+```
+
+> **Important** : Le chemin doit être absolu et le volume en lecture seule (`:ro`)
+
+Créez votre fichier `.env` pour l'authentification et les secrets :
+
+```bash
+cp .env.example .env
+# Éditez .env pour configurer vos mots de passe et options
+```
+
+> **Ne committez jamais `.env` !** Il est dans `.gitignore`. Utilisez `.env.example` comme référence.
+
+### 3. Lancer l'application
+
+```bash
+chmod +x build.sh # une seule fois
+./build.sh # build + déploiement en une commande
+```
+
+> Options utiles : `./build.sh --help`, `./build.sh --cache` (rebuild rapide), `./build.sh --build-only` (construire sans démarrer).
+
+### 4. Accéder à l'interface
+
+Ouvrez votre navigateur sur : **http://localhost:2020**
+
+---
+
+## ⚙️ Configuration détaillée
+
+### Étape 1 : Préparation des vaults
+
+1. **Localisez vos vaults Obsidian** sur votre système
+2. **Notez les chemins absolus** vers chaque dossier
+3. **Vérifiez les permissions** : Docker doit pouvoir lire ces dossiers
+
+### Étape 2 : Configuration docker-compose.yml
+
+```yaml
+services:
+ obsigate:
+ build:
+ context: .
+ image: obsigate:latest
+ container_name: obsigate
+ restart: unless-stopped
+ ports:
+ - "2020:8080" # Port local 2020 → Port conteneur 8080
+ volumes:
+ - /home/user/Documents/Obsidian-Recettes:/vaults/Recettes:ro
+ - /home/user/Documents/Obsidian-IT:/vaults/IT:ro
+ - ./data:/app/data # Persistance des données d'auth
+ environment:
+ - VAULT_1_NAME=Recettes
+ - VAULT_1_PATH=/vaults/Recettes
+ - VAULT_2_NAME=IT
+ - VAULT_2_PATH=/vaults/IT
+ - OBSIGATE_AUTH_ENABLED=true
+ - OBSIGATE_ADMIN_USER=admin
+ env_file:
+ - .env # Contient OBSIGATE_ADMIN_PASSWORD et autres secrets
+```
+
+### Étape 3 : Build & déploiement
+
+```bash
+chmod +x build.sh
+./build.sh
+```
+
+**Alternative manuelle :**
+
+```bash
+docker compose build --no-cache
+docker compose up -d
+```
+
+> **Compatibilité Docker** : l'image utilise une variante minimale d'`uvicorn` et `fastapi 0.110.3` afin d'éviter les dépendances optionnelles natives (`watchfiles`, `uvloop`, …) qui peuvent échouer au build sur Alpine, ARM ou i386.
+
+---
+
+## 🌍 Variables d'environnement
+
+Les vaults sont configurées par paires `VAULT_N_NAME` / `VAULT_N_PATH` (N = 1, 2, 3…) :
+
+| Variable | Description | Exemple |
+|----------|-------------|---------|
+| `VAULT_1_NAME` | Nom affiché de la vault | `Recettes` |
+| `VAULT_1_PATH` | Chemin dans le conteneur | `/vaults/Obsidian-RECETTES` |
+| `VAULT_1_ATTACHMENTS_PATH` | Dossier d'attachements (optionnel) | `06_Boite_a_Outils/6.2_Attachments` |
+| `VAULT_1_SCAN_ATTACHMENTS` | Scan d'images au démarrage (défaut : true) | `true` |
+
+**Règles de nommage :** lettres, chiffres et tirets uniquement ; pas d'espaces ; le nom doit correspondre au chemin dans le conteneur.
+
+---
+
+## 🔒 Authentification
+
+> **Désactivée par défaut** — Compatible avec toutes les installations existantes.
+
+Système optionnel basé sur **JWT + Argon2id** avec contrôle d'accès par vault.
+
+### Activer l'authentification
+
+1. `cp .env.example .env`
+2. Éditez `.env` :
+ ```bash
+ OBSIGATE_AUTH_ENABLED=true
+ OBSIGATE_ADMIN_USER=admin
+ OBSIGATE_ADMIN_PASSWORD=votre_mot_de_passe # Laissez vide = auto-généré (voir logs)
+ # OBSIGATE_SECURE_COOKIES=false # true si derrière HTTPS
+ ```
+3. Dans `docker-compose.yml` : `env_file: - .env`
+
+> **Ne mettez jamais de mot de passe dans `docker-compose.yml` !** Utilisez toujours `.env`.
+
+### Premier démarrage
+
+Si aucun utilisateur n'existe, ObsiGate crée automatiquement un compte admin et **affiche le mot de passe une seule fois dans les logs** :
+
+```bash
+docker-compose logs obsigate | grep -A4 "PREMIER"
+```
+
+### Gestion des utilisateurs via CLI
+
+```bash
+# Créer un utilisateur
+docker exec obsigate python backend/create_admin.py create alice MonMotDePasse --role user --vaults Recettes IT
+
+# Créer un admin avec accès total
+docker exec obsigate python backend/create_admin.py create bob SecretPass --role admin --vaults "*"
+
+# Lister / supprimer
+docker exec obsigate python backend/create_admin.py list
+docker exec obsigate python backend/create_admin.py delete alice
+```
+
+### Interface d'administration
+
+Un compte **admin** connecté voit une icône 🛡️ dans le header : liste, création/édition/suppression d'utilisateurs, assignation des vaults, activation/désactivation de comptes.
+
+### Contrôle d'accès par vault
+
+| Valeur vaults | Accès |
+|---------------|-------|
+| `["*"]` | Toutes les vaults (y compris futures) — défaut admin |
+| `["Recettes", "IT"]` | Uniquement ces vaults |
+| `[]` | Aucun accès |
+
+### Variables d'environnement d'auth
+
+| Variable | Description | Défaut |
+|----------|-------------|--------|
+| `OBSIGATE_AUTH_ENABLED` | Activer l'authentification | `false` |
+| `OBSIGATE_ADMIN_USER` | Nom de l'admin auto-créé | `admin` |
+| `OBSIGATE_ADMIN_PASSWORD` | Mot de passe admin (vide = auto-généré) | *(auto)* |
+| `OBSIGATE_SECURE_COOKIES` | Cookie `Secure` (HTTPS uniquement) | `false` |
+| `OBSIGATE_ACCESS_TOKEN_TTL` | Durée de vie token JWT (secondes) | `3600` |
+| `OBSIGATE_REFRESH_TOKEN_TTL` | Durée de vie refresh token (secondes) | `2592000` |
+| `OBSIGATE_LOGIN_MAX_ATTEMPTS` | Tentatives de login max par IP | `10` |
+| `OBSIGATE_LOGIN_WINDOW_SECONDS` | Fenêtre de rate limiting (secondes) | `900` |
+
+### Volume pour la persistance
+
+Les données d'auth (`users.json`, `secret.key`) sont stockées dans `/app/data` :
+
+```yaml
+volumes:
+ - ./data:/app/data # Persistance des utilisateurs et clé JWT
+```
+
+---
+
+## ➕ Ajouter une nouvelle vault
+
+### Méthode 1 : Édition directe
+
+1. `docker-compose down`
+2. Ajoutez le volume : `- /nouveau/chemin/vault:/vaults/NouvelleVault:ro`
+3. Ajoutez les variables : `VAULT_4_NAME=NouvelleVault` + `VAULT_4_PATH=/vaults/NouvelleVault`
+4. Redémarrez : `./build.sh`
+
+### Méthode 2 : Hot-reload (recommandé)
+
+1. Ajoutez volume + variables comme ci-dessus
+2. `./build.sh`
+3. `curl http://localhost:2020/api/index/reload`
+
+### Méthode 3 : API dynamique (sans redémarrage)
+
+```bash
+curl -X POST http://localhost:2020/api/vaults/add \
+ -H "Content-Type: application/json" \
+ -d '{"name": "NouvelleVault", "path": "/vaults/NouvelleVault"}'
+
+curl -X DELETE http://localhost:2020/api/vaults/NouvelleVault
+```
+
+---
+
+## 🔨 Build & déploiement avec build.sh
+
+### Utilisation de base
+
+```bash
+chmod +x build.sh # une seule fois
+./build.sh # build from scratch + démarrage
+```
+
+### Options disponibles
+
+| Option | Description |
+|--------|-------------|
+| `--help`, `-h` | Affiche l'aide complète |
+| `--build-only` | Construit l'image sans démarrer le conteneur |
+| `--no-cache` | Rebuild complet sans cache Docker **(défaut)** |
+| `--cache` | Utilise le cache Docker (plus rapide si peu de changements) |
+| `--progress=plain` | Sortie verbeuse (recommandé pour le debug) |
+| `--progress=tty` | Sortie interactive avec barres de progression |
+
+### Ce que fait le script
+
+1. **Vérifie** Docker et Docker Compose (versions)
+2. **Valide** `docker-compose.yml` (présence + syntaxe)
+3. **Vérifie chaque volume** monté (avertit si la source n'existe pas)
+4. **Construit** l'image Docker (multi-stage, ~180MB)
+5. **Démarre** le conteneur (`docker compose up -d`)
+6. **Affiche** statut + logs en temps réel
+
+### Arrêter / redémarrer
+
+```bash
+docker compose down # Arrêter
+docker compose up -d # Redémarrer sans rebuild
+docker compose logs -f # Voir les logs
+```
+
+---
+
+## 🖼️ Rendu d'images Obsidian
+
+ObsiGate supporte **toutes les syntaxes d'images Obsidian** avec résolution intelligente multi-stratégies.
+
+### Syntaxes supportées
+
+1. **Markdown standard avec attributs HTML** : `[
](https://example.com)`
+2. **Wiki-link embed chemin complet** : `![[06_Boite_a_Outils/6.2_Attachments/image.svg]]`
+3. **Wiki-link embed nom de fichier** : `![[image.svg]]`
+4. **Markdown standard** : ``
+
+### Résolution intelligente (7 stratégies, par priorité)
+
+1. Chemin absolu
+2. Dossier d'attachements configuré (`VAULT_N_ATTACHMENTS_PATH`)
+3. Index de démarrage (match unique)
+4. Même répertoire que le fichier markdown
+5. Racine du vault
+6. Index de démarrage (match le plus proche)
+7. Fallback : placeholder stylisé `[image not found: filename.ext]`
+
+### Configuration
+
+```yaml
+environment:
+ - VAULT_1_NAME=MonVault
+ - VAULT_1_PATH=/vaults/MonVault
+ - VAULT_1_ATTACHMENTS_PATH=Assets/Images # Chemin relatif
+ - VAULT_1_SCAN_ATTACHMENTS=true # Activer le scan (défaut)
+```
+
+### Rescan manuel
+
+```bash
+curl -X POST http://localhost:2020/api/attachments/rescan/MonVault
+```
+
+---
+
+## 🖥️ Desktop (Tauri) — Application native
+
+ObsiGate Desktop est une application native construite avec [Tauri](https://tauri.app/) (Rust + webview système). Elle embarque le backend Python et le frontend dans un exécutable standalone — zéro Docker, zéro ligne de commande.
+
+> 🚧 **Version 2.0.0 — binaires en cours de stabilisation.** Pour l'instant, le build depuis les sources est recommandé.
+
+### Fonctionnalités desktop natives
+
+| Fonctionnalité | Web | Desktop |
+|---|---|---|
+| Accès fichiers local | Via upload | Natif (sélecteur dossier) |
+| Thème système | Manuel | Auto (suit OS dark/light) |
+| Notifications | Service Worker | Natif OS |
+| Associations `.md` | ❌ | ✅ « Ouvrir avec ObsiGate » |
+| Tray icon | ❌ | ✅ Barre des tâches |
+| Auto-update | ❌ | ✅ Vérifie les releases Gitea |
+| Mode hors-ligne | Limité | Complet (backend local) |
+
+### Téléchargement (binaires pré-buildés)
+
+Les releases sont publiées sur [Gitea](https://git.dracodev.net/Projets/ObsiGate/releases) :
+
+| Plateforme | Format |
+|---|---|
+| **Linux** | `.deb` + `.AppImage` |
+| **Windows** | `.msi` + `.exe` (NSIS) |
+
+```bash
+# Linux — .deb (Debian/Ubuntu/Deepin)
+sudo dpkg -i obsigate_2.0.0_amd64.deb
+# Linux — .AppImage (toute distrib)
+chmod +x ObsiGate_2.0.0_amd64.AppImage && ./ObsiGate_2.0.0_amd64.AppImage
+```
+
+```cmd
+REM Windows : double-cliquer sur ObsiGate_2.0.0_x64.msi (ou le setup NSIS)
+```
+
+### Démarrage
+
+1. **Lancez l'application** depuis le menu ou la ligne de commande
+2. Le backend Python démarre automatiquement sur `127.0.0.1:17890` (splash « Démarrage… » pendant le boot)
+3. La fenêtre s'ouvre et charge l'interface ObsiGate
+4. **Premier lancement** : sélectionnez le dossier de vos vaults Obsidian via le sélecteur natif
+5. Pour fermer : icône tray → Quitter (arrêt propre du backend)
+
+### Build depuis les sources
+
+Guide détaillé : [desktop/README.md](./desktop/README.md).
+
+#### Prérequis communs
+
+| Outil | Version | Installation |
+|---|---|---|
+| Rust (cargo) | ≥ 1.75 | `rustup` |
+| Tauri CLI | ≥ 2.0 | `cargo install tauri-cli` |
+| Git | — | — |
+| Dépendances système Linux | — | `sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev` |
+
+> **Important — staging :** `tauri.conf.json` embarque `backend/**` et `frontend/**` **depuis le dossier `desktop/`**.
+> Les scripts de build copient automatiquement `../backend` et `../frontend` dans `desktop/` avant `cargo tauri build`.
+> Sans ce staging, le build échoue avec « glob pattern backend/**/* path not found ».
+
+#### 🪟 Windows — `build-windows.bat`
+
+```cmd
+REM Prérequis (via Scoop) : rustup, curl, git
+scoop install rustup curl git
+rustup default stable
+cargo install tauri-cli
+
+cd desktop
+build-windows.bat
+```
+
+Étapes du script :
+
+1. Tue les processus Python résiduels (`taskkill /F /IM python.exe`)
+2. Télécharge **Python 3.11 embed** (python.org) → `desktop\python-embed\` + activation de pip (`python311._pth`)
+3. `pip install -r ..\backend\requirements.txt` dans l'embed
+4. **Staging** : copie `..\backend` et `..\frontend` dans `desktop\`
+5. `cargo tauri build --target x86_64-pc-windows-msvc --bundles nsis`
+6. Copie `python-embed` à côté de l'exécutable (`target\x86_64-pc-windows-msvc\release\`) pour le mode dev local
+7. Nettoie les dossiers stagés
+
+→ **Artefact :** `desktop\target\x86_64-pc-windows-msvc\release\bundle\nsis\ObsiGate_2.0.0_x64-setup.exe`
+
+#### 🐧 Linux — `build-linux.sh`
+
+```bash
+cd desktop
+chmod +x build-linux.sh
+./build-linux.sh
+```
+
+Étapes du script :
+
+1. Vérifie Rust + Tauri CLI, installe les dépendances système (apt)
+2. Crée un venv Python `desktop/python-embed/venv` + `pip install -r ../backend/requirements.txt`
+3. **Staging** : copie `../backend` et `../frontend` dans `desktop/`
+4. `cargo tauri build --target x86_64-unknown-linux-gnu --bundles deb,appimage`
+5. Copie le runtime (`python-embed/`, `backend/`, `frontend/`) à côté de l'exécutable
+
+→ **Artefacts :**
+
+- `desktop/target/x86_64-unknown-linux-gnu/release/bundle/deb/obsigate_2.0.0_amd64.deb`
+- `desktop/target/x86_64-unknown-linux-gnu/release/bundle/appimage/ObsiGate_2.0.0_amd64.AppImage`
+
+### 🤖 Builds CI/CD — artefacts automatiques
+
+**Oui** — le workflow [`.gitea/workflows/desktop-build.yml`](./.gitea/workflows/desktop-build.yml) construit les binaires desktop à chaque push sur `main` touchant `desktop/**`, `frontend/**` ou `backend/**` (et manuellement via `workflow_dispatch`), sur des **runners self-hosted** :
+
+| Job | Runner | Artefacts (conservés 30 jours) |
+|---|---|---|
+| `build-windows` | `[self-hosted, windows, desktop]` | `desktop/target/release/bundle/msi/*.msi` |
+| `build-linux` | `[self-hosted, linux, desktop]` | `*.AppImage` + `*.deb` |
+
+- Les artefacts sont téléchargeables depuis la page **Actions** du run Gitea.
+- La publication en **Gitea Release** est prévue sur les tags `v*` (étape `Publish to Gitea Release`).
+- Le workflow web [`.gitea/workflows/ci.yml`](./.gitea/workflows/ci.yml) gère de son côté lint → tests → sécurité → build Docker → e2e Playwright.
+
+### Architecture Desktop
+
+```
+┌────────────────────────────────────────────┐
+│ Tauri (Rust) │
+│ ├─ Webview (webview système) │
+│ │ └─ Frontend (HTML/JS/CSS) │
+│ └─ Sidecar Python │
+│ └─ uvicorn backend.main:app │
+│ └─ port 127.0.0.1:17890 │
+└────────────────────────────────────────────┘
+```
+
+Cycle de vie : Tauri spawn le backend Python → health check → splash de démarrage → redirection vers la webview. À la fermeture : kill propre du backend.
+
+---
+
+## 📖 Utilisation
+
+### Interface web
+
+1. **Navigation** : Cliquez sur les vaults dans la sidebar pour les développer
+2. **Recherche** : Utilisez la barre de recherche pour chercher dans toutes les vaults
+3. **Tags** : Cliquez sur les tags pour filtrer les contenus
+4. **Wikilinks** : Les liens `[[page]]` sont cliquables et navigables
+5. **Images** : Toutes les syntaxes d'images Obsidian sont rendues automatiquement
+6. **Thème** : Basculez entre thème clair/sombre avec l'icône 🌙/☀️
+
+### Raccourcis clavier
+
+| Action | Raccourci |
+|--------|-----------|
+| Recherche | `Ctrl + K` ou `/` |
+| Toggle thème | `Ctrl + T` |
+| Focus recherche | `Esc` |
+
+---
+
+## 🔌 API
+
+ObsiGate expose une API REST complète :
+
+| Endpoint | Description | Méthode | Auth |
+|----------|-------------|---------|------|
+| `/api/health` | Health check (status, version, stats) | GET | Non |
+| `/api/auth/status` | Statut auth (activé, utilisateurs présents) | GET | Non |
+| `/api/auth/login` | Connexion (access token + cookie refresh) | POST | Non |
+| `/api/auth/refresh` | Renouveler l'access token via cookie refresh | POST | Cookie |
+| `/api/auth/logout` | Déconnexion + révocation refresh token | POST | Oui |
+| `/api/auth/me` | Infos utilisateur courant | GET | Oui |
+| `/api/auth/change-password` | Changer son mot de passe | POST | Oui |
+| `/api/auth/admin/users` | Lister / créer des utilisateurs | GET/POST | Admin |
+| `/api/auth/admin/users/{u}` | Modifier / supprimer un utilisateur | PATCH/DELETE | Admin |
+| `/api/vaults` | Liste des vaults (filtrée par permissions) | GET | Oui |
+| `/api/browse/{vault}?path=` | Navigation dans les dossiers | GET | Oui |
+| `/api/file/{vault}?path=` | Contenu rendu d'un fichier | GET | Oui |
+| `/api/file/{vault}/raw?path=` | Contenu brut d'un fichier | GET | Oui |
+| `/api/file/{vault}/download?path=` | Téléchargement d'un fichier | GET | Oui |
+| `/api/file/{vault}/save?path=` | Sauvegarder un fichier | PUT | Oui |
+| `/api/file/{vault}?path=` | Supprimer un fichier | DELETE | Oui |
+| `/api/search/advanced` | Recherche avancée TF-IDF | GET | Oui |
+| `/api/suggest` / `/api/tags/suggest` | Autocomplétion | GET | Oui |
+| `/api/tags?vault=` | Tags uniques avec compteurs | GET | Oui |
+| `/api/index/reload` | Force un re-scan des vaults | GET | Admin |
+| `/api/events` | Flux SSE temps réel | GET | Oui |
+| `/api/vaults/add` / `/api/vaults/{name}` | Gestion dynamique des vaults | POST/DELETE | Admin |
+| `/api/image/{vault}?path=` | Servir une image | GET | Oui |
+| `/api/config` | Lire / écrire la configuration | GET/POST | Oui/Admin |
+| `/api/diagnostics` | Statistiques index et mémoire | GET | Admin |
+
+> Quand `OBSIGATE_AUTH_ENABLED=false`, tous les endpoints sont accessibles sans token.
+> Tous les endpoints exposent des schémas Pydantic documentés ; doc interactive sur `/docs` (Swagger UI).
+
+**Exemples :**
+
+```bash
+curl http://localhost:2020/api/health
+curl http://localhost:2020/api/vaults
+curl "http://localhost:2020/api/search/advanced?q=recette%20tag:cuisine&vault=all&limit=20&offset=0&sort=relevance"
+curl "http://localhost:2020/api/suggest?q=piz&vault=all"
+curl "http://localhost:2020/api/file/Recettes?path=pizza.md"
+```
+
+---
+
+## 🔍 Recherche avancée
+
+### Syntaxe de requête
+
+| Opérateur | Description | Exemple |
+|-----------|-------------|---------|
+| `tag:` | Filtrer par tag | `tag:recette docker` |
+| `#` | Raccourci tag | `#linux serveur` |
+| `vault:` | Filtrer par vault | `vault:IT kubernetes` |
+| `title:` | Filtrer par titre | `title:pizza` |
+| `path:` | Filtrer par chemin | `path:recettes/soupes` |
+| `ext:` | Filtrer par type de fichier | `ext:md kubernetes` |
+| `"phrase exacte"` | Recherche de phrase | `tag:"multi mots"` |
+
+Les opérateurs sont combinables : `tag:linux vault:IT ext:md serveur web`.
+
+### Raccourcis clavier
+
+| Raccourci | Action |
+|-----------|--------|
+| `Ctrl+K` / `Cmd+K` | Focaliser la barre de recherche |
+| `/` | Focaliser la recherche (hors champ texte) |
+| `↑` / `↓` | Naviguer dans les suggestions |
+| `Enter` | Sélectionner la suggestion active ou lancer la recherche |
+| `Escape` | Fermer les suggestions / quitter la recherche |
+
+### Fonctionnalités
+
+- **TF-IDF** : scoring par fréquence pondérée des termes
+- **Boost titre** : correspondances dans le titre ×3
+- **Normalisation des accents** : `resume` trouve `résumé`
+- **Snippets surlignés** (``), **facettes** (compteurs par vault/tag), **pagination** (50/page), **tri** pertinence/date, **chips** de filtres, **historique** (50 recherches)
+
+---
+
+## 🔧 Dépannage
+
+**Port déjà utilisé :**
+
+```bash
+sudo netstat -tulpn | grep 2020
+# Changer le port dans docker-compose.yml : ports: - "2021:8080"
+```
+
+**Vault non trouvée :** chemins absolus, permissions de lecture, redémarrer le conteneur après modification.
+
+**Build échoue :**
+
+```bash
+docker system prune -f
+docker compose down
+./build.sh --progress=plain
+# Si l'échec persiste :
+./build.sh --progress=plain 2>&1 | tee build.log
+```
+
+**Logs pour debugging :**
+
+```bash
+docker compose logs -f obsigate
+docker compose logs --tail=100 obsigate
+```
+
+**Desktop :** les logs du backend sont dans `%APPDATA%\ObsiGate\logs\backend.log` (Windows) / `~/.config/obsigate/logs/backend.log` (Linux).
+
+---
+
+## ⚡ Performance
+
+| Métrique | Estimation |
+|----------|------------|
+| **Indexation** | ~1–2s pour 1 000 fichiers markdown |
+| **Recherche avancée** | < 10ms pour la plupart des requêtes (index inversé + TF-IDF) |
+| **Résolution wikilinks** | O(1) via table de lookup |
+| **Mémoire** | ~80–150MB par 1 000 fichiers (contenu capé à 100 KB/fichier) |
+| **Image Docker** | ~180MB (multi-stage) |
+| **CPU** | Non-bloquant ; recherche offloadée sur thread pool dédié |
+
+### Paramètres recommandés par taille de vault
+
+| Taille | Fichiers | `search_workers` | `prefix_max_expansions` | `max_content_size` |
+|--------|----------|-------------------|--------------------------|---------------------|
+| Petit | < 500 | 1 | 50 | 100 000 |
+| Moyen | 500–5 000 | 2 | 50 | 100 000 |
+| Grand | 5 000+ | 4 | 30 | 50 000 |
+
+Configurables via l'interface (Settings) ou l'API `/api/config`.
+
+### Optimisations clés
+
+- **Index inversé avec set-intersection** + prefix matching par recherche binaire
+- **ThreadPoolExecutor** : recherche CPU-bound hors de l'event loop asyncio
+- **InvertedIndex incrémental** : hooks `add_document`/`remove_document`, plus de rebuild O(N)
+- **Compression GZip** (~70% de bande passante économisée) + **Cache-Control immutable** (1 an)
+- **Race condition guard** (`currentSearchId` + AbortController), progress bar, timeout 30s
+- **Rendu Markdown singleton**, debounced icon rendering, recherche sans I/O disque
+
+---
+
+## 🛡️ Sécurité
+
+- **Path traversal** : tous les endpoints fichier valident que le chemin résolu reste dans la vault
+- **Rate limiting** : 10 tentatives de login max par IP sur 15 minutes + lockout par compte (5 tentatives)
+- **Audit log** : écritures/suppressions/config journalisées dans `data/audit.log` (JSON lines, rotation 10 MB)
+- **Backup automatique** : chaque modification/suppression sauvegardée dans `.obsigate-backup/` avec timestamp
+- **Secret redaction** : masquage automatique des JWT, clés API, tokens dans les aperçus
+- **Utilisateur non-root** : conteneur Docker sous `obsigate` (UID 1000)
+- **Volumes read-only** : vaults montées en `:ro` par défaut
+- **Secrets dans `.env`** : jamais dans `docker-compose.yml`
+- **Atomic writes** : tmp+replace pour users.json, shares.json, webhooks.json
+
+---
+
+## 🏗️ Stack technique
+
+- **Backend** : Python 3.11 + FastAPI 0.110 + Uvicorn
+- **Auth** : python-jose (JWT HS256) + argon2-cffi (Argon2id)
+- **File Watcher** : watchdog 4.x (inotify natif + fallback polling)
+- **Frontend** : Vanilla JS + HTML + CSS (zéro framework, zéro build)
+- **Rendu Markdown** : mistune 3.x
+- **PDF Export** : WeasyPrint 60+
+- **Desktop** : Tauri v2 (Rust) + Python embarqué
+- **Image Docker** : python:3.11-slim (multi-stage)
+- **Stockage utilisateurs** : JSON local (`data/users.json`) — aucune base de données
+- **Architecture** : SPA + API REST + SSE
+
+---
+
+## 🏠 Architecture
+
+```
+┌─────────────────┐ ┌─────────────────────────────────────────┐
+│ Navigateur │◄───►│ FastAPI (backend/main.py) │
+│ (SPA) │ REST │ ┌──────────────┐ ┌──────────────┐ │
+│ │ │ │ indexer.py │ │ search.py │ │
+│ app.js │ │ │ (scan+cache) │ │ (in-memory) │ │
+│ style.css │ │ └───────┬────── └──────┬───────┘ │
+│ index.html │ │ │ │ │
+└─────────────────┘ │ ┌───┴──────────────┴───┐ │
+ │ │ Index en mémoire │ │
+ │ │ (fichiers, tags, │ │
+ │ │ contenu, lookup) │ │
+ │ └───────────────────────┘ │
+ └─────────────────────────────────────────┘
+```
+
+**Flux de données :**
+
+1. Au démarrage, `indexer.py` scanne tous les vaults en parallèle (thread pool, non-bloquant)
+2. Contenu, tags (YAML + inline) et métadonnées mis en cache en mémoire
+3. Table de lookup O(1) pour la résolution des wikilinks
+4. `watcher.py` surveille les fichiers (watchdog natif ou polling)
+5. Modifications → mise à jour incrémentale de l'index
+6. Changements notifiés au frontend via SSE
+7. Recherche sur l'index mémoire (zéro I/O disque)
+8. Le frontend SPA communique via REST + SSE
+
+---
+
+## 📝 Développement
+
+### Structure du projet
+
+```
+ObsiGate/
+├── backend/ # API FastAPI
+│ ├── main.py # Endpoints, Pydantic models, rendu markdown
+│ ├── indexer.py # Scan des vaults, index en mémoire, lookup table
+│ ├── search.py # Moteur de recherche fulltext avec scoring
+│ ├── watcher.py # Surveillance fichiers (watchdog + debounce)
+│ ├── auth/ # Module d'authentification (JWT + Argon2id)
+│ └── create_admin.py # CLI gestion utilisateurs
+├── frontend/ # Interface web (Vanilla JS, zéro framework)
+│ ├── index.html # Page SPA + écran de login + splash de boot
+│ ├── js/ # Modules ES (app, viewer, editor, graph, …)
+│ └── style.css # Styles (CSS variables, thèmes, responsive)
+├── desktop/ # Application native Tauri (Rust + Python embarqué)
+│ ├── src/main.rs # Shell natif : cycle de vie backend, tray, menus
+│ ├── build-windows.bat
+│ └── build-linux.sh
+├── data/ # Données persistantes (créé au démarrage)
+├── Dockerfile # Multi-stage, healthcheck, non-root
+├── docker-compose.yml # Déploiement avec healthcheck et auth env vars
+├── build.sh # Build & déploiement automatisé
+└── docs/ # ROADMAP, guides, audits
+```
+
+### Contribuer
+
+Voir [docs/CONTRIBUTING.md](./docs/CONTRIBUTING.md) pour les détails.
+
+---
+
+## 📄 Licence
+
+Ce projet est sous licence **MIT** — voir le fichier [LICENSE](LICENSE) pour les détails.
+
+---
+
+## 🤝 Support
+
+- **Issues** : [git.dracodev.net/Projets/ObsiGate/issues](https://git.dracodev.net/Projets/ObsiGate/issues)
+- **Documentation** : [git.dracodev.net/Projets/ObsiGate/wiki](https://git.dracodev.net/Projets/ObsiGate/wiki)
+
+---
+
+## 📝 Changelog
+
+Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.0.0-dev).
+
+---
+
+*Projet : ObsiGate | Version : 2.0.0-dev | Dernière mise à jour : Juin 2026*
diff --git a/README.md b/README.md
index 78c665e..a92adb2 100644
--- a/README.md
+++ b/README.md
@@ -552,28 +552,79 @@ chmod +x ObsiGate_2.0.0_amd64.AppImage
### Build depuis les sources
-Voir le [guide détaillé dans desktop/](./desktop/README.md).
+Guide détaillé : [desktop/README.md](./desktop/README.md).
-```bash
-# Linux
-cd desktop
-chmod +x build-linux.sh
-./build-linux.sh
-# → target/release/bundle/deb/obsigate_2.0.0_amd64.deb
-# → target/release/bundle/appimage/ObsiGate_2.0.0_amd64.AppImage
-```
+#### Prérequis communs
+
+| Outil | Version | Installation |
+|---|---|---|
+| Rust (cargo) | ≥ 1.75 | `rustup` |
+| Tauri CLI | ≥ 2.0 | `cargo install tauri-cli` |
+| Git | — | — |
+| Dépendances système Linux | — | `sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev` |
+
+> **Important — staging :** `tauri.conf.json` embarque `backend/**` et `frontend/**` **depuis le dossier `desktop/`**.
+> Les scripts de build copient automatiquement `../backend` et `../frontend` dans `desktop/` avant `cargo tauri build`.
+> Sans ce staging, le build échoue avec « glob pattern backend/**/* path not found ».
+
+#### 🪟 Windows — `build-windows.bat`
```cmd
-REM Windows (via Scoop)
+REM Prérequis (via Scoop) : rustup, curl, git
scoop install rustup curl git
rustup default stable
cargo install tauri-cli
cd desktop
build-windows.bat
-REM → target\release\bundle\msi\ObsiGate_2.0.0_x64.msi
```
+Étapes du script :
+
+1. Tue les processus Python résiduels (`taskkill /F /IM python.exe`)
+2. Télécharge **Python 3.11 embed** (python.org) → `desktop\python-embed\` + activation de pip (`python311._pth`)
+3. `pip install -r ..\backend\requirements.txt` dans l'embed
+4. **Staging** : copie `..\backend` et `..\frontend` dans `desktop\`
+5. `cargo tauri build --target x86_64-pc-windows-msvc --bundles nsis`
+6. Copie `python-embed` à côté de l'exécutable (`target\x86_64-pc-windows-msvc\release\`) pour le mode dev local
+7. Nettoie les dossiers stagés
+
+→ **Artefact :** `desktop\target\x86_64-pc-windows-msvc\release\bundle\nsis\ObsiGate_2.0.0_x64-setup.exe`
+
+#### 🐧 Linux — `build-linux.sh`
+
+```bash
+cd desktop
+chmod +x build-linux.sh
+./build-linux.sh
+```
+
+Étapes du script :
+
+1. Vérifie Rust + Tauri CLI, installe les dépendances système (apt)
+2. Crée un venv Python `desktop/python-embed/venv` + `pip install -r ../backend/requirements.txt`
+3. **Staging** : copie `../backend` et `../frontend` dans `desktop/`
+4. `cargo tauri build --target x86_64-unknown-linux-gnu --bundles deb,appimage`
+5. Copie le runtime (`python-embed/`, `backend/`, `frontend/`) à côté de l'exécutable
+
+→ **Artefacts :**
+
+- `desktop/target/x86_64-unknown-linux-gnu/release/bundle/deb/obsigate_2.0.0_amd64.deb`
+- `desktop/target/x86_64-unknown-linux-gnu/release/bundle/appimage/ObsiGate_2.0.0_amd64.AppImage`
+
+### 🤖 Builds CI/CD — artefacts automatiques
+
+**Oui** — le workflow [`.gitea/workflows/desktop-build.yml`](./.gitea/workflows/desktop-build.yml) construit les binaires desktop à chaque push sur `main` touchant `desktop/**`, `frontend/**` ou `backend/**` (et manuellement via `workflow_dispatch`), sur des **runners self-hosted** :
+
+| Job | Runner | Artefacts (conservés 30 jours) |
+|---|---|---|
+| `build-windows` | `[self-hosted, windows, desktop]` | `desktop/target/release/bundle/msi/*.msi` |
+| `build-linux` | `[self-hosted, linux, desktop]` | `*.AppImage` + `*.deb` |
+
+- Les artefacts sont téléchargeables depuis la page **Actions** du run Gitea.
+- La publication en **Gitea Release** est prévue sur les tags `v*` (étape `Publish to Gitea Release`).
+- Le workflow web [`.gitea/workflows/ci.yml`](./.gitea/workflows/ci.yml) gère de son côté lint → tests → sécurité → build Docker → e2e Playwright.
+
### Architecture Desktop
```
diff --git a/desktop/build-linux.sh b/desktop/build-linux.sh
index 08039e8..08eb1d2 100755
--- a/desktop/build-linux.sh
+++ b/desktop/build-linux.sh
@@ -29,6 +29,15 @@ pip install --upgrade pip -q
pip install -r ../backend/requirements.txt -q
echo "✅ Python environment ready"
+# ── 2b. Stage backend + frontend pour le bundle ───────────────
+# tauri.conf.json embarque backend/** et frontend/** depuis desktop/ :
+# sans ce staging, tauri_build échoue (« glob pattern backend/**/* path not found »).
+echo "[2b/5] Staging backend + frontend for bundle..."
+rm -rf backend frontend
+cp -r ../backend backend
+cp -r ../frontend frontend
+echo "✅ Staged"
+
# ── 3. Build Tauri ────────────────────────────────────────────
echo "[3/5] Building Tauri application..."
cargo tauri build --target x86_64-unknown-linux-gnu --bundles deb,appimage
@@ -43,6 +52,10 @@ cp -r ../backend "$OUTDIR/"
cp -r ../frontend "$OUTDIR/"
echo "✅ Runtime + frontend copied"
+# ── 4b. Nettoyer les dossiers stagés ──────────────────────────
+rm -rf backend frontend
+echo "✅ Cleaned"
+
# ── 5. Résultats ───────────────────────────────────────────────
OUTDIR="target/x86_64-unknown-linux-gnu/release"
echo "[5/5] Results:"
diff --git a/desktop/gen/schemas/windows-schema.json b/desktop/gen/schemas/windows-schema.json
index d2f01e3..438ec59 100644
--- a/desktop/gen/schemas/windows-schema.json
+++ b/desktop/gen/schemas/windows-schema.json
@@ -6337,6 +6337,234 @@
"type": "string",
"const": "shell:deny-stdin-write",
"markdownDescription": "Denies the stdin_write command without any pre-configured scope."
+ },
+ {
+ "description": "This permission set configures what kind of\noperations are available from the store plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n\n#### This default permission set includes:\n\n- `allow-load`\n- `allow-get-store`\n- `allow-set`\n- `allow-get`\n- `allow-has`\n- `allow-delete`\n- `allow-clear`\n- `allow-reset`\n- `allow-keys`\n- `allow-values`\n- `allow-entries`\n- `allow-length`\n- `allow-reload`\n- `allow-save`",
+ "type": "string",
+ "const": "store:default",
+ "markdownDescription": "This permission set configures what kind of\noperations are available from the store plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n\n#### This default permission set includes:\n\n- `allow-load`\n- `allow-get-store`\n- `allow-set`\n- `allow-get`\n- `allow-has`\n- `allow-delete`\n- `allow-clear`\n- `allow-reset`\n- `allow-keys`\n- `allow-values`\n- `allow-entries`\n- `allow-length`\n- `allow-reload`\n- `allow-save`"
+ },
+ {
+ "description": "Enables the clear command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:allow-clear",
+ "markdownDescription": "Enables the clear command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the delete command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:allow-delete",
+ "markdownDescription": "Enables the delete command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the entries command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:allow-entries",
+ "markdownDescription": "Enables the entries command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the get command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:allow-get",
+ "markdownDescription": "Enables the get command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the get_store command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:allow-get-store",
+ "markdownDescription": "Enables the get_store command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the has command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:allow-has",
+ "markdownDescription": "Enables the has command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the keys command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:allow-keys",
+ "markdownDescription": "Enables the keys command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the length command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:allow-length",
+ "markdownDescription": "Enables the length command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the load command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:allow-load",
+ "markdownDescription": "Enables the load command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the reload command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:allow-reload",
+ "markdownDescription": "Enables the reload command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the reset command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:allow-reset",
+ "markdownDescription": "Enables the reset command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the save command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:allow-save",
+ "markdownDescription": "Enables the save command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the set command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:allow-set",
+ "markdownDescription": "Enables the set command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the values command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:allow-values",
+ "markdownDescription": "Enables the values command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the clear command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:deny-clear",
+ "markdownDescription": "Denies the clear command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the delete command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:deny-delete",
+ "markdownDescription": "Denies the delete command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the entries command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:deny-entries",
+ "markdownDescription": "Denies the entries command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the get command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:deny-get",
+ "markdownDescription": "Denies the get command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the get_store command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:deny-get-store",
+ "markdownDescription": "Denies the get_store command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the has command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:deny-has",
+ "markdownDescription": "Denies the has command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the keys command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:deny-keys",
+ "markdownDescription": "Denies the keys command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the length command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:deny-length",
+ "markdownDescription": "Denies the length command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the load command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:deny-load",
+ "markdownDescription": "Denies the load command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the reload command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:deny-reload",
+ "markdownDescription": "Denies the reload command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the reset command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:deny-reset",
+ "markdownDescription": "Denies the reset command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the save command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:deny-save",
+ "markdownDescription": "Denies the save command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the set command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:deny-set",
+ "markdownDescription": "Denies the set command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the values command without any pre-configured scope.",
+ "type": "string",
+ "const": "store:deny-values",
+ "markdownDescription": "Denies the values command without any pre-configured scope."
+ },
+ {
+ "description": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`",
+ "type": "string",
+ "const": "updater:default",
+ "markdownDescription": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`"
+ },
+ {
+ "description": "Enables the check command without any pre-configured scope.",
+ "type": "string",
+ "const": "updater:allow-check",
+ "markdownDescription": "Enables the check command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the download command without any pre-configured scope.",
+ "type": "string",
+ "const": "updater:allow-download",
+ "markdownDescription": "Enables the download command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the download_and_install command without any pre-configured scope.",
+ "type": "string",
+ "const": "updater:allow-download-and-install",
+ "markdownDescription": "Enables the download_and_install command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the install command without any pre-configured scope.",
+ "type": "string",
+ "const": "updater:allow-install",
+ "markdownDescription": "Enables the install command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the check command without any pre-configured scope.",
+ "type": "string",
+ "const": "updater:deny-check",
+ "markdownDescription": "Denies the check command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the download command without any pre-configured scope.",
+ "type": "string",
+ "const": "updater:deny-download",
+ "markdownDescription": "Denies the download command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the download_and_install command without any pre-configured scope.",
+ "type": "string",
+ "const": "updater:deny-download-and-install",
+ "markdownDescription": "Denies the download_and_install command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the install command without any pre-configured scope.",
+ "type": "string",
+ "const": "updater:deny-install",
+ "markdownDescription": "Denies the install command without any pre-configured scope."
}
]
},
diff --git a/desktop/src/main.rs b/desktop/src/main.rs
index 7ee0992..23343a0 100644
--- a/desktop/src/main.rs
+++ b/desktop/src/main.rs
@@ -566,6 +566,10 @@ fn main() {
match wait_for_backend().await {
Ok(()) => {
if let Some(window) = handle.get_webview_window("main") {
+ // Met à jour le splash inline avant la redirection
+ let _ = window.eval(
+ r#"if (window.__setBootStatus) window.__setBootStatus("Backend prêt — chargement de l'interface…")"#,
+ );
match window.eval("window.location.href = 'http://127.0.0.1:17890'") {
Ok(_) => info!("Redirect to backend OK"),
Err(e) => error!("Redirect eval failed: {}", e),
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
index 36bf690..57af31a 100644
--- a/docs/ROADMAP.md
+++ b/docs/ROADMAP.md
@@ -841,7 +841,7 @@
- [ ] **Page de release** : intégrer le build desktop dans les releases Gitea + README d'installation
##### E. Expérience utilisateur (1 jour)
- - [ ] Écran de chargement pendant le démarrage du backend (« ObsiGate démarre... » avec spinner)
+ - [x] Écran de chargement pendant le démarrage du backend (« ObsiGate démarre... » avec spinner) — splash inline `#boot-splash` dans `index.html`, retiré quand `app.js` signale le boot ; statut mis à jour depuis Rust
- [ ] Gestion des erreurs : backend crash → message explicite + bouton « Redémarrer »
- [ ] Sauvegarde des préférences desktop (taille fenêtre, position, dernier vault)
- [ ] Première expérience : wizard « Choisissez votre vault » au premier lancement
diff --git a/frontend/index.html b/frontend/index.html
index 3685243..1cd87bd 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -47,6 +47,33 @@
href="/static/icons/icon-192x192.svg"
/>
+
+
-
-
-
+
+
+
+
+
+
+
+
📖
+
ObsiGate
+
+
Démarrage…
+
+
+
diff --git a/frontend/js/app.js b/frontend/js/app.js
index 0dc50cb..582c790 100644
--- a/frontend/js/app.js
+++ b/frontend/js/app.js
@@ -102,8 +102,15 @@ async function init() {
}
document.addEventListener("DOMContentLoaded", () => {
- init();
- Sync.init();
+ try {
+ init();
+ Sync.init();
+ } finally {
+ // Signale au splash de démarrage (index.html #boot-splash) que l'app est
+ // initialisée : le splash est retiré et le squelette stylé révélé.
+ window.__OBSIGATE_BOOTED = true;
+ if (typeof window.__obsigateBootReady === "function") window.__obsigateBootReady();
+ }
// Show version in header
fetch('/api/health')
.then(r => r.json())