diff --git a/CHANGELOG.md b/CHANGELOG.md index b160c25..a755228 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -251,6 +251,14 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Corrigé +- **BUG-016 — Mobile : le bouton « mode lecture » recouvre le bouton d'envoi de l'assistant** — + le bouton flottant `📖` (`#me-reading-btn`, `z-index: 890`) passait au-dessus du panneau + assistant plein écran sur mobile (`z-index: 100`) et masquait le bouton d'envoi `✈️`. Il + n'apparaît désormais que lorsqu'un **fichier est ouvert** (`state.currentPath`) **et** que le + panneau assistant est fermé ; sa visibilité est resynchronisée à chaque mutation de + `#content-area` et sur les événements `bookslm:opened`/`bookslm:closed`. Tests JSDOM + (`tests/frontend/mobile-editor.test.mjs` +2) et E2E mis à jour. + - **BUG-015 — Assistant IA : sélection des menus `/` et `@` invisible au clavier** — l'élément actif utilisait `background: var(--surface2)`, or `--surface2` est **identique** à `--bg-primary` (fond du menu) dans le thème sombre par défaut : la ligne sélectionnée par les diff --git a/docs/ISSUES_TODOLIST.md b/docs/ISSUES_TODOLIST.md index b08d5e2..5d5a644 100644 --- a/docs/ISSUES_TODOLIST.md +++ b/docs/ISSUES_TODOLIST.md @@ -125,6 +125,7 @@ Avant de corriger quoi que ce soit, un agent IA doit : | *BUG-013* | Assistant IA : les liens fichiers/répertoires des réponses affichent « Aucun vault actif pour ouvrir ce lien » | 🟢 corrigé | P1 | 📱 frontend | IA | `frontend/js/bookslm.js` | En mode Général, cliquer un lien de fichier/dossier dans une réponse | `_openFileLink()` et `_revealPath()` utilisent `_activeVault()` (vault de contexte → vault sélectionné → premier vault) au lieu de `_resolveVault()` seul | `_resolveVault()` est nul sans document ouvert ; les liens échouaient en mode Général. Tests : `tests/frontend/ai.test.mjs` (+1) | | *BUG-014* | Assistant IA : les flèches ↑/↓ ne naviguent pas dans les menus `/` et `@` | 🟢 corrigé | P1 | 📱 frontend | IA | `frontend/js/bookslm.js` | Ouvrir `/`, puis ↑/↓ (y compris après un clic hors zone de texte) | Navigation gérée au niveau du **panneau en phase de capture** (`panel.addEventListener('keydown', ..., true)`), donc indépendante du focus | Le `keydown` n'était écouté que sur la zone de texte ; dès que le focus changeait, les flèches étaient ignorées. Tests : `tests/frontend/ai.test.mjs` (+1) | | *BUG-015* | Assistant IA : la ligne sélectionnée des menus `/` et `@` est invisible au clavier | 🟢 corrigé | P2 | 📱 frontend | IA | `frontend/style.css` | Ouvrir `/` ou `@`, naviguer avec ↑/↓ | État `.active` en `--bg-hover` + barre d'accent à gauche (`inset 3px 0 0 var(--accent)`) au lieu de `--surface2` | `--surface2` est identique à `--bg-primary` (fond du menu) en thème sombre : la sélection ne se voyait pas. Idem pour la liste de modèles | +| *BUG-016* | Mobile : le bouton « mode lecture » flottant recouvre le bouton d'envoi de l'assistant IA | 🟢 corrigé | P2 | 📱 frontend | IA | `frontend/js/mobile-editor.js`, `frontend/style.css` | Mobile, sans fichier ouvert : ouvrir l'assistant puis constater que `📖` masque `✈️` | `updateReadingButtonVisibility()` n'affiche `#me-reading-btn` que si `state.currentPath` est défini **et** que le panneau assistant est fermé ; resynchronisé à chaque mutation de `#content-area` et sur `bookslm:opened`/`bookslm:closed` ; règle `.me-reading-btn[hidden]{display:none}` | Le bouton flottant (z-index 890) passait au-dessus du panneau plein écran mobile (z-index 100). Tests : `tests/frontend/mobile-editor.test.mjs` (+2) | ### TODOs techniques (améliorations / nouvelles tâches) @@ -156,6 +157,7 @@ Avant de corriger quoi que ce soit, un agent IA doit : | 2026-09-12 | BUG-015, #82 | Correction + UX | `frontend/js/bookslm.js`, `frontend/style.css`, `tests/frontend/ai.test.mjs` | BUG-015 : sélection des menus `/` et `@` rendue visible (`--bg-hover` + barre d'accent à gauche) car `--surface2` = `--bg-primary`. #82 : retrait de l'indice clavier sous la zone de saisie. Vérifié Playwright (contraste `#1f2430` vs `#0f1117` + barre accent) et tests frontend 47/47. | 🟢 corrigé (en attente vérif utilisateur) | | 2026-09-12 | #82 | Amélioration perf + UX | `backend/services/search.py`, `backend/main.py`, `frontend/js/bookslm.js`, `frontend/js/ui.js`, `frontend/style.css`, `tests/test_api_main.py`, `tests/frontend/ai.test.mjs` | #82 : endpoint `GET /api/vault/{vault}/paths` + préchargement et filtrage client du menu `@` (instantané) ; sélecteurs Fournisseur/Modèle agrandis (0,8 rem / 34 px) ; « 🧠 BooksLM » ajouté au menu contextuel de la racine des vaults. Vérifié : pytest 151 (ciblés) + ruff/mypy, tests frontend 49/49. | ✅ livré (en attente vérif utilisateur) | | 2026-09-12 | #82 | UX | `frontend/js/ai.js`, `frontend/style.css`, `tests/frontend/ai.test.mjs` | Sélecteurs Fournisseur/Modèle alignés à droite dans la barre de l'assistant et réordonnés : capacité du modèle (ⓘ) → fournisseur → modèle. Bulle de capacités ouverte vers la droite (`left: 0`). Test JSDOM de l'ordre. Vérifié : tests frontend 49/49. | ✅ livré (en attente vérif utilisateur) | +| 2026-09-12 | BUG-016 | Correction | `frontend/js/mobile-editor.js`, `frontend/style.css`, `tests/frontend/mobile-editor.test.mjs`, `tests/e2e/mobile-editor.spec.js` | BUG-016 : le bouton « mode lecture » n'apparaît que si un fichier est ouvert et que l'assistant est fermé, sinon il recouvrait le bouton d'envoi sur mobile. Vérifié : tests frontend 24/24 (mobile) + suite JSDOM verte. | 🟢 corrigé (en attente vérif utilisateur) | --- diff --git a/docs/features/mobile-editor.md b/docs/features/mobile-editor.md index e6806e3..eb6cf4b 100644 --- a/docs/features/mobile-editor.md +++ b/docs/features/mobile-editor.md @@ -1,6 +1,6 @@ # #69 - Éditeur mobile natif — Interface tactile optimisée -> **Statut :** ✅ Terminé — 100 % implémenté + 22 tests frontend (2026-09-11) +> **Statut :** ✅ Terminé — 100 % implémenté + 24 tests frontend (2026-09-12) > **Effort :** 2-3 jours (réalisé) | **Impact :** 🟢 > **Références :** [Roadmap](../ROADMAP.md) · [Changelog](../../CHANGELOG.md) @@ -10,7 +10,7 @@ - `frontend/index.html` — section « Édition mobile » du guide intégré + lien de navigation - `frontend/locales/{fr,en}.json` — clés `mobile_editor.*` et `help.mobile_editor_*` - `frontend/js/app.js` — appel `initMobileEditor()` au démarrage - - `tests/frontend/mobile-editor.test.mjs` — 22 tests JSDOM (helpers + câblage DOM) + - `tests/frontend/mobile-editor.test.mjs` — 24 tests JSDOM (helpers + câblage DOM) - `.gitea/workflows/ci.yml` — test ajouté au job `lint` - **Description :** sur téléphone et tablette, l'édition et la lecture s'adaptent au tactile : @@ -100,17 +100,24 @@ Le bouton flottant `📖` (`#me-reading-btn`) bascule `body.reading-mode` : borné avec message si extrémité atteinte) ; - l'état est mémorisé (`obsigate-reading-mode`) et restauré au rechargement sur mobile. +**Visibilité conditionnelle (BUG-016).** Le bouton `📖` n'est affiché que si un **fichier est +ouvert** (`state.currentPath`) **et** que le panneau assistant est fermé : sinon il passait +au-dessus du panneau mobile plein écran et masquait le bouton d'envoi. `updateReadingButtonVisibility()` +est appelé à l'init, à chaque mutation de `#content-area` (MutationObserver) et sur les événements +`bookslm:opened` / `bookslm:closed`. + --- ## Tests -`tests/frontend/mobile-editor.test.mjs` (JSDOM, 22 tests) couvre : +`tests/frontend/mobile-editor.test.mjs` (JSDOM, 24 tests) couvre : - `FORMAT_ACTIONS`, `clampZoom`, `detectSwipe` (seuil, dominance verticale, gauche/droite) ; - `formatChange` pour bold/italic/code/liste/lien, avec et sans sélection, toggle et ligne unique ; - `insertText`, `applyFormatToTextarea`, `applyEditorFontSize` (clamp + persistance) ; - le câblage DOM : construction de `#me-toolbar`, idempotence, contrôles du mode lecture, - bascule `toggleReadingMode`/`isReadingMode`. + bascule `toggleReadingMode`/`isReadingMode`, et visibilité conditionnelle de `#me-reading-btn` + (fichier ouvert + assistant fermé). Les chemins réseau (backlinks, fichiers adjacents) et les gestes réels restent à valider en E2E navigateur / test manuel mobile. diff --git a/frontend/js/mobile-editor.js b/frontend/js/mobile-editor.js index 0251344..ef68f10 100644 --- a/frontend/js/mobile-editor.js +++ b/frontend/js/mobile-editor.js @@ -603,6 +603,27 @@ function buildReadingControls() { document.body.appendChild(exit); } +/** True when the BooksLM assistant panel is expanded on screen. */ +function isAssistantOpen() { + const panel = document.querySelector('.bookslm-panel'); + return !!(panel && panel.classList.contains('open') && !panel.classList.contains('hidden')); +} + +/** + * Show the floating reading-mode button only while a file is open and the + * assistant panel is closed. Without this guard the button floats above the + * full-screen mobile assistant and covers its send button. + * + * Called on init, whenever the content area changes and when the assistant + * opens/closes. + */ +export function updateReadingButtonVisibility() { + const btn = document.getElementById('me-reading-btn'); + if (!btn) return; + const fileOpen = !!state.currentPath; + btn.hidden = !fileOpen || isAssistantOpen(); +} + // ── Swipe gestures ───────────────────────────────────────────────────────── function initSwipeGestures() { @@ -659,6 +680,19 @@ export function initMobileEditor() { buildReadingControls(); initSwipeGestures(); + // Keep the reading button in sync with the current view: visible only when + // a file is open and the assistant is closed. + updateReadingButtonVisibility(); + document.addEventListener('bookslm:opened', updateReadingButtonVisibility); + document.addEventListener('bookslm:closed', updateReadingButtonVisibility); + const readingArea = document.getElementById('content-area'); + if (readingArea && typeof MutationObserver !== 'undefined') { + new MutationObserver(updateReadingButtonVisibility).observe(readingArea, { + childList: true, + subtree: true, + }); + } + const bodyEl = document.getElementById('editor-body'); if (bodyEl) { initPinchZoom(bodyEl); diff --git a/frontend/style.css b/frontend/style.css index 2780bf6..5f3ba4f 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -9577,6 +9577,10 @@ body.bookslm-resizing { cursor: ew-resize; user-select: none; } .me-reading-exit { display: none; } +/* Author `display: flex` would otherwise win over the `[hidden]` attribute. */ +.me-reading-btn[hidden] { + display: none; +} @media (max-width: 768px) { .me-reading-btn { diff --git a/tests/e2e/mobile-editor.spec.js b/tests/e2e/mobile-editor.spec.js index 619bd42..40a58d2 100644 --- a/tests/e2e/mobile-editor.spec.js +++ b/tests/e2e/mobile-editor.spec.js @@ -30,6 +30,11 @@ test.describe('Mobile editor (#69)', () => { await boot(page); const enter = page.locator('#me-reading-btn'); + // The floating button only shows once a file is open (and not on the + // dashboard), so it cannot cover the assistant's send button. + await expect(enter).toBeHidden(); + + await page.evaluate(() => window.TabManager.open('TestVault', 'note1.md')); await expect(enter).toBeVisible(); await enter.click(); diff --git a/tests/frontend/mobile-editor.test.mjs b/tests/frontend/mobile-editor.test.mjs index 2a99591..34067d9 100644 --- a/tests/frontend/mobile-editor.test.mjs +++ b/tests/frontend/mobile-editor.test.mjs @@ -14,6 +14,7 @@ import { strict as assert } from "node:assert"; import { JSDOM } from "jsdom"; import { fileURLToPath, pathToFileURL } from "node:url"; import path from "node:path"; +import { state } from "../../frontend/js/state.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -62,6 +63,7 @@ const { initMobileEditor, toggleReadingMode, isReadingMode, + updateReadingButtonVisibility, } = mod; let testCount = 0; @@ -223,5 +225,34 @@ test("reading mode exposes enter and exit controls", () => { assert.ok(w.document.getElementById("me-reading-exit"), "exit button exists"); }); +test("reading button only shows while a file is open", () => { + const btn = w.document.getElementById("me-reading-btn"); + state.currentPath = null; + updateReadingButtonVisibility(); + assert.ok(btn.hidden, "hidden without an open file"); + state.currentPath = "Notes/note.md"; + updateReadingButtonVisibility(); + assert.ok(!btn.hidden, "shown when a file is open"); + state.currentPath = null; + updateReadingButtonVisibility(); + assert.ok(btn.hidden, "hidden again once the file is closed"); +}); + +test("reading button is hidden while the assistant panel is open", () => { + const btn = w.document.getElementById("me-reading-btn"); + const panel = w.document.createElement("div"); + panel.className = "bookslm-panel open"; + w.document.body.appendChild(panel); + state.currentPath = "Notes/note.md"; + updateReadingButtonVisibility(); + assert.ok(btn.hidden, "hidden while the assistant is expanded"); + panel.classList.add("hidden"); + updateReadingButtonVisibility(); + assert.ok(!btn.hidden, "shown again once the assistant is collapsed"); + panel.remove(); + state.currentPath = null; + updateReadingButtonVisibility(); +}); + console.log(`\n${passCount}/${testCount} tests passed\n`); process.exit(passCount === testCount ? 0 : 1);