diff --git a/AGENTS.md b/AGENTS.md index 11f48fd..3cd6c00 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,44 +1,93 @@ # AGENTS.md — Instructions obligatoires du dépôt ObsiGate > Ces instructions s'appliquent à **toute** intervention (humaine ou IA) sur ce dépôt. +> Documentation et réponses en **français**. ## Règle n°1 — Méthode de livraison unique Avant toute tâche (fonctionnalité, bug, refactor), **lire et appliquer** [`docs/DELIVERY_WORKFLOW.md`](./docs/DELIVERY_WORKFLOW.md) (Definition of Done). -Aucune tâche n'est terminée avant que sa checklist soit complète **et le CI vert**. +Aucune tâche n'est terminée avant que sa checklist soit complète **et le CI vert** +(jobs `lint`, `test`, `security`, `build`, `e2e` de `.gitea/workflows/ci.yml`). ## Avant de commencer 1. Lire [`docs/ROADMAP.md`](./docs/ROADMAP.md) (travail à venir + index) et [`docs/ISSUES_TODOLIST.md`](./docs/ISSUES_TODOLIST.md) (bugs). -2. Identifier ou créer l'**ID stable** (`#NN` pour une feature, `BUG-NNN` pour un bug) - et passer son statut à « en cours » **avant** de coder. +2. Identifier ou créer l'**ID stable** (`#NN` pour une feature, `BUG-NNN` pour un bug — + jamais réutilisé) et passer son statut à « en cours » **avant** de coder. + +## Architecture (ce qui n'est pas obvious) + +- **Backend** : FastAPI/Python 3.11, point d'entrée `backend/main.py` (endpoints + rendu + markdown), index en mémoire (`indexer.py`, `search.py`), watcher (`watcher.py`), + auth dans `backend/auth/`. Pas de base de données : JSON dans `data/`. +- **Frontend** : vanilla JS **zéro framework, zéro build npm** (`frontend/app.js`, + `index.html`, `style.css`). Ne pas ajouter de dépendances npm ni d'étape de build. +- **Desktop** : Tauri (Rust) dans `desktop/` ; `tauri.conf.json` embarque `backend/**` et + `frontend/**` depuis `desktop/` — les scripts de build font le **staging** (copie) avant + `cargo tauri build`, sinon le build échoue. +- **i18n** : tout texte d'interface doit exister en FR **et** EN + (`frontend/locales/fr.json` + `en.json`). + +## Vérifications locales (pwsh, à faire passer avant tout commit/push) + +```powershell +# Backend (venv à la racine) +.\.venv\Scripts\python.exe -m pytest tests/ +.\.venv\Scripts\python.exe -m ruff check backend/ +.\.venv\Scripts\python.exe -m mypy backend/ --ignore-missing-imports + +# Frontend : scripts Node à exécuter directement (pas de runner) +node tests/frontend/validate-imports.mjs +node tests/frontend/unit.test.mjs +# Tests JSDOM : node_modules dans tests/frontend/ (npm install là-bas si absent), ex : +node tests/frontend/pane-manager.test.mjs + +# E2E (si UI touchée, ~5 min) : reproduit le job CI e2e (port 2029, auth désactivée) +npm run test:e2e # prérequis : uv, Node >= 20, npx playwright install chromium +bash scripts/run-e2e-local.sh -g "nom du test" # filtre / --headed +``` + +- Un seul test backend : `.\.venv\Scripts\python.exe -m pytest tests/test_search.py -q`. +- **Sélection E2E** : vérifier chaque sélecteur dans le DOM réel avant de l'utiliser dans un + test ; tout test nouveau/modifié doit passer en local avant push ; pas de contournement + qui masque la flakiness (`waitForTimeout` arbitraires, fallbacks silencieux). +- La suite E2E doit finir à **100 %** sans s'appuyer sur les retries. Jamais de `git push` + avant que les 5 étapes locales soient vertes. + +## Version & hooks (pièges) + +- `VERSION` (racine) = **source unique de vérité** (SemVer), incrémenté **automatiquement à + chaque commit** par le hook `.githooks/prepare-commit-msg` — `feat` → mineur, + `!:` / `BREAKING CHANGE` → majeur, sinon correctif. Le même commit resynchronise + `package.json`, le desktop Tauri, `README.md`/`README.fr.md`, `docs/ROADMAP.md` et publie + la section `[Unreleased]` du `CHANGELOG.md` en `[X.Y.Z] — date` ; tag `vX.Y.Z` créé au + commit, publié au push (`push.followTags`). +- Hooks **obligatoires**, à installer une fois par clone : `scripts/install-hooks.sh` + (sinon la version ne suit plus et le CI échoue via le garde-fou `tests/test_version.py`). +- Le rattachement des fichiers de bump se fait par un `--amend` immédiat : **le SHA affiché + par `git commit` change** — ne pas s'y fier. +- Commit sans incrément (exceptionnel) : `SKIP_VERSION_BUMP=1 git commit …`. +- Ne jamais réécrire une version déjà publiée dans le CHANGELOG ; jamais de détail dupliqué + entre Roadmap et CHANGELOG. ## À la fin de chaque tâche (obligatoire) -- Ajouter/mettre à jour les **tests unitaires**. -- Vérifications locales vertes : `pytest`, `ruff`, `mypy`, tests frontend (`E2E` si UI). -- Mettre à jour la documentation requise : `CHANGELOG.md` (`[Unreleased]`), `docs/ROADMAP.md` - (statut + index), fiche `docs/features/` **ou** `docs/archive/`, `docs/ISSUES_TODOLIST.md`, - guide utilisateur i18n FR/EN + README si impact utilisateur. -- **Commit** conventionnel référençant l'ID, puis **push**. -- Version : le fichier VERSION (racine du dépôt) est la **source unique de - vérité (MAJEUR.MINEUR.CORRECTIF), incrémenté automatiquement à chaque commit** par le hook - .githooks/prepare-commit-msg — feat → mineur, !: / BREAKING CHANGE → majeur, sinon - correctif. Le même commit resynchronise package.json, le desktop Tauri, README.md/ - README.fr.md, docs/ROADMAP.md et publie la section [Unreleased] du CHANGELOG.md en - [X.Y.Z] — date ; le tag vX.Y.Z est créé au commit et publié au push (push.followTags). - Hooks à installer une fois par clone : scripts/install-hooks.sh. Garde-fou : - tests/test_version.py (détail : docs/DELIVERY_WORKFLOW.md §7). -- Vérifier le **CI Gitea vert** (jobs `lint`, `test`, `security`, `build`, `e2e`). +- Tests unitaires ajoutés/mis à jour (correctif sans test de non-régression = pas terminé). +- Toutes les vérifications locales ci-dessus vertes (`E2E` si UI). +- Documentation mise à jour : `CHANGELOG.md` (`[Unreleased]`), `docs/ROADMAP.md` (statut + + index), fiche `docs/features/` **ou** `docs/archive/`, `docs/ISSUES_TODOLIST.md` (si bug), + guide utilisateur i18n FR/EN + README si impact utilisateur, docstrings + + `response_model` si API. +- **Commit** conventionnel référençant l'ID (`feat: … #12`), puis **push** et **CI vert**. ## Cartographie documentaire | Sujet | Fichier | |---|---| | Méthode de livraison / DoD | `docs/DELIVERY_WORKFLOW.md` | -| Version livrée (source unique) | VERSION + scripts/bump_version.py | +| Version livrée (source unique) | `VERSION` + `scripts/bump_version.py` | | Travail à venir + index | `docs/ROADMAP.md` | | Historique des versions | `CHANGELOG.md` | | Conception par feature | `docs/features/.md` | @@ -50,5 +99,8 @@ Aucune tâche n'est terminée avant que sa checklist soit complète **et le CI v ## Conventions - Commits : `type: description` — `feat`, `fix`, `perf`, `refactor`, `docs`, `style`, `chore`, `test`. -- **Ne jamais** committer de secrets, clés ou tokens. -- Réponses et documentation en **français** ; respecter le style du code existant. +- Sécurité : tout chemin fichier fourni par l'utilisateur passe par `_resolve_safe_path()`. +- **Ne jamais** committer de secrets, clés ou tokens (`.env` jamais committé ; secrets dans + `data/api_keys.json` ou variables `OBSIGATE_*`). +- Respecter le style du code existant (ruff/mypy 0 erreur ; CSS variables, pas de couleurs + hardcodées ; `safeCreateIcons()` plutôt que `lucide.createIcons()` direct). diff --git a/CHANGELOG.md b/CHANGELOG.md index 0da39b8..334944c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Format basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/), et [Semantic Versioning](https://semver.org/spec/v2.0.0.html). > **En cours de développement** : les changements à venir sont listés dans la section -> [Unreleased](#unreleased). La dernière version livrée est **2.11.5**. +> [Unreleased](#unreleased). La dernière version livrée est **2.11.6**. --- @@ -14,6 +14,53 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html). --- +## [2.11.6] — 2026-09-18 + +### Corrigé + +- **BUG-064 - Éditeur Excalidraw : le diagramme ne s'affiche jamais (canvas vide)** — deux + causes cumulées : + 1. **La feuille de style d'Excalidraw n'était jamais chargée** (`@excalidraw/excalidraw` exige + un import CSS explicite). Sans elle, l'éditeur est non stylisé et `.excalidraw` n'a aucune + hauteur fixe : la boucle de redimensionnement d'Excalidraw fait grossir le canvas jusqu'au + plafond codé en dur de `2^25` (33 554 432 px), que le navigateur ne peut pas dessiner → scène + blanche. Correctifs : `` vers `…/@excalidraw/excalidraw@0.18.0/dist/prod/index.css` dans + `frontend/excalidraw-editor.html` et ajout de `https://esm.sh` à `style-src` de la CSP + (`backend/main.py`). + 2. `appState.collaborators` est une `Map` sérialisée en objet JSON (`{}`) par l'app Excalidraw / + le plugin Obsidian ; réinjectée via `initialData`, Excalidraw 0.18 appelait `.forEach()` dessus + et plantait (`e.appState.collaborators.forEach is not a function`). `sanitizeAppState()` la + reconvertit en `Map` et écarte la géométrie de viewport importée (`width`, `height`, + `offsetLeft`, `offsetTop`) pour les deux formats (`.excalidraw` et `.excalidraw.md`). + Tests de non-régression : `tests/frontend/excalidraw-viewer.test.mjs` (CSS lié + CSP), + `tests/test_security_hardening.py::TestCspExcalidrawStylesheet`, `tests/e2e/excalidraw.spec.js` + (hauteur de canvas bornée) + fixtures `test_vault/diagram-app-export.excalidraw`. +- **BUG-064 (complément) - Éditeur Excalidraw : pleine largeur quand la navigation est masquée** : + la règle `.sidebar.hidden ~ .content-wrapper .content-area { max-width: 1200px }` (colonne de + lecture centrée) s'appliquait aussi au viewer Excalidraw. Ajout de + `.content-area:has(iframe[src*="excalidraw-editor.html"])` en `max-width: none; margin: 0`, + comme pour les viewers PDF/image (BUG-062). Fichier : `frontend/style.css`. +- **BUG-065 - Excalidraw : l'auto-save rechargeait la page en pleine édition** : chaque + modification déclenchait, 2 s plus tard, un `PUT /api/file/…/save` ; l'écriture émettait + `index_updated` (SSE) qui re-rendait la vue et **recréait l'iframe** — un refresh visible qui + interrompait le dessin. L'auto-save est supprimée (`frontend/js/excalidraw-viewer.js`) : + sauvegarde explicite par le bouton « 💾 Save » ou `Ctrl+S`. En complément, `reloadExternalWrite` + ne re-rend plus la vue quand un iframe Excalidraw est déjà ouvert sur le fichier + (`iframe[data-excalidraw-*]`), et le badge « Modified » ne se déclenche plus sur les + changements d'`appState` (resize, zoom) mais uniquement sur le contenu (signature des éléments). +- **#78 (complément) - Bouton plein écran pour les diagrammes Excalidraw** : nouveau bouton + `#btn-fullscreen` dans la barre d'outils de l'éditeur (`frontend/excalidraw-editor.html`) qui + bascule le mode plein écran natif ; l'iframe est créée avec `allow="fullscreen" allowfullscreen` + (`frontend/js/excalidraw-viewer.js`). Fichiers : `frontend/excalidraw-editor.html`, + `frontend/js/excalidraw-viewer.js`, `tests/frontend/excalidraw-viewer.test.mjs`. +- **#78 (complément) - Barre d'outils Excalidraw en icônes, verticale à droite** : les boutons + Save / PNG / SVG / plein écran passent en **icônes seules** (34×34 px) dans une colonne + **collée au bord droit** (`right: 0`), débutant à `45%` de la hauteur, empilée verticalement, + avec infobulles et `aria-label`. L'icône du bouton Save devient une coche après une sauvegarde + réussie. Fichier : `frontend/excalidraw-editor.html`. + +--- + ## [2.11.5] — 2026-09-17 ### Corrigé diff --git a/README.fr.md b/README.fr.md index e3b5810..e46a0cf 100644 --- a/README.fr.md +++ b/README.fr.md @@ -4,7 +4,7 @@ **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. -[![Version](https://img.shields.io/badge/Version-2.11.5-blue.svg)]() +[![Version](https://img.shields.io/badge/Version-2.11.6-blue.svg)]() [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/) [![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/) @@ -926,8 +926,8 @@ Ce projet est sous licence **MIT** — voir le fichier [LICENSE](LICENSE) pour l ## 📝 Changelog -Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.11.5). +Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.11.6). --- -*Projet : ObsiGate | Version : 2.11.5 | Dernière mise à jour : Juin 2026* +*Projet : ObsiGate | Version : 2.11.6 | Dernière mise à jour : Juin 2026* diff --git a/README.md b/README.md index ca4eac7..f3b3769 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **Ultra-light web gateway for your Obsidian vaults** — Access, browse, and search all your Obsidian notes from any device via a modern, responsive web interface. -[![Version](https://img.shields.io/badge/Version-2.11.5-blue.svg)]() +[![Version](https://img.shields.io/badge/Version-2.11.6-blue.svg)]() [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/) [![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/) @@ -1095,8 +1095,8 @@ This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) ## 📝 Changelog -See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.11.5). +See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.11.6). --- -*Project: ObsiGate | Version: 2.11.5 | Last updated: May 2026* +*Project: ObsiGate | Version: 2.11.6 | Last updated: May 2026* diff --git a/VERSION b/VERSION index 497a78c..03b7bd6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.11.5 +2.11.6 diff --git a/backend/main.py b/backend/main.py index ec3596b..ce73b74 100644 --- a/backend/main.py +++ b/backend/main.py @@ -702,7 +702,7 @@ class SecurityHeadersMiddleware(BaseHTTPMiddleware): response.headers["Content-Security-Policy"] = ( "default-src 'self'; " "script-src 'self' 'unsafe-inline' blob: https://cdnjs.cloudflare.com https://unpkg.com https://esm.sh https://cdn.jsdelivr.net https://static.cloudflareinsights.com; " - "style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://fonts.googleapis.com https://cdn.jsdelivr.net; " + "style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://fonts.googleapis.com https://cdn.jsdelivr.net https://esm.sh; " "img-src 'self' data: blob:; " "connect-src 'self' blob: https://esm.sh https://unpkg.com https://cdnjs.cloudflare.com https://fonts.googleapis.com https://fonts.gstatic.com https://cdn.jsdelivr.net; " "font-src 'self' data: https://fonts.gstatic.com https://esm.sh; " diff --git a/desktop/Cargo.lock b/desktop/Cargo.lock index d687935..57b374c 100644 --- a/desktop/Cargo.lock +++ b/desktop/Cargo.lock @@ -2626,7 +2626,7 @@ dependencies = [ [[package]] name = "obsigate-desktop" -version = "2.11.5" +version = "2.11.6" dependencies = [ "chrono", "env_logger", diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index b1ec994..242d961 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "obsigate-desktop" -version = "2.11.5" +version = "2.11.6" description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian" authors = ["Bruno Charest"] edition = "2021" diff --git a/desktop/tauri.conf.json b/desktop/tauri.conf.json index 3278b2a..100f855 100644 --- a/desktop/tauri.conf.json +++ b/desktop/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json", "productName": "ObsiGate", - "version": "2.11.5", + "version": "2.11.6", "identifier": "com.obsigate.desktop", "build": { "frontendDist": "../frontend", diff --git a/docs/ISSUES_TODOLIST.md b/docs/ISSUES_TODOLIST.md index 0d2cee5..c9616d8 100644 --- a/docs/ISSUES_TODOLIST.md +++ b/docs/ISSUES_TODOLIST.md @@ -172,6 +172,8 @@ Avant de corriger quoi que ce soit, un agent IA doit : | *BUG-061* | [🟡 IMPORTANT] Assistant IA : le bouton « Plein écran » n'agrandit plus le panneau | 🟢 corrigé | P2 | 📱 frontend | IA | `frontend/style.css`, `tests/frontend/ai.test.mjs` | Ouvrir l'assistant, redimensionner le panneau, puis cliquer « Plein écran » | La largeur du panneau est écrite en ligne par la poignée de redimensionnement / la largeur persistée (`localStorage`) ; l'inline l'emportait sur `.bookslm-panel.fullscreen { width: 100vw }`. Ajout de `!important` sur la règle plein écran. Tests : `ai.test.mjs` (+1 : classe basculée + règle CSS). Vérifié Playwright : 640 px → 1400 px (viewport) | | *BUG-062* | [🟡 IMPORTANT] Viewer PDF : le document ne prend pas toute la largeur quand la navigation est masquée | 🟢 corrigé | P2 | 📱 frontend | IA | `frontend/style.css`, `tests/frontend/pdf-viewer.test.mjs`, `tests/e2e/pdf-viewer.spec.js` | Ouvrir un PDF puis masquer la barre de navigation gauche | La règle `.sidebar.hidden ~ .content-wrapper .content-area { max-width: 1200px }` (colonne de lecture centrée) s'appliquait aussi aux viewers plein cadre. Ajout de `.content-area:has(.pdf-viewer-container)` (et `.image-viewer-container`) avec `max-width: none; margin: 0`. Test E2E : `max-width` calculé = `none`, conteneur = largeur du contenu | | *BUG-063* | [🟡 IMPORTANT] Viewer PDF : la table des matières s'affiche mais ne navigue pas | 🟢 corrigé | P1 | 📱 frontend | IA | `frontend/js/viewer.js`, `tests/frontend/pdf-viewer.test.mjs`, `tests/e2e/pdf-viewer.spec.js` (fixture `test_vault/sample-pdf-toc.pdf`) | Ouvrir un PDF avec signets, puis cliquer une entrée de la TOC | Deux causes : (1) `contentWindow.location.hash='page=N'` n'atteint pas le document (lecteur PDF natif dans une fenêtre `about:blank`) ; (2) un simple changement de fragment sur `iframe.src` est une navigation same-document **ignorée** par le lecteur natif. `navigatePdfToPage()` (liens `data-page` + listeners, plus d'`onclick` inline) recharge réellement l'iframe via un paramètre de query qui change (`&_pdfpage=#page=N`). Test E2E : `src` finit par `&_pdfpage=#page=3`. Vérifié en Chrome *headful* : page 1 → page 8 → page 1 (captures identiques au retour) | Le fragment seul ne suffisait pas : Chrome applique `#page=N` au **chargement**, pas lors d'un changement de fragment | +| *BUG-064* | [🟡 IMPORTANT] Éditeur Excalidraw : le diagramme ne s'affiche jamais (canvas vide), pour tout fichier `.excalidraw` / `.excalidraw.md` | 🟢 corrigé | P1 | 📱 frontend + ⚙️ backend | IA | `frontend/excalidraw-editor.html`, `backend/main.py`, `tests/frontend/excalidraw-viewer.test.mjs`, `tests/test_security_hardening.py`, `tests/e2e/excalidraw.spec.js`, `test_vault/diagram-app-export.excalidraw` | Ouvrir un `.excalidraw` (ou `.excalidraw.md`) dans ObsiGate | Deux causes : (1) la feuille de style d'Excalidraw n'était jamais chargée → éditeur non stylisé + `.excalidraw` sans hauteur fixe → boucle de resize jusqu'au plafond `2^25` (33 554 432 px) → scène blanche. Correctif : `` CSS depuis esm.sh + `style-src` CSP autorisant `https://esm.sh`. (2) `appState.collaborators` objet JSON → `collaborators.forEach is not a function` ; `sanitizeAppState()` reconvertit en `Map` et écarte `width/height/offsetLeft/offsetTop`. | Vérifié navigateur : hauteur canvas 525 px (avant 33 554 432), dessin affiché, UI stylisée, 0 erreur. E2E + tests statiques CSP/CSS ajoutés. | +| *BUG-065* | [🟡 IMPORTANT] Éditeur Excalidraw : l'auto-save recharge la page en pleine édition | 🟢 corrigé | P1 | 📱 frontend | IA | `frontend/js/excalidraw-viewer.js`, `frontend/js/utils.js`, `frontend/excalidraw-editor.html`, `tests/frontend/excalidraw-viewer.test.mjs` | Ouvrir un `.excalidraw` puis modifier un élément : au bout de 2 s la vue se recharge | Chaque modification déclenchait un `PUT save` 2 s plus tard → SSE `index_updated` → `reloadExternalWrite` → `openFile` → **recréation de l'iframe** (refresh visible). Auto-save supprimée : sauvegarde explicite (bouton 💾 / Ctrl+S). `reloadExternalWrite` ignore le fichier si un iframe Excalidraw est ouvert (`iframe[data-excalidraw-vault/path]`). Le badge « Modified » ne réagit plus aux changements d'`appState` (resize/zoom) mais à la signature des éléments. | Vérifié Playwright : plus de refresh, badge stable après bascule plein écran. Test statique (absence de `requestSave`/`saveTimer`). | | | | | | | | | | | | | ### TODOs techniques (améliorations / nouvelles tâches) @@ -238,6 +240,10 @@ Avant de corriger quoi que ce soit, un agent IA doit : | 2026-09-17 | BUG-035, BUG-036, BUG-037, BUG-038, BUG-039, BUG-040 | Correction | `backend/secret_redactor.py`, `backend/collab.py`, `backend/auth/{middleware,password,router}.py`, `backend/indexer.py`, `backend/main.py`, `tests/test_api_main.py`, `tests/test_auth.py`, `tests/test_auth_api.py`, `tests/test_collab.py`, `tests/test_pdf.py`, `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md` | **Lot de 6 bugs mineurs (P2/P3)** : BUG-035 masquage hex conditionné au contexte (git/SHA épargnés) ; BUG-036 jeton WebSocket cookie-only (plus de `?token=`) + plafond de trame 16 Mio ; BUG-037 garde-fou au démarrage (refus d'un bind public sans auth sauf `OBSIGATE_ALLOW_INSECURE=true`) ; BUG-038 Argon2 recalibré 19 Mio/t=2/p=1 ; BUG-039 login uniforme 401 (fini 429/403 distinctifs) ; BUG-040 extraction PDF différée via `enrich_pdf_texts()`. Vérifié : pytest 1204 passed / 6 skipped, ruff 0, mypy 0 (77 fichiers), frontend validate-imports 38 modules + unit 9/9. | 🟢 corrigé (en attente vérif utilisateur) | | 2026-09-17 | BUG-061, BUG-062, BUG-063 | Correction | `frontend/style.css`, `frontend/js/viewer.js`, `tests/frontend/ai.test.mjs`, `tests/frontend/pdf-viewer.test.mjs`, `tests/e2e/pdf-viewer.spec.js`, `test_vault/sample-pdf-toc.pdf` (nouveau), `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md` | **Viewer PDF & assistant IA** : BUG-061 le bouton plein écran du panneau assistant l'emportait mal sur la largeur inline (redimensionnement/persistée) → `width: 100vw !important` ; BUG-062 le plafond de lecture 1200 px s'appliquait au PDF quand la navigation était masquée → `:has(.pdf-viewer-container)` en `max-width:none` ; BUG-063 la TOC PDF ne naviguait pas (`contentWindow` = `about:blank`) → `navigatePdfToPage()` recharge l'iframe avec `#page=N`, liens `data-page` sans `onclick` inline. Vérifié : `ai.test.mjs` 93/93, `pdf-viewer.test.mjs` 8/8, validate-imports 38 modules (311 exports), unit 9/9, E2E `pdf-viewer.spec.js` 3/3, et Playwright sur l'instance de test (plein écran 640→1400 px, `src` → `#page=3`, `max-width:none`). | 🟢 corrigé (en attente vérif utilisateur) | | 2026-09-17 | BUG-063 (complément) | Correction | `frontend/js/viewer.js`, `tests/frontend/pdf-viewer.test.mjs`, `tests/e2e/pdf-viewer.spec.js`, `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md` | **BUG-063 non résolu au premier correctif** : définir `iframe.src = base + '#page=N'` ne change que le fragment → navigation same-document que le lecteur PDF natif ignore. Diagnostic en Chrome *headful* (comparaison de captures) : fragment présent au chargement = OK ; changement de fragment après chargement = aucun effet ; changement de query + fragment = OK. `navigatePdfToPage()` ajoute donc un paramètre de query horodaté (`&_pdfpage=#page=N`) pour forcer un vrai rechargement. Vérifié via l'UI de l'app (Chrome headful) : page 1 → page 8 → retour page 1 (hash de capture identique au retour). Tests : `pdf-viewer.test.mjs` 8/8, E2E `pdf-viewer.spec.js` 3/3. | 🟢 corrigé (en attente vérif utilisateur) | +| 2026-09-17 | BUG-064 | Correction | `frontend/excalidraw-editor.html`, `backend/main.py`, `tests/frontend/excalidraw-viewer.test.mjs`, `tests/test_security_hardening.py`, `tests/e2e/excalidraw.spec.js`, `test_vault/diagram-app-export.excalidraw` (nouveau), `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md` | **BUG-064** : aucun diagramme Excalidraw ne s'affichait (canvas vide). Diagnostic navigateur : `.excalidraw` sans hauteur fixe → boucle de redimensionnement 525 → 56 181 → **33 554 432 px** (`2^25`, plafond Excalidraw) ; canvas de 33 Mpx impossible à dessiner → scène blanche. **Cause 1** : la feuille de style `@excalidraw/excalidraw` n'était jamais chargée (seuls 18 règles CSS présentes, toutes ObsiGate) — l'éditeur était non stylisé. Correctif : `` + `https://esm.sh` ajouté à `style-src` de la CSP. **Cause 2** : `appState.collaborators` (Map sérialisée en objet JSON par l'app/plugin) faisait planter Excalidraw 0.18 (`collaborators.forEach is not a function`) ; `sanitizeAppState()` reconvertit en `Map` et écarte la géométrie de viewport importée (`width/height/offsetLeft/offsetTop`). Vérifié Playwright sur l'instance de test (port 2020) : hauteur canvas 525 px, rectangle + losange affichés, UI stylisée, 0 `pageerror`. Tests : `excalidraw-viewer.test.mjs` 8/8 (dont 3 nouveaux), `TestCspExcalidrawStylesheet` (pytest), E2E (hauteur de canvas bornée). | 🟢 corrigé (en attente vérif utilisateur) | +| 2026-09-17 | BUG-064 (complément) | Correction | `frontend/style.css`, `tests/frontend/excalidraw-viewer.test.mjs`, `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md` | **BUG-064 (complément)** : quand la barre de navigation gauche est masquée, le viewer Excalidraw restait borné à la colonne de lecture centrée de 1200 px. La règle `.sidebar.hidden ~ .content-wrapper .content-area { max-width: 1200px }` s'appliquait au viewer comme aux notes. Ajout de `.content-area:has(iframe[src*="excalidraw-editor.html"])` en `max-width: none; margin: 0` (même traitement que les viewers PDF/image, BUG-062). Vérifié Playwright (viewport 1400 px) : contenu 1115 → 1400 px, iframe 1035 → 1320 px, `max-width` calculé `none`. Test statique ajouté (`excalidraw-viewer.test.mjs` 9/9). | 🟢 corrigé (en attente vérif utilisateur) | +| 2026-09-17 | BUG-065, #78 (complément) | Correction + feature | `frontend/js/excalidraw-viewer.js`, `frontend/js/utils.js`, `frontend/excalidraw-editor.html`, `tests/frontend/excalidraw-viewer.test.mjs`, `docs/features/excalidraw.md`, `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md` | **BUG-065** : l'auto-save Excalidraw (débounce 2 s) déclenchait `PUT save` → SSE `index_updated` → `reloadExternalWrite` → `openFile` → recréation de l'iframe = refresh visible pendant le dessin. Auto-save retirée (`excalidraw-viewer.js` : plus de `requestSave`/`saveTimer`), sauvegarde explicite (bouton 💾 / Ctrl+S) ; `reloadExternalWrite` (utils.js) court-circuite le re-rendu si un iframe Excalidraw est ouvert sur ce fichier (attributs `data-excalidraw-vault`/`data-excalidraw-path`) ; le badge « Modified » suit désormais une signature des éléments (`id:versionNonce`) au lieu de tout `onChange` — resize/zoom/plein écran ne marquent plus le fichier modifié. **#78 (complément)** : bouton **plein écran** `#btn-fullscreen` dans la barre d'outils de l'éditeur (`requestFullscreen` sur le document de l'iframe) + iframe créée avec `allow="fullscreen" allowfullscreen`. Vérifié Playwright : bascule plein écran OK (`document.fullscreenElement` true→false), badge non modifié après bascule ; tests statiques `excalidraw-viewer.test.mjs` 12/12, validate-imports 38 modules, unit 9/9. | 🟢 corrigé (en attente vérif utilisateur) | +| 2026-09-17 | #78 (complément) | UI | `frontend/excalidraw-editor.html`, `docs/features/excalidraw.md`, `CHANGELOG.md` | **#78 (complément)** : la barre d'outils de l'éditeur Excalidraw passe en **colonne d'icônes** (34×34 px, SVG seuls), **collée au bord droit** (`right: 0` ; `top: 45%` ; empilement vertical), avec `title`/`aria-label`. L'icône du bouton Save est remplacée par une coche pendant 1,2 s après une sauvegarde réussie. Badge « Modifié » réduit à une pastille. Vérifié Playwright : bord droit au bord de l'iframe, haut 45 %, 4 boutons empilés ; bascule plein écran OK, cycle d'icône Save + `PUT save` observés. | 🟢 livré (en attente vérif utilisateur) | --- diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index eb0e198..639be3d 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -1,6 +1,6 @@ # ObsiGate — Roadmap -> **Version :** 2.11.5 | **Dernière mise à jour :** 2026-09-17 +> **Version :** 2.11.6 | **Dernière mise à jour :** 2026-09-18 > **Ce fichier ne contient que le travail à venir** (🔵 En cours + ⚪ Backlog) et un index compact > vers les fonctionnalités livrées. > - **Méthode de livraison à appliquer pour toute tâche : [DELIVERY_WORKFLOW.md](./DELIVERY_WORKFLOW.md)** @@ -186,6 +186,7 @@ | 102 | Assistant IA — bouton « Ajouter la section » par bloc de code (insertion du bloc seul) | 2.9.0 | [archive](./archive/COMPLETED_v1-v2.md) | | 92 | Assistant IA — Écosystème d'outils phase 2 (recherche à clé, cache/retry, Playwright, crawl, Gitea/GitHub, documents XLSX/DOCX/CSV/PDF) | 2.10.0 | [features/ai-tools-roadmap.md](./features/ai-tools-roadmap.md) | | 103 | Configuration — clés utilisateur des sources connectées & recherche à clé (page Configurations, `data/api_keys.json`, priorité sur l'env) | 2.11.0 | [features/ai-tools-roadmap.md](./features/ai-tools-roadmap.md) | +| 104 | Configuration — Redesign UI de la section « Clés API IA » : recherche fournisseurs, carte défaut 2 colonnes + badges de capacités, cartes dépliables, footer d'actions sticky | 2.12.0 | [features/ai-keys-ui.md](./features/ai-keys-ui.md) | --- @@ -193,7 +194,7 @@ | Priorité | Items | Effort total estimé | |---|---|---| -| ✅ Complété | #1 → #59, #61–72, #74–76, #78–84, #88–93, #94–100, #102, #92 | ~114 jours réalisés | +| ✅ Complété | #1 → #59, #61–72, #74–76, #78–84, #88–93, #94–100, #102–104, #92 | ~115 jours réalisés | | 🔵 P2 restant | #77 Desktop : 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 | | ⚪ P0/P1 restant | #85-87 Refonte architecturale, performance, CI/CD (BUG-035 → BUG-040 corrigés) | ~15-23 jours | diff --git a/docs/features/excalidraw.md b/docs/features/excalidraw.md index 7c46537..6b6d673 100644 --- a/docs/features/excalidraw.md +++ b/docs/features/excalidraw.md @@ -1,6 +1,6 @@ # #78 — Éditeur Excalidraw — Ouverture et édition de fichiers .excalidraw -> **Statut :** ✅ Terminé (2026-09-10 — éditeur iframe complet, détection, création, autosave, support `.excalidraw.md`, B5 extraction texte pour la recherche, C8 création via menu contextuel, F3 E2E `tests/e2e/excalidraw.spec.js`, doc H1-H3. F2 non retenu. BUG-002 corrigé) +> **Statut :** ✅ Terminé (2026-09-10 — éditeur iframe complet, détection, création, autosave, support `.excalidraw.md`, B5 extraction texte pour la recherche, C8 création via menu contextuel, F3 E2E `tests/e2e/excalidraw.spec.js`, doc H1-H3. F2 non retenu. BUG-002 et BUG-064 corrigés. 2026-09 : A9 bouton **plein écran** ajouté, auto-save retirée au profit d'une sauvegarde explicite (BUG-065)) > **Effort :** 3-4 jours | **Impact :** 🟡 > **Références :** [Roadmap](../ROADMAP.md) · [Changelog — 2.2.0](../../CHANGELOG.md) @@ -56,17 +56,18 @@ ``` - [x] **A5. Rendu du composant** : Monter `` dans le conteneur avec les `initialData` reçues. Configurer les callbacks `onChange` pour détecter les modifications. - - [x] **A6. Barre d'outils minimaliste** (dans l'iframe, superposée en haut à droite) : - - Bouton « 💾 Sauvegarder » → envoie les données au parent - - Badge « Modifié » (disparaît après sauvegarde) - - Indicateur de thème 🌙/☀️ - - Optionnel : bouton « Export PNG » et « Export SVG » (natif Excalidraw) + - [x] **A6. Barre d'outils minimaliste** (dans l'iframe ; depuis 2026-09 : **colonne d'icônes** collée au bord droit (`right: 0`), début à `45%` de la hauteur, empilement vertical) : + - Bouton « Sauvegarder » (icône disquette → coche après sauvegarde) → envoie les données au parent + - Boutons « Export PNG » (icône image) et « Export SVG » (icône vectorielle) — infobulles au survol + - Bouton plein écran (A9) + - Badge « Modifié » réduit à une pastille au-dessus des boutons - [x] **A7. Communication postMessage** : - Réception : écouter `message` → si `type === "init"`, charger `data.elements` + `data.appState` + `data.files` dans l'état Excalidraw. Si `type === "theme"`, basculer `theme` (dark/light). - Émission : `postMessage({type: "save", data: {elements, appState, files}}, "*")` quand l'utilisateur sauvegarde. - Émission : `postMessage({type: "ready"}, "*")` au chargement pour signaler que l'iframe est prête. - Émission : `postMessage({type: "modified", dirty: true/false}, "*")` pour l'indicateur de modification. - [x] **A8. Gestion des erreurs** : Si les données sont invalides (JSON corrompu, pas un fichier Excalidraw), afficher un message d'erreur stylisé dans l'iframe. + - [x] **A9. Bouton plein écran** (ajouté 2026-09) : bouton `#btn-fullscreen` dans la barre d'outils de l'iframe → `document.documentElement.requestFullscreen()` (l'iframe parent est créée avec `allow="fullscreen" allowfullscreen`) ; l'icône bascule entrer/sortir via `fullscreenchange`. La feuille de style Excalidraw étant chargée, le canvas suit le redimensionnement. Test statique : `tests/frontend/excalidraw-viewer.test.mjs`. ## B. Backend — Détection et API (0.5 jour) - [x] **B1. Ajout à `SUPPORTED_EXTENSIONS`** : Ajouter `.excalidraw` dans `backend/indexer.py:56` pour que les fichiers apparaissent dans l'arborescence et soient indexés. @@ -100,7 +101,7 @@ - Pour les fichiers `.excalidraw` : remplacer « Éditer (Forge) » par « Ouvrir dans Excalidraw.com » (lien externe, nouvel onglet) - Garder « Télécharger » (.excalidraw) et « pop-out » - Badge « Excalidraw » avec icône `pen-tool` - - [x] **C4. Auto-save** : Débounce 2 secondes après la dernière modification dans l'iframe → sauvegarde automatique silencieuse (comme l'éditeur markdown #29). L'iframe émet `modified` → le parent démarre un timer → au bout de 2s sans nouvelle modification → `postMessage({type: "requestSave"})` → l'iframe répond avec `save` → le parent écrit via l'API. + - [x] **C4. Sauvegarde explicite uniquement** (modifié 2026-09 : l'auto-save a été **retirée**, BUG-065) : l'iframe émet `modified` → le badge « Modified » s'affiche, mais **aucune sauvegarde automatique** n'est déclenchée. La sauvegarde se fait par le bouton « 💾 Save » de l'iframe ou `Ctrl+S`. Raison : chaque écriture déclenche l'événement SSE `index_updated`, qui re-rendait la vue et **rechargeait l'iframe** (refresh visible en pleine édition). - [x] **C5. Raccourci Ctrl+S** : L'iframe intercepte Ctrl+S → envoie `save` au parent → le parent sauvegarde → confirmation visuelle (toast « Excalidraw sauvegardé »). - [x] **C6. Compatibilité Split View (#75)** : L'iframe s'affiche dans le content-area du panneau actif. Le `PaneTabManager` gère le cache : quand on switch d'onglet, l'état de l'iframe est préservé (elle reste dans le DOM, juste masquée). Plusieurs iframes Excalidraw peuvent coexister dans différents panneaux. - [x] **C7. Création via la modale « Nouveau fichier »** : Dans `frontend/js/ui.js`, fonction `showCreateFileModal()` : @@ -141,6 +142,8 @@ - **Taille du bundle** : React + ReactDOM + Excalidraw ≈ 2.5 Mo minifié. Chargé depuis `esm.sh` (CDN global, cache HTTP). L'impact n'est perceptible qu'à la première ouverture d'un `.excalidraw`. Solution : précharger l'iframe en arrière-plan (``) après le chargement de l'app. - **Performance React dans iframe** : React dans une iframe fonctionne parfaitement — c'est un contexte JavaScript indépendant. Testé sur Chrome, Firefox, Safari, Edge. - **CORS et esm.sh** : Les modules ESM depuis `esm.sh` sont servis avec les headers CORS appropriés. L'iframe est same-origin (`/frontend/excalidraw-editor.html`) donc pas de problème. + - **Compatibilité des exports de l'app Excalidraw** (BUG-064) : `appState.collaborators` est une `Map` qu'Excalidraw sérialise en objet JSON (`{}`) ; elle doit être reconvertie en `Map` (`sanitizeAppState()` dans `frontend/excalidraw-editor.html`) avant `initialData`, sinon Excalidraw 0.18 plante (`collaborators.forEach is not a function`). La géométrie de viewport (`width`, `height`, `offsetLeft`, `offsetTop`) est également écartée : ce sont des valeurs mesurées côté fenêtre source, qu'Excalidraw recalcule. Couvert par un test E2E (`diagram-app-export.excalidraw`). + - **Feuille de style Excalidraw obligatoire** (BUG-064) : `@excalidraw/excalidraw` n'injecte pas son CSS automatiquement — il faut le charger explicitement (`` vers `…/@excalidraw/excalidraw@0.18.0/dist/prod/index.css`). Sans lui, l'éditeur est non stylisé **et** `.excalidraw` n'a pas de hauteur fixe, ce qui déclenche une boucle de redimensionnement jusqu'au plafond `2^25` (33 554 432 px) : le canvas devient indessinable et la scène reste blanche. Le CDN `esm.sh` doit donc figurer dans `style-src` de la CSP (`backend/main.py`). Garde-fous : `tests/frontend/excalidraw-viewer.test.mjs` et `TestCspExcalidrawStylesheet`. - **Mises à jour d'Excalidraw** : La version est épinglée (`@0.18.0`). Pour mettre à jour, changer le numéro dans le HTML + tester. Le format de données `.excalidraw` est stable (v2 depuis 2021). - **Sécurité postMessage** : Vérifier `event.origin` dans les deux sens. L'iframe n'accepte que les messages de `window.parent`. Le parent n'accepte que les messages de l'iframe connue. Pas de `"*"` en production. - **Tauri Desktop (#77)** : L'iframe se charge depuis le filesystem local (`tauri://localhost/frontend/excalidraw-editor.html`). Les imports ESM depuis `esm.sh` fonctionnent si le réseau est disponible. Pour le mode offline, bundler Excalidraw dans l'app desktop (à traiter dans #77, pas ici). diff --git a/frontend/excalidraw-editor.html b/frontend/excalidraw-editor.html index 1c15024..1a1db60 100644 --- a/frontend/excalidraw-editor.html +++ b/frontend/excalidraw-editor.html @@ -7,34 +7,48 @@ Excalidraw Editor + +