docs: spec jalon 2 (polish design system)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-05 04:52:50 +02:00
parent 1e1be7f627
commit 50df83fda1
@@ -0,0 +1,107 @@
# Jalon 2 — Polish design system — Design
> Spec du deuxième jalon : refonte de l'UI avec le design system Gruvbox seventies.
> Statut : **validé** (2026-06-05). Langue de travail : français.
> Voir aussi : `CLAUDE.md`, `design_system/consigne_design_system.md`, jalon 1 (`docs/superpowers/specs/2026-06-04-jalon1-tranche-verticale-apt-design.md`).
## Objectif
Le jalon 1 a livré une UI fonctionnelle mais "brute" : des `<button className="interactive">` et des styles inline, sans utiliser les composants du design system. Le `ui-kit.tsx` porté n'est même pas consommable (il expose ses composants via `Object.assign(window, …)` et dépend de Font Awesome non chargé).
Ce jalon **branche correctement le design system** et **refond les écrans existants** avec ses composants, en respectant la consigne (`design_system/consigne_design_system.md`). Aucune nouvelle capacité métier : c'est un jalon qualité.
## Périmètre
**Dedans** : wiring du DS (exports ESM, Font Awesome, polices, tous bundlés offline), refonte des écrans existants avec les composants DS, ajout d'un header (titre + actions + bascule thème), ajout d'une status bar style tmux, vérification des deux thèmes (dark + light).
**Dehors** : aucune logique backend, aucune nouvelle route, pas de panneaux redimensionnables (split panes — reporté), pas de nouveaux écrans.
## Décisions verrouillées
| Sujet | Décision |
|-------|----------|
| Font Awesome | Bundlé via npm `@fortawesome/fontawesome-free` (solid), import CSS dans `main.tsx`. Offline, pas de CDN. |
| Polices | Bundlées via `@fontsource/inter`, `@fontsource/jetbrains-mono`, `@fontsource/share-tech-mono`, importées dans `main.tsx`. Offline. |
| Consommation `ui-kit` | Ajout d'exports ESM nommés. On garde `@ts-nocheck` (pas de réécriture typée) et le `Object.assign(window, …)` existant. |
| Layout | Ajout d'un header et d'une status bar. Les 3 volets restent. |
| Thème | Bascule dark/light via `lib/theme.ts` (persistance localStorage), IconButton sun/moon dans le header. |
| Tests | Helpers purs testés (`theme`, `sumUpdates`). Le reste = vérif visuelle. `ui-kit` jamais importé dans un test (touche `window`/`document` au chargement). |
## Contrainte transverse
Le design system impose (consigne) : variables CSS uniquement, composants existants réutilisés, `<Icon name=>` (jamais d'emoji/SVG custom), **pas de hover** sauf jauges (pression 3D `.interactive`), tooltips obligatoires sur IconButton isolé, polices Inter/JetBrains Mono/Share Tech Mono, labels uppercase. Tout écran doit être lisible et cohérent en **dark ET light**.
## API du design system (vérifiée dans `ui-kit.tsx`)
- `Icon({ name, size, style })``name` mappé via `ICON_MAP` vers `fa-solid fa-…`. Icônes dispo : cpu, memory, disk, network, clock, grid, list, cog, alert, bell, server, chart, bars, terminal, refresh, play, pause, power, sun, moon, search, close, chevR/L/D/U, plus, filter, download, folder, node, user.
- `Button({ children, icon, onClick, variant, size })` — variant: default/primary/ghost/danger ; size: sm/md/lg.
- `IconButton({ icon, label, onClick, active, danger, size, primary })``label` = tooltip (obligatoire).
- `StatusLed({ status, size, pulse })` — status: ok/warn/err/info/off.
- `Popup({ open, onClose, title, children, footer, width })`.
- `Toggle`, `Tooltip`, `BatteryGauge`, `RadialGauge`, `BigRadialGauge`, `TreeNav`, `Sparkline`, `LineChart` (non utilisés ici mais exportés).
## Wiring du design system
1. **`client/src/components/ui-kit.tsx`** : ajouter en fin de fichier
`export { Icon, Tooltip, IconButton, Toggle, StatusLed, BatteryGauge, RadialGauge, BigRadialGauge, Popup, Button, TreeNav, Sparkline, LineChart };`
Conserver `@ts-nocheck`, l'import React et le `Object.assign(window, …)`.
2. **`client/src/main.tsx`** : ajouter les imports CSS
`import "@fortawesome/fontawesome-free/css/all.min.css";`
`import "@fontsource/inter";` `import "@fontsource/jetbrains-mono";` `import "@fontsource/share-tech-mono";`
3. **`package.json`** : ajouter les deps `@fortawesome/fontawesome-free`, `@fontsource/inter`, `@fontsource/jetbrains-mono`, `@fontsource/share-tech-mono`.
## Layout cible
```
┌─ Header : « System Update » ............ [+ Ajouter] [☀/☾] ┐
├──────────┬─────────────────────────────┬────────────────────┤
│ Hermes │ Dashboard (tuiles machines) │ Terminal │
├──────────┴─────────────────────────────┴────────────────────┤
│ SYSTEM UPDATE · N machines · M updates · ⏱ 14:22:07 │
└──────────────────────────────────────────────────────────────┘
```
`App.tsx` orchestre : `<Header>` en haut, la rangée 3 volets au milieu (flex:1), `<StatusBar>` en bas.
## Composants
### Nouveaux
- **`Header.tsx`** : titre « System Update », bouton `<Button variant="primary" icon="plus">Ajouter</Button>` (ouvre la modale via callback remonté), et `<IconButton icon={theme==="dark"?"sun":"moon"} label="Basculer le thème">`. Hauteur 48-56px, fond `--bg-2`.
- **`StatusBar.tsx`** : 1re cellule mode « SYSTEM UPDATE » fond `--accent` ; cellules suivantes séparées par `border-right: 1px solid var(--border-1)` ; nb machines, total updates ; horloge live (Share Tech Mono, tick 1s via `setInterval`, nettoyé au démontage) à droite. Hauteur 24-28px.
- **`lib/theme.ts`** :
- `type Theme = "dark" | "light"`
- `getInitialTheme(): Theme` — lit `localStorage["su-theme"]`, défaut `"dark"`, robuste si localStorage indisponible.
- `applyTheme(t: Theme): void``document.documentElement.dataset.theme = t` + persiste.
- `nextTheme(t: Theme): Theme` — bascule dark↔light (fonction pure, testable).
- **`lib/stats.ts`** : `sumUpdates(counts: Record<string, number>): number` (fonction pure, testable).
### Refondus
- **`MachineTile.tsx`** : point d'état → `<StatusLed status={machine.status} pulse={machine.status==="running"}>` ; compteur → `<span className="label">UPDATES</span> <span className="mono">{count}</span>` ; actions → `<IconButton icon="refresh" label="Rafraîchir">`, `<IconButton icon="download" label="Upgrade">`, `<IconButton icon="power" label="Redémarrer" danger>`. Conserver `className="glass"`, `onClick` sélection (les IconButton stoppent la propagation).
- **`AddMachineModal.tsx`** : enveloppé dans `<Popup open onClose title="Ajouter une machine" footer={…}>` ; footer = `<Button variant="ghost" onClick={onClose}>Annuler</Button>` + `<Button variant="primary" icon="download" onClick={submit}>Ajouter</Button>` ; champs en inputs tokenisés ; erreur affichée avec `<StatusLed status="err">` + texte `--err`. Logique de soumission inchangée (POST /api/machines).
- **`Dashboard.tsx`** : retirer le bouton « + Ajouter » local (déplacé dans le Header) ; le Dashboard expose l'ouverture de la modale via prop/état remonté à `App`. Grille de tuiles inchangée. État vide : texte `--ink-3`.
- **`HermesPanel.tsx`** : en-tête `.label` + `<Icon name="bell">` (ou autre), texte stub inchangé.
- **`TerminalPanel.tsx`** : ajouter un petit en-tête `.label` « TERMINAL · {machineId ?? "—"} » au-dessus du conteneur xterm. xterm inchangé.
## Flux thème
Au montage de `App` : `applyTheme(getInitialTheme())`. État `theme` dans `App` ; le toggle du Header appelle `setTheme(nextTheme(theme))` puis `applyTheme`. `data-theme` initial dans `index.html` reste `dark` (cohérent avec le défaut).
## Gestion d'erreurs / cas limites
- `localStorage` indisponible (mode privé) → `getInitialTheme` retombe sur `"dark"`, `applyTheme` ignore l'échec de persistance (try/catch) sans casser l'UI.
- Icône inconnue → le composant `Icon` retombe déjà sur `circle-question`.
- Horloge : l'intervalle est nettoyé dans le cleanup du `useEffect`.
## Tests
- **`lib/theme.test.ts`** : `nextTheme("dark")==="light"` et inverse ; `getInitialTheme()` retombe sur `"dark"` quand localStorage vide. (localStorage mocké, pas d'import de `ui-kit`.)
- **`lib/stats.test.ts`** : `sumUpdates({a:2,b:3})===5` ; `sumUpdates({})===0`.
- Vérif build : `pnpm check` + `pnpm build` verts.
- **Vérif visuelle manuelle** (utilisateur) : dark ET light lisibles ; icônes FA affichées ; polices Inter/JetBrains Mono/Share Tech Mono appliquées ; tooltips sur les IconButton ; modale Popup OK ; status bar + horloge ; aucun hover sur boutons/tuiles (pression 3D seulement).
## Critères d'acceptation
- [ ] `ui-kit` exporte ses composants en ESM ; les écrans les importent (plus aucun `<button className="interactive">` brut dans les features).
- [ ] Font Awesome et les 3 polices sont bundlés (offline) et appliqués.
- [ ] Header avec titre, bouton Ajouter, bascule thème fonctionnelle et persistée.
- [ ] Status bar tmux avec mode, compteurs et horloge live.
- [ ] MachineTile utilise StatusLed + IconButton (tooltips) ; AddMachineModal utilise Popup + Button.
- [ ] Les deux thèmes sont cohérents et lisibles.
- [ ] `pnpm check`, `pnpm build`, et les tests des helpers passent.