From 94ea5909f4b07b4f3af543045e279c957acbd414 Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Thu, 17 Sep 2026 09:19:03 -0400 Subject: [PATCH] fix(forge): le parent quitte aussi le plein ecran avant l'assistant IA (BUG-056) --- CHANGELOG.md | 19 +++++++++++++------ README.fr.md | 6 +++--- README.md | 6 +++--- VERSION | 2 +- desktop/Cargo.lock | 2 +- desktop/Cargo.toml | 2 +- desktop/tauri.conf.json | 2 +- docs/ISSUES_TODOLIST.md | 4 ++-- docs/ROADMAP.md | 2 +- frontend/js/sync.js | 25 ++++++++++++++++++++----- package.json | 2 +- tests/frontend/editor-inline.test.mjs | 8 ++++++++ 12 files changed, 55 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a51f5c..9c556b9 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.8.3**. +> [Unreleased](#unreleased). La dernière version livrée est **2.8.4**. --- @@ -14,17 +14,24 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html). --- +## [2.8.4] — 2026-09-17 + +--- + ## [2.8.3] — 2026-09-17 ### Corrigé - **BUG-056 - Éditeur Forge en plein écran : l'Assistant IA s'ouvre en arrière-plan** : le panneau de l'assistant est monté dans le document parent, alors que le plein écran - Forge porte sur le document de l'iframe — l'API Fullscreen n'affichant que l'élément - plein écran et ses descendants, le panneau restait invisible. Le bouton « Assistant IA » - (et `Ctrl+J`) quitte désormais le plein écran avant d'ouvrir le panneau, la demande étant - envoyée au parent une fois la sortie effective. Fichier : `frontend/editor-poc.html`. - Test : `tests/frontend/forge-completion.test.mjs` (+1). + Forge porte sur l'iframe — l'API Fullscreen n'affichant que l'élément plein écran et ses + descendants, le panneau restait invisible. Le plein écran est désormais quitté avant + d'ouvrir le panneau : côté **parent** (`sync.js`, sur `forge-open-ai` — le plein écran + peut appartenir au document parent et non à l'iframe) **et** côté iframe + (`editor-poc.html`, `openAssistant`), la demande d'ouverture étant émise une fois la + sortie effective. Fichiers : `frontend/editor-poc.html`, `frontend/js/sync.js`. + Tests : `tests/frontend/forge-completion.test.mjs` (+1), + `tests/frontend/editor-inline.test.mjs` (+1). --- diff --git a/README.fr.md b/README.fr.md index ec7ac9e..4c3f4d3 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.8.3-blue.svg)]() +[![Version](https://img.shields.io/badge/Version-2.8.4-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/) @@ -916,8 +916,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.8.3). +Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.8.4). --- -*Projet : ObsiGate | Version : 2.8.3 | Dernière mise à jour : Juin 2026* +*Projet : ObsiGate | Version : 2.8.4 | Dernière mise à jour : Juin 2026* diff --git a/README.md b/README.md index 9d78d38..d150a48 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.8.3-blue.svg)]() +[![Version](https://img.shields.io/badge/Version-2.8.4-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/) @@ -1085,8 +1085,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.8.3). +See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.8.4). --- -*Project: ObsiGate | Version: 2.8.3 | Last updated: May 2026* +*Project: ObsiGate | Version: 2.8.4 | Last updated: May 2026* diff --git a/VERSION b/VERSION index 9f8d8a9..2701a22 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.8.3 +2.8.4 diff --git a/desktop/Cargo.lock b/desktop/Cargo.lock index 4724d34..30d8fbf 100644 --- a/desktop/Cargo.lock +++ b/desktop/Cargo.lock @@ -2626,7 +2626,7 @@ dependencies = [ [[package]] name = "obsigate-desktop" -version = "2.8.3" +version = "2.8.4" dependencies = [ "chrono", "env_logger", diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 89a1b88..4736514 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "obsigate-desktop" -version = "2.8.3" +version = "2.8.4" 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 a0c3a62..ec4a3de 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.8.3", + "version": "2.8.4", "identifier": "com.obsigate.desktop", "build": { "frontendDist": "../frontend", diff --git a/docs/ISSUES_TODOLIST.md b/docs/ISSUES_TODOLIST.md index e37b86e..5563270 100644 --- a/docs/ISSUES_TODOLIST.md +++ b/docs/ISSUES_TODOLIST.md @@ -165,7 +165,7 @@ Avant de corriger quoi que ce soit, un agent IA doit : | *BUG-053* | [🟡 IMPORTANT] Assistant IA (mode agent) : le fichier demandé n'est pas créé — le modèle émet un bloc texte `obsigate-action` au lieu d'appeler l'outil `create_file` | 🟢 corrigé | P1 | ⚙️ backend + 🤖 ia | IA | `backend/bookslm.py`, `backend/bookslm_routes.py`, `tests/test_bookslm.py` | Mode agent, contexte Général (ou dossier vide) : « créer le fichier TestVault/sport/… avec le tableau des 84 matchs » → réponse avec un bloc ```obsigate-action``` tronqué, aucun fichier | `backend/bookslm.py` : protocole d'action scindé — `GENERAL_ACTION_TOOL_PROTOCOL` (outils natifs, interdiction des blocs `obsigate-action`) utilisé quand `agent=True`, protocole texte conservé pour le chat classique ; `backend/bookslm_routes.py` : `_resolve_system_prompt(..., agent=True)` depuis l'endpoint agent + règle « Mode agent » pour les prompts dossier/documents, `max_tokens` agent 4096 → 8192 (contenu de fichier complet) | Cause : le prompt Général enseignait encore le protocole texte alors que l'agent dispose du function calling. Tests : `tests/test_bookslm.py` (+3) | | *BUG-054* | [🟡 IMPORTANT] Éditeur « Editer » : le bouton Sauvegarder reste bloqué sur le spinner de chargement (retour au crochet uniquement après un refresh complet) | 🟢 corrigé | P1 | 📱 frontend | IA | `frontend/js/utils.js` | Ouvrir un fichier → Editer → cliquer Sauvegarder (ou Ctrl+S) ; rouvrir l'éditeur : le bouton reste un spinner désactivé | Nouveau helper `resetSaveButton()` (crochet `✓` + `disabled=false` + styles en ligne nettoyés) appelé à l'ouverture (`openEditor`), à la fermeture (`closeEditor`) et en cas d'échec (`saveFile`). Tests : `tests/frontend/editor-inline.test.mjs` (+4) | Le nœud `#editor-save` est partagé entre sessions : l'état « spinner + désactivé » posé par une sauvegarde manuelle n'était jamais remis à zéro (succès → fermeture puis réouverture, Forge, ou échec réseau dans le `catch`). Seul un rechargement de `index.html` restaurait le crochet | | *BUG-055* | [🟡 IMPORTANT] Éditeur Forge : l'autocomplétion (Tab) ajoute des espaces parasites, l'effacement détruit le mot complété et la complétion fantôme est illisible | 🟢 corrigé | P1 | 📱 frontend + ⚙️ backend | IA | `frontend/editor-poc.html`, `frontend/js/autocomplete.js`, `backend/ai.py`, `.gitea/workflows/ci.yml`, `tests/frontend/forge-completion.test.mjs` (nouveau) | Forge : taper un mot, puis Tab pour compléter ; un espace (voire deux) s'insère avant le mot complété, et le retour arrière efface l'ajout. La prédiction IA s'affichait décalée (texte miroir du document entier) | **Cause** : trois gestionnaires `keydown` Tab indépendants s'exécutaient tous — l'indentation (`insertAtCursor(' ')`) s'ajoutait à la complétion de mot et à l'acceptation du ghost. **Correctif** : gestion **unifiée** de Tab (`liste ouverte > ghost > mot du document > indentation`, une seule action), helpers purs partagés (`getWordFragment`, `findWordCompletions`, `normalizeGhost`, `chooseTabAction`) dans `autocomplete.js`, liste déroulante si plusieurs candidats, dropdown positionné au curseur, ghost **positionné au curseur** (fini le miroir du document, nettoyé au déplacement/scroll), complétion de mot sans espace garanti (`normalizeGhost` tronque au premier espace) et prompt `/api/ai/inline-complete` simplifié. Tests : `tests/frontend/forge-completion.test.mjs` (28). | Cause du bug : l'indentation Tab n'était pas conditionnée à l'absence de suggestion. Le ghost re-rendait tout le texte transparent + prédiction, d'où l'impression d'espaces et les erreurs d'effacement | -| *BUG-056* | [🟡 IMPORTANT] Éditeur Forge en plein écran : l'Assistant IA s'ouvre en arrière-plan et reste invisible | 🟢 corrigé | P1 | 📱 frontend | IA | `frontend/editor-poc.html`, `tests/frontend/forge-completion.test.mjs` | Forge : passer en plein écran puis cliquer le bouton « Assistant IA » (ou `Ctrl+J`) — le panneau s'ouvre dans le document parent, masqué par l'iframe plein écran | `openAssistant()` quitte d'abord le plein écran (`document.exitFullscreen()`) puis poste `forge-open-ai` une fois la sortie effectuée (`promise.then`) ; l'assistant s'affiche alors réellement | Le panneau assistant est monté dans `document.body` du parent : l'API Fullscreen ne rend que l'élément plein écran et ses descendants, donc il ne peut pas s'afficher au-dessus de l'iframe Forge en plein écran. Tests : `tests/frontend/forge-completion.test.mjs` (+1) | +| *BUG-056* | [🟡 IMPORTANT] Éditeur Forge en plein écran : l'Assistant IA s'ouvre en arrière-plan et reste invisible | 🟢 corrigé | P1 | 📱 frontend | IA | `frontend/editor-poc.html`, `frontend/js/sync.js`, `tests/frontend/forge-completion.test.mjs`, `tests/frontend/editor-inline.test.mjs` | Forge : passer en plein écran puis cliquer le bouton « Assistant IA » (ou `Ctrl+J`) — le panneau s'ouvre dans le document parent, masqué par l'iframe plein écran | Sortie du plein écran **avant** d'ouvrir le panneau, des deux côtés : côté iframe (`openAssistant` → `document.exitFullscreen()` puis `postMessage` à la résolution) **et** côté parent (`sync.js` sur `forge-open-ai` → `document.exitFullscreen()` puis `openForCurrentContext()`), car le plein écran peut être détenu par le document parent et non par l'iframe (dans ce cas `document.fullscreenElement` est nul dans l'iframe et sa sortie échoue). Tests : `forge-completion.test.mjs` (+1), `editor-inline.test.mjs` (+1) | Le panneau assistant est monté dans `document.body` du parent : l'API Fullscreen ne rend que l'élément plein écran et ses descendants, donc il ne peut pas s'afficher au-dessus de l'iframe Forge en plein écran. La sortie côté iframe seule ne suffisait pas quand le parent détient le plein écran | | | | | | | | | | | | | ### TODOs techniques (améliorations / nouvelles tâches) @@ -224,7 +224,7 @@ Avant de corriger quoi que ce soit, un agent IA doit : | 2026-09-17 | #101 | Feature | `frontend/editor-poc.html`, `frontend/js/sync.js`, `frontend/js/viewer.js`, `frontend/js/utils.js`, `frontend/index.html`, `frontend/style.css`, `frontend/locales/{fr,en}.json`, `tests/frontend/editor-inline.test.mjs`, `docs/features/forge-assistant.md` (nouvelle), `docs/ROADMAP.md`, `CHANGELOG.md` | **#101** : le bouton « AI Panel » de Forge ouvre désormais l'**Assistant IA** partagé (`postMessage forge-open-ai` → `bookslm.openForCurrentContext()`) au lieu du mini-chat isolé (supprimé) ; Forge lit `localStorage['obsigate_ai_picker']` (`aiPickerSelection()`) pour ses appels `/api/ai/*` et sa complétion fantôme (repli `ollama`), endpoints corrigés (`make-longer`/`make-shorter`, `target_lang`) ; bouton **plein écran** natif ajouté à Forge (iframe `allow="fullscreen"`) et à Editer (`#editor-fullscreen`, conteneur `#editor-container`, sortie à la fermeture, Échap laissé au navigateur) ; i18n `editor.fullscreen`/`editor.exit_fullscreen`. Vérifié : `tests/frontend/editor-inline.test.mjs` 40/40 (+10), unit 9/9, validate-imports 38 modules, 13 suites JSDOM vertes. | 🟢 livré (en attente vérif utilisateur) | | 2026-09-17 | BUG-055 | Correction | `frontend/editor-poc.html`, `frontend/js/autocomplete.js`, `backend/ai.py`, `.gitea/workflows/ci.yml`, `tests/frontend/forge-completion.test.mjs` (nouveau), `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md` | **BUG-055** : trois gestionnaires `keydown` Tab indépendants s'exécutaient à chaque appui — l'indentation (`insertAtCursor(' ')`) s'ajoutait à la complétion de mot (`insertAtCursor(suffixe)`) et à l'acceptation du ghost text, d'où l'espace parasite avant le mot complété puis un effacement destructeur. Gestion **unifiée** de Tab (`liste ouverte > ghost > mot du document > indentation`), helpers purs partagés (`getWordFragment`/`findWordCompletions`/`normalizeGhost`/`chooseTabAction`) extraits dans `autocomplete.js`, liste déroulante au curseur quand plusieurs mots correspondent, ghost **positionné au curseur** (plus de miroir du document entier, nettoyé au déplacement/scroll), complétion de mot sans espace garantie et prompt `/api/ai/inline-complete` simplifié (128 tokens). Vérifié : `tests/frontend/forge-completion.test.mjs` 28/28 (nouveau), `unit.test.mjs` 9/9, `editor-inline.test.mjs` 40/40, `ai.test.mjs` 88/88, validate-imports 38 modules, pytest 1101 passed / 6 skipped, ruff 0, mypy 0. | 🟢 corrigé (en attente vérif utilisateur) | | 2026-09-17 | BUG-055 (complément) | Correction | `frontend/editor-poc.html`, `frontend/js/utils.js`, `tests/frontend/forge-completion.test.mjs`, `tests/frontend/editor-inline.test.mjs`, `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md` | **BUG-055 (complément)** : une complétion acceptée au `Tab` disparaissait 1–2 s plus tard. Cause : l'auto-sauvegarde (2 s) déclenche un `index_updated` SSE sur le fichier affiché, et `reloadExternalWrite` rechargeait le tampon Forge **depuis le disque**, écrasant toute frappe postérieure à la sauvegarde. Le rechargement SSE est désormais ignoré si le tampon est modifié (`parent-reload` sans `force` + `isDirty` ; garde équivalente sur le point d'auto-sauvegarde CodeMirror) ; seul `obsigate:file-written` (assistant IA) passe `force=true`. L'auto-sauvegarde ne remet plus l'état « enregistré » si des modifications sont arrivées pendant la requête (Forge + CodeMirror), et `acceptGhost()` annule la requête de prédiction en attente. Vérifié : `forge-completion.test.mjs` 31/31 (+3), `editor-inline.test.mjs` 41/41 (+1), 14 suites frontend vertes, validate-imports 38 modules. | 🟢 corrigé (en attente vérif utilisateur) | -| 2026-09-17 | BUG-056 | Correction | `frontend/editor-poc.html`, `tests/frontend/forge-completion.test.mjs`, `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md` | **BUG-056** : en plein écran Forge, l'Assistant IA s'ouvrait en arrière-plan (panneau parent masqué par l'iframe plein écran). `openAssistant()` quitte désormais le plein écran (`document.exitFullscreen()`) avant de poster `forge-open-ai`, la notification étant envoyée à la résolution de la promesse de sortie. Vérifié : `forge-completion.test.mjs` 32/32 (+1), 14 suites frontend vertes, validate-imports 38 modules. | 🟢 corrigé (en attente vérif utilisateur) | +| 2026-09-17 | BUG-056 | Correction | `frontend/editor-poc.html`, `frontend/js/sync.js`, `tests/frontend/forge-completion.test.mjs`, `tests/frontend/editor-inline.test.mjs`, `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md` | **BUG-056** : en plein écran Forge, l'Assistant IA s'ouvrait en arrière-plan. La sortie du plein écran est désormais faite **côté iframe** (`openAssistant` → `document.exitFullscreen()` puis `postMessage` à la résolution) **et côté parent** (`sync.js` sur `forge-open-ai` → `document.exitFullscreen()` puis `openForCurrentContext()`), car le plein écran peut appartenir au document parent (l'iframe voit alors `fullscreenElement` nul et sa sortie échoue — c'était le cas non couvert par le premier correctif). Vérifié : `forge-completion.test.mjs` 32/32 (+1), `editor-inline.test.mjs` 42/42 (+1), 14 suites frontend vertes, validate-imports 38 modules. | 🟢 corrigé (en attente vérif utilisateur) | --- diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 9c0fe8c..709a84b 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -1,6 +1,6 @@ # ObsiGate — Roadmap -> **Version :** 2.8.3 | **Dernière mise à jour :** 2026-09-17 +> **Version :** 2.8.4 | **Dernière mise à jour :** 2026-09-17 > **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)** diff --git a/frontend/js/sync.js b/frontend/js/sync.js index 7f98b6d..5d53a1b 100644 --- a/frontend/js/sync.js +++ b/frontend/js/sync.js @@ -649,11 +649,26 @@ export function init() { // #101 — Forge has no rich AI panel of its own: its AI button asks the // parent to open the shared AI Assistant (same provider/model, skills, // context and history). - import('./bookslm.js').then(function(m) { - if (m && m.default) m.default.openForCurrentContext(); - }).catch(function(err) { - console.warn('[Forge] Unable to open AI assistant', err); - }); + var openForgeAssistant = function() { + import('./bookslm.js').then(function(m) { + if (m && m.default) m.default.openForCurrentContext(); + }).catch(function(err) { + console.warn('[Forge] Unable to open AI assistant', err); + }); + }; + // BUG-056 — the assistant panel is mounted in this document. The native + // fullscreen may be owned by the parent (Forge iframe) rather than by the + // iframe itself, so the parent must also leave fullscreen before the + // panel can be seen. + if (document.fullscreenElement && document.exitFullscreen) { + try { + document.exitFullscreen().catch(function() {}).then(openForgeAssistant, openForgeAssistant); + } catch (err) { + openForgeAssistant(); + } + } else { + openForgeAssistant(); + } } }); } diff --git a/package.json b/package.json index f26fc3d..190e792 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsigate", - "version": "2.8.3", + "version": "2.8.4", "description": "**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.", "main": "patch.js", "directories": { diff --git a/tests/frontend/editor-inline.test.mjs b/tests/frontend/editor-inline.test.mjs index 59141d7..a70ce7a 100644 --- a/tests/frontend/editor-inline.test.mjs +++ b/tests/frontend/editor-inline.test.mjs @@ -299,6 +299,14 @@ test("sync.js forge-close goes through the shared close path", () => { assert.match(handler[1], /closeEditor\(\);/); }); +test("sync.js leaves fullscreen before opening the Forge assistant (BUG-056)", () => { + const handler = syncSrc.match(/if \(e\.data\.type === 'forge-open-ai'\) \{([\s\S]*?)\n \}/); + assert.ok(handler, "forge-open-ai handler not found"); + assert.match(handler[1], /document\.fullscreenElement/); + assert.match(handler[1], /document\.exitFullscreen\(\)/); + assert.match(handler[1], /openForCurrentContext\(\)/); +}); + test("sync.js keeps an open edition session alive on external file changes", () => { const sse = syncSrc.match(/const changed = \(data\.changes \|\| \[\]\)([\s\S]*?)\n \}/); assert.ok(sse, "SSE index_updated refresh block not found");