# #115 / BUG-078 / #117 — Barre d'outils épinglée, coloration syntaxique des fichiers de code & avatars prédéfinis > **Statut :** 🟢 livré (en attente vérification utilisateur) > **Impact :** 🟡 (#115, BUG-078) · 🟢 (#117) > **Zone :** frontend (`frontend/js/viewer.js`, `frontend/js/themes.js`, > `frontend/js/ui.js`, `frontend/js/config.js`, `frontend/index.html`, > `frontend/style.css`, `frontend/popout.html`, `frontend/locales/fr.json`, > `frontend/locales/en.json`, `frontend/icons/avatar/*`) Trois demandes traitées dans la même livraison, toutes côté frontend. ## #115 — Barre d'outils de lecture toujours visible ### Problème La barre d'actions d'un document (pop-out, bookmark, Editer, Source, Copier, PDF, Export, Partager…) était rendue **dans** `.file-header`, un conteneur court placé en haut de la zone de lecture. Or un élément `position: sticky` reste borné par son parent : dès que `.file-header` sortait de l'écran au défilement d'un document long, la barre disparaissait. ### Correctif - `frontend/js/viewer.js` et `frontend/popout.html` sortent la barre d'actions de `.file-header` et la placent dans un nouveau conteneur `.file-toolbar`, **enfant direct de `.content-area`** (le conteneur de défilement). La barre peut donc se coller au haut de la zone de lecture pour toute la hauteur du document. - `frontend/style.css` : ```css .file-toolbar { position: sticky; top: 0; z-index: 30; margin: 0 0 16px; padding: 8px 0; background: var(--bg-primary); border-bottom: 1px solid var(--border); } ``` Le fond est opaque pour qu'aucun texte ne transparaisse dessous. - `body.reading-mode .file-toolbar { display: none }` : la barre reste masquée en mode lecture, comme les autres actions. ## BUG-078 — Coloration syntaxique des fichiers de code ### Problème Les fichiers `.py`, `.sh`, `.ps1`, `.yml`, `.json`… (et les blocs de code Markdown) s'affichaient en **texte brut**, sans couleurs. Le code était pourtant correctement généré côté backend (`
…`) et `safeHighlight()` appelait
bien `hljs.highlightElement()`.
La cause était ailleurs : les deux feuilles de style de highlight.js (`#hljs-theme-dark`
et `#hljs-theme-light`, chargées depuis le CDN dans `index.html`) étaient basculées à
partir de la **clé de thème** persistée (`obsigate-theme` = `defaut-obsigate`, …) :
```js
darkSheet.disabled = theme !== "dark"; // "defaut-obsigate" !== "dark" → true
lightSheet.disabled = theme !== "light"; // "defaut-obsigate" !== "light" → true
```
Les deux feuilles finissaient donc désactivées, privant tous les tokens de leurs couleurs.
Le résultat dépendait de l'ordre de deux initialisations concurrentes — `UI.initTheme()`
(clé de thème) au démarrage et `themes.initThemes()` (mode) via `Sync.init()` — d'où un
comportement **non déterministe** (parfois coloré, le plus souvent non).
### Correctif
- `frontend/js/themes.js` — `applyTheme(themeKey, mode)` bascule désormais les feuilles
highlight.js selon le **mode** :
```js
var isDark = mode === 'dark';
darkSheet.disabled = !isDark;
lightSheet.disabled = isDark;
```
(`sepia` et `high-contrast` réutilisent la palette claire.)
- `frontend/js/ui.js` — `initTheme()` lit le **mode** persisté (`obsigate-theme-mode`) et
`applyTheme()` résout un mode avant de fixer `data-theme` et de basculer les feuilles,
au lieu de comparer la clé de thème à `"dark"`/`"light"`.
Le basculement est ainsi **déterministe** dès le premier rendu et à chaque changement de
mode.
## #117 — Avatars prédéfinis dans le profil
### Ce qui a été livré
- **Galerie de 12 avatars** dans la section `Profil` (`#cfg-profile`), servis depuis
`frontend/icons/avatar/` (`/static/icons/avatar/.jpg`) : Chat, chien, elephan,
hibou, koala, lapin, lion, ours, penda, pingouin, raton, tigre.
- Un clic charge l'image, la fait passer par le **même pipeline que l'import** (recadrage
carré central, redimensionnement 256 px, export JPEG 0,85) et l'enregistre via
`PATCH /api/auth/me` — aucune modification backend n'a été nécessaire, la validation
data-URL PNG/JPEG/WebP existante (BUG-113) s'applique telle quelle.
- L'avatar actif est **surligné** ; le choix est mémorisé dans `localStorage`
(`obsigate-avatar-preset`) et purgé dès qu'on importe une photo personnalisée ou qu'on
supprime l'avatar.
- L'import d'une photo et la suppression restent disponibles.
- i18n : nouvelle clé `config.avatar_presets_label` (FR/EN).
## Tests
- `tests/frontend/unit.test.mjs` — `syntax highlight theme` (BUG-078) : `themes.applyTheme`
bascule les feuilles selon le mode et `ui.js` ne compare plus la clé au mode.
- `tests/frontend/toolbar-order.test.mjs` — #115 : `.file-toolbar` dans `viewer.js` et
`popout.html`, règle CSS `position: sticky; top: 0`, masquage en mode lecture.
- `tests/frontend/settings-order-avatar.test.mjs` — #117 : 12 avatars présents dans
`#cfg-profile`, fichiers d'images existants, pipeline `config.js`, règles CSS, clé i18n
FR/EN.
- Vérification Playwright (instance locale, auth désactivée) : coloration déterministe sur
5 chargements successifs d'un `.py` ; `.file-toolbar` dont le `top` ne bouge plus après
défilement (épinglage effectif).
## Limitations
- L'avatar reste stocké en data-URL 256 px dans `data/users.json` (comportement #113
conservé).
- Le mode `high-contrast`/`sepia` utilise le thème highlight.js **clair** (pas de palette
dédiée).