# #114 — Configuration — refonte mobile-responsive de la section Settings > **Statut :** 🟢 · **Impact :** 🟡 · **Zone :** frontend (mobile, ≤ 768 px) > **Fichiers :** `frontend/index.html`, `frontend/js/config.js`, `frontend/style.css`, > `frontend/locales/{fr,en}.json`, `tests/frontend/config-mobile.test.mjs`, > `tests/e2e/config-mobile.spec.js`. ## Contexte La page **Configurations** (`#config-modal`) était utilisable en mobile seulement partiellement (correctifs BUG-071) : le sommaire s'ouvrait en bloc haut, la modale n'était pas plein écran, les cibles tactiles étaient sous 44 px, le clavier virtuel iOS zoomait les champs, la rangée « Sauvegarder » disparaissait au scroll et plusieurs dettes HTML/i18n étaient restées en place. ## Ce qui a été livré ### A. Sommaire en drawer coulissant - `#config-nav` devient un **panneau coulissant gauche** (`position: fixed`, `width: min(320px, 88vw)`, `z-index: 40`) sous un fond assombri (`#config-modal.config-toc-open::before`, `z-index: 35`). - Ouverture/fermeture pilotée par la classe **`.config-toc-open`** sur `#config-modal` (JS `_setConfigNav`) + un `display` inline conservé pour le contrat de reset. - Bouton **`#config-toc-close`** (classe `.help-toc-close`, `aria-label` `config.toc_close`) dans l'en-tête du drawer ; visible uniquement dans le drawer de la config (masqué sur desktop où la nav est toujours visible). - **Backdrop** : un tap hors du drawer ferme d'abord le drawer, pas la modale (`e.target === modal` → `config-toc-open` présent → `_setConfigNav(false)`). - **Échap** : ferme le drawer d'abord, puis la modale. - Fermeture de la modale (`closeConfigModal`) nettoie toujours la classe `config-toc-open` et le `display` inline (aucun fond ne subsiste). - Animation `config-toc-slide-in` (translateX) à l'ouverture. ### B. Modale plein écran - `#config-modal` : `padding: 0`, `.editor-container` en `100vw × 100dvh` (`100dvh` = hauteur du viewport dynamique, tient compte de la barre du navigateur mobile), `border-radius: 0`, `border: none`. - Le contenu (`#config-scroll`) garde son scroll propre ; le sous-bloc `.config-content` reçoit un `padding-bottom: 80 px` pour ne jamais passer sous la rangée sticky. ### C. Cibles tactiles ≥ 44 px & anti-zoom iOS - **Champs** : `.config-input`, `.config-select`, `.help-nav-search`, `.profile-field .config-input/.config-select` → `min-height: 44px` + `font-size: 16px` (**anti-zoom iOS** : un `font-size < 16px` déclenche le zoom automatique au focus). La règle est **scoppée `#config-modal`** pour ne pas écraser `.mfa-code-input` (qui a sa propre typographie). - **Boutons** : `.config-btn-save`, `.config-btn-secondary`, `.config-btn-primary`, `.config-btn-danger`, `.config-btn-add`, `.config-btn-sm`, `.mfa-link-btn`, `.theme-action-btn`, `.profile-avatar-actions .config-btn-secondary`, `.editor-btn` (fermer), `#config-hamburger`, `#config-toc-close`, `.help-search-clear` → `min-height/min-width: 44px`. - **Liens du sommaire** : `.help-nav-link` → `min-height: 44px` (ligne tactile confortable). - `.help-hamburger` passe de 36 px à **44 px** en mobile (règle partagée avec le modal d'aide). ### D. Rangée « Sauvegarder » sticky - `.config-actions-row` (dans `#cfg-backend-settings`) → `position: sticky; bottom: 0`, empilée verticalement (`flex-direction: column`), boutons pleine largeur 44 px, fond opaque + bordure, `padding-bottom` avec `env(safe-area-inset-bottom)` (barre home iOS). - La rangée reste visible pendant le scroll de la section backend ; le `padding-bottom: 80px` de `.config-content` garantit qu'elle ne masque jamais les derniers contrôles. ### E. Formulaires 1 colonne & grilles - `.config-row` → 1 colonne (`grid-template-columns: 1fr`), `.config-input--num` pleine largeur, `text-align: left`. - `.ai-default-grid` / `.ai-provider-fields` → 1 colonne. - Add-rows (`.config-add-row`, `.config-add-pattern`) → wrap + largeurs inline (`180/140/100px`) neutralisées (`width: auto !important`). - Items webhook/token/share → wrap ; URLs/méta sur leur propre ligne (`overflow-wrap: anywhere`) ; boutons de suppression 44 px. - `.hidden-files-add-row` → wrap, input pleine largeur. - `.config-diag-row` → wrap. - `.profile-avatar-row` → wrap ; `.profile-form` → `max-width: 100%`. - `.webauthn-key-item` → wrap ; `.webauthn-key-label` → pleine largeur. - `.theme-grid` reste en `auto-fill minmax(160px, 1fr)` (déjà responsive). ### F. MFA & sécurité - `.mfa-recovery-list` → **1 colonne** en mobile (2 colonnes illisibles à 360 px). - `.mfa-verify-section`, `.mfa-recovery-actions`, `.mfa-disable-actions` → wrap ; champs/boutons enfants en pleine largeur. - `.mfa-code-input` → `width: 100%`, `max-width: 320px`, `letter-spacing: 6px` (au lieu de 12 px qui débordait), `min-height: 52px`. - `.mfa-secret-code` → `word-break: break-all` (secret TOTP long). - `.mfa-code-input-group` → wrap. - Règle morte **`.mfa-recovery-input`** supprimée (auth.js utilise `mfa-code-input recovery-input`). ### G. Dettes HTML corrigées - `.config-actions-row` (Sauvegarder / Réindexer / Réinitialiser) déplacée **dans** `#cfg-backend-settings` (elle était hors de toute section → le sticky n'avait pas de conteneur de scroll fiable) ; `` orphelin supprimé. - Id dupliqué **`cfg-partages-publics`** retiré du `

