fix(mobile): bouton mode lecture visible uniquement fichier ouvert (BUG-016)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user