` (l'id reste sur la `
`, cf. #113). - Conteneur mort **`#plugins-settings-container`** supprimé (le rendu réel est `#cfg-plugins` via `plugins.js`). - Sections plugins/about ré-indentées. - **`#mt-explorer`** : libellé brut `settings.search` remplacé par `` (i18n correct, FR « Explorateur » / EN « Files »). - Doublons CSS **`.config-btn-add`** (3 définitions) réduits à la définition de référence. ### H. i18n FR/EN - **Purge des clés mortes** (aucune référence HTML/JS/tests/backend) : `settings.backend`, `settings.backend_hint`, `settings.restart_badge`, `settings.save`, `settings.plugins`. - **Ajouts** : `settings.explorer` (FR « Explorateur » / EN « Files »), `config.toc_close` (FR « Fermer le sommaire » / EN « Close contents »). - **Correctif** : `settings.tabs` en FR était le mot anglais « Tabs » → **« Onglets »** (EN reste « Tabs »). - Clés vivantes conservées : `settings.reindex`, `settings.no_restart_badge`, `settings.backend_section`, `settings.security`, `settings.search`, `settings.tabs`, `settings.search_placeholder`. ## Tests ### Statics — `tests/frontend/config-mobile.test.mjs` (27, au CI) - BUG-071a–e (hamburger, toggle JS, grilles/wrap, ancres mortes, `data-i18n-attr` multi-paires) — conservés et adaptés au drawer. - **#114a** : `#config-toc-close` présent + i18n ; `_setConfigNav` bascule `.config-toc-open` ; backdrop `::before` (z-index 35) ; `.help-toc-close` masqué sur desktop / visible dans le drawer ; Échap et backdrop ferment le drawer d'abord ; `closeConfigModal` nettoie la classe. - **#114b** : modale `100dvh` + `padding: 0` ; inputs/selects `16px` + `44px` ; boutons `44px` ; rangée sticky (`position: sticky` + `flex-direction: column` + safe-area) ; MFA 1 colonne + wrap + code full-width ; `.config-actions-row` bien dans `#cfg-backend-settings`. - **#114i18n** : clés mortes purgées ; `settings.explorer` FR/EN ; `settings.tabs` FR = « Onglets » ; `#mt-explorer` porte `data-i18n="settings.explorer"` ; `#plugins-settings-container` absent. ### E2E — `tests/e2e/config-mobile.spec.js` (5, projet `chromium-mobile`) 1. Hamburger → drawer `position: fixed` + classe `config-toc-open` sur la modale. 2. Bouton `#config-toc-close` et tap backdrop ferment le drawer **sans** fermer la modale. 3. Sélection d'une section → scroll doux + lien actif + repli du drawer. 4. Modale plein écran (largeur/hauteur ≈ viewport) + `#config-hamburger`, `#config-close` et `#cfg-save-backend` ≥ 44 px. 5. Aucun débordement horizontal à 393 px (sections IA / tokens / webhooks / partages). ## Détails d'implémentation notables - **Spécificité CSS** : les règles `#config-modal #config-nav` (2 ids) priment sur les règles génériques `.help-nav` / `body .help-nav` qui masquent la nav en mobile — pas besoin de `!important`. - **Backdrop = pseudo-élément** : les clics sur `::before` sont attribués à l'élément or (`#config-modal`), donc le handler `e.target === modal` existant fonctionne sans node supplémentaire. - **Contrat de reset conservé** : `configNavOnOpen.style.display = ''` à l'ouverture (test statique BUG-071b) — le CSS reprend la main (drawer masqué par défaut sur mobile). - **`100dvh` avec repli `100vh`** : les navigateurs sans support `dvh` gardent le comportement précédent. - La règle `.help-hamburger { display: inline-flex }` du bloc mobile du modal d'aide est **partagée** (44 px) ; le drawer de la config double avec `#config-modal .help-hamburger` pour rester robuste à un réordonnancement des règles.