Files
veille-ia-gen/SPECS_SVELTE.md
T
cyber-mawonaj dc6bf1bbde chore : import initial du dossier de travail (PRD, guides, recommandations doc2sveltekit)
Inclut SPECS_SVELTE.md : conventions Svelte 5/SvelteKit pour veille-ia,
consolidées depuis doc2sveltekit-transition (playbook, charte OKI, best practices).
2026-08-01 09:18:27 -04:00

226 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SPECS_SVELTE.md — Conventions Svelte/SvelteKit pour « veille-ia »
> **Statut :** ce fichier PRÉVAUT sur tout choix technique frontend (cf. `prompt_kimi_cli_veille_ia.md`).
> Il consolide les recommandations de `doc2sveltekit-transition/` (playbook SvelteKit, charte OKI,
> best practices Svelte 5) adaptĂ©es Ă  une **app privĂ©e d'administration** self-hosted YunoHost —
> pas un site vitrine public : pas de SEO, pas de motion « Awwwards », pas de PWA.
>
> En cas de conflit : ce document prime sur la recette générique pour la marque et le style ;
> le PRD (`plan_prd_veille_ia.md`) prime pour le fonctionnel ; la recette prime pour la méthode technique.
---
## 1. Stack
- **SvelteKit 2 + Svelte 5** (runes uniquement) + **TypeScript strict** (`strict: true`).
- **`@sveltejs/adapter-node`** — l'app tourne en service systemd derriùre nginx (YunoHost).
- **Vite** (fourni par SvelteKit). Aucune configuration exotique.
- **CSS vanilla + tokens OKI** — **pas de Tailwind**, aucun framework CSS lourd (dĂ©cision : recette §2.3, charte OKI).
- **Zod** pour la validation des schémas YAML (registre, profils, alertes).
- **js-yaml** pour la persistance YAML ; **simple-git** pour les commits automatiques du data_dir.
- **vitest** pour les tests (moteur de recommandation notamment).
- **eslint + prettier** (config SvelteKit standard).
- Gestionnaire de paquets : **npm** (lockfile `package-lock.json`, jamais mélangé avec pnpm/yarn).
- Node : **24 LTS** en dev comme en prod (ressource `nodejs` YunoHost alignée sur le catalogue : nodered, etherpad, hedgedoc utilisent `"24"`).
**Interdictions (recette §0, adaptées) :**
- Jamais de dĂ©pendance installĂ©e sans ĂȘtre importĂ©e et utilisĂ©e.
- Jamais de SSR désactivé globalement pour « faire marcher » une lib client.
- Jamais d'appel Ă  un service tiers au chargement (fonts, CDN, analytics) — tout asset est self-hĂ©bergĂ©.
- Aucune dépendance propriétaire ou source-available (licences OSI uniquement, projet AGPL-3.0).
## 2. Structure de dossiers (`app/`)
```
app/
├── src/
│ ├── app.html # lang="fr", script theme.js externe (anti-FOUC)
│ ├── app.d.ts # typage Locals (user SSO), env
│ ├── hooks.server.ts # auth SSO (YNH_USER), CSP, paths.base
│ ├── lib/
│ │ ├── styles/
│ │ │ ├── oki-tokens.css # tokens charte OKI (copie versionnĂ©e, §4)
│ │ │ └── base.css # reset, primitives, gate reduced-motion
│ │ ├── server/ # code serveur uniquement (jamais importĂ© cĂŽtĂ© client)
│ │ │ ├── config.ts # variables d'env (DATA_DIR, OLLAMA_URL, ALERTS_TOKEN
)
│ │ │ ├── registre.ts # lecture/Ă©criture YAML + validation Zod
│ │ │ ├── profils.ts
│ │ │ ├── alertes.ts
│ │ │ ├── git.ts # simple-git : commit atomique Ă  chaque Ă©criture
│ │ │ └── recommandation.ts # moteur (pur, testable, sans I/O)
│ │ ├── components/ # Badge, Card, Tag, Icon, DataTable, FormField

│ │ └── types.ts # types TS partagĂ©s (infĂ©rĂ©s des schĂ©mas Zod)
│ └── routes/
│ ├── +layout.svelte # skip-link, nav SSO-aware, flag-bar
│ ├── +layout.server.ts # expose l'utilisateur SSO aux pages
│ ├── +page.svelte # accueil (public minimal si non connectĂ©)
│ ├── +error.svelte # erreurs designĂ©es (P5)
│ ├── registre/ # CRUD registre (protĂ©gĂ© SSO)
│ ├── profils/ # CRUD profils (protĂ©gĂ© SSO)
│ ├── recommander/ # UI moteur de recommandation
│ ├── alertes/ # inbox (phase 2)
│ └── api/
│ ├── recommander/+server.ts
│ └── alerts/+server.ts # POST protĂ©gĂ© par token (phase 2)
├── static/
│ ├── fonts/ # Archivo + Inter woff2 self-hĂ©bergĂ©es + fonts.css
│ ├── theme.js # bascule thùme clair/sombre anti-FOUC
│ └── icons.svg # sprite SVG (<symbol>), zĂ©ro emoji
├── package.json # private: true
├── svelte.config.js # adapter-node, paths
└── vite.config.ts
```
RĂšgles :
- Tout code qui lit des fichiers, des variables d'env ou fait du fetch sortant vit dans `lib/server/`.
- Le moteur de recommandation est une fonction **pure** (entrĂ©es → sorties), testable sans serveur.
- Les données d'exemple/seed vivent dans `app/src/lib/server/seed/` et ne sont copiées dans le
`DATA_DIR` qu'au premier démarrage (jamais d'écrasement de données existantes).
## 3. Conventions Svelte 5 (obligatoires)
- **Runes only.** `$state` uniquement pour du rĂ©actif ; gros objets rĂ©assignĂ©s (rĂ©ponses API) → `$state.raw`.
- `$derived` pour tout calcul ; `$derived.by` si l'expression est complexe. **Jamais `$effect` pour calculer.**
- `$effect` = escape hatch uniquement (sync avec lib externe), toujours avec cleanup symétrique.
- `$props()` en traitant les props comme changeantes : toute valeur dérivée d'une prop passe par `$derived`.
- ÉvĂ©nements : `onclick={...}` (jamais `on:click`) ; listeners globaux via `<svelte:window>` / `<svelte:document>`.
- `{#each}` avec **clé unique** (id métier), jamais l'index ; pas de destructuration si mutation (`bind:`).
- Snippets `{#snippet}` / `{@render}` plutĂŽt que `<slot>`.
- Classes conditionnelles : tableaux/objets style clsx dans `class={...}`, pas de `class:`.
- Liens internes via `resolve()` de `$app/paths` ; assets via `asset()`. Jamais de chemin en dur
(l'app peut ĂȘtre servie sous un sous-chemin YunoHost).
- `{@html}` interdit sauf contenu first-party, commenté comme tel.
- État partagĂ© inter-composants : module `.svelte.ts` avec `$state` + fonctions — pas de stores legacy.
- **RÚgle des loads :** toute donnée lue cÎté serveur (YAML, API externe) passe par `+page.server.ts` /
`+layout.server.ts` ou des **form actions** — jamais de fetch direct vers le data_dir cĂŽtĂ© client.
- Mutations : **form actions SvelteKit** par défaut (progressive enhancement, fonctionne sans JS) ;
`fetch` JSON uniquement pour les endpoints API externes (`/api/*`).
- Persistance de préférences : `?? défaut`, jamais `|| défaut` (un `0`/`false` persisté est légitime).
- Validation systématique via `npx @sveltejs/mcp svelte-autofixer <fichier>` avant de finaliser un composant.
## 4. Style — tokens OKI (charte §2, copie versionnĂ©e)
Un seul fichier `src/lib/styles/oki-tokens.css` importé en premier dans `+layout.svelte` (aucune valeur
recopiée à la main dans les composants) :
```css
:root {
/* Noyau (thÚme sombre = défaut) */
--noir-oki: #0D0D0D; /* background */
--noir-profond: #1A0F1A; /* surface */
--blanc-creme: #FFF8E7; /* foreground */
--line: rgba(255,255,255,0.10);
--or-oki: #FDB813; /* accent : SEULE couleur d'action */
--rouge-oki: #FF1654; /* signal — jamais un lien/bouton */
--vert-oki: #00D66C; /* succĂšs, validation */
--or-clair: #FFE066; /* survol des boutons */
--muted: color-mix(in srgb, var(--blanc-creme) 70%, transparent);
--card-bg: rgba(255,255,255,0.03);
/* Typographie */
--font-display: 'Archivo', 'Arial Black', sans-serif;
--font-body: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
/* Formes, espacements */
--radius-sm: 3px; --radius-md: 6px;
--border-card: 1px solid var(--line);
--border-btn: 2px solid var(--or-oki);
--space-1: 0.75rem; --space-2: 1rem; --space-3: 1.5rem; --space-4: 2rem; --space-5: 4rem;
--container: 1200px;
/* Motion */
--ease-ka: cubic-bezier(0.22, 1, 0.36, 1);
--ease-syncope: cubic-bezier(0.65, 0, 0.35, 1);
--dur-tanbou: 120ms; --dur-mesure: 480ms; --dur-phrase: 960ms;
}
```
Sémantique des couleurs, adaptée au back-office (rÎles **jamais permutés**) :
- **Or = agir** : boutons, liens, focus. Seule couleur d'action.
- **Vert = valider** : statut `production`, licence vérifiée, alerte traitée.
- **Rouge = signaler** : statut `mort`, urgence haute, erreur — jamais cliquable.
- **Badge « ⚠ Ă  vĂ©rifier »** (rĂšgle anti-hallucination du PRD) : fond or 8 % + bordure or 1 px
(tag OKI canonique) + texte crÚme ; rendu par un composant `<BadgeAVerifier>` dédié, utilisé
dĂšs qu'un champ est `null`. Le pictogramme ⚠ fait partie du sprite SVG (rĂšgle zĂ©ro emoji ci-dessous).
RĂšgles d'usage :
- ThÚme **sombre par défaut**, clair opt-in via `html.light-theme` posée par `static/theme.js`
(script externe, localStorage, anti-FOUC). Accents assombris en thĂšme clair pour WCAG AA.
- Texte secondaire = crĂšme 70–85 % d'opacitĂ© ; filets = blanc 10 %, jamais de gris plein.
- Titres h1–h4 : Archivo **majuscules**, `letter-spacing: -0.01em`. Boutons : Archivo 700 uppercase,
bordure 2 px or, survol `--or-clair`. Corps : Inter, `line-height: 1.5`.
- Cartes : fond `--card-bg`, liserĂ© gauche 4 px d'accent, rayon ≀ 6 px. Angles nets partout.
- **Flag-bar** (6 px, 4 segments francs noir/or/vert/rouge) sous la navigation — **une occurrence
visible par écran maximum**.
- Iconographie : sprite `static/icons.svg` (`<symbol>` 24×24, stroke 2 px, `currentColor`) +
`<svg><use href="...#id"/></svg>`. **ZĂ©ro emoji en production** — y compris dans les badges et
messages d'Ă©tat (le glyph ⚠ est fourni par le sprite, pas par un emoji Unicode).
- Fonts **self-hébergées** : woff2 dans `static/fonts/`, `@font-face` avec `font-display: swap`,
preload de la display uniquement. Aucun appel Google Fonts.
- Motion : micro-interactions sobres uniquement (`--dur-tanbou` sur hover/focus, `--dur-mesure`
sur transitions de vues). Uniquement `transform` + `opacity`. Durées lues depuis les tokens,
jamais hardcodées. **Gate unique `prefers-reduced-motion`** dans `base.css` (contenu statique complet).
- Styles scopĂ©s dans les composants ; personnalisation parent→enfant via custom properties ;
`:global` en dernier recours.
- Layout « app » : `height: 100dvh` + overflow par panneau si pertinent ; cibles tactiles ≄ 44 px.
- Listes longues (registre, inbox) : `content-visibility: auto` + `contain-intrinsic-size` d'abord,
pagination « charger plus » avant toute virtualisation.
## 5. Accessibilité (non négociable)
- Navigation clavier complĂšte : tab order logique, **focus visible** (outline or 2 px), skip-link
« Aller au contenu » en premier élément du layout.
- ContrÎles de formulaire tous labellisés (`<label for>`), erreurs de validation annoncées
(`aria-describedby`, `aria-invalid`).
- Contrastes WCAG AA vĂ©rifiĂ©s sur les deux thĂšmes (texte courant ≄ 4,5:1).
- IcĂŽnes dĂ©coratives `aria-hidden="true"` ; icĂŽnes seules dans un bouton → `aria-label`.
- Tableaux de données sémantiques (`<th scope>`), tri annoncé via `aria-sort`.
- Focus déplacé sur le `<h1>` aprÚs chaque navigation ; `lang="fr"` sur `<html>`.
- Le badge « à vérifier » ne repose jamais sur la couleur seule (pictogramme + texte).
- Zéro warning d'accessibilité svelte-check toléré.
## 6. i18n
- **Français d'abord** : tous les libellés UI en français, regroupés dans `src/lib/i18n/fr.ts`
(objet typĂ© clĂ© → libellĂ©), jamais de chaĂźne en dur dans les templates.
- Structure prĂȘte pour crĂ©ole/anglais : un fichier par locale, sĂ©lection par `data-locale`
(pas de routing i18n au MVP — app monoprivĂ©e).
- Microcopy signature en krĂ©yĂČl admise (404, Ă©tats vides) avec attribut `lang="gcf"` sur le span.
## 7. Sécurité & intégration YunoHost (cÎté app)
- **SSO :** `hooks.server.ts` lit le header **`Ynh-User`** injecté par SSOwat via `proxy_params_with_auth`
(conf nginx du package ; le header est vidĂ© cĂŽtĂ© nginx avant d'ĂȘtre renseignĂ© aprĂšs authentification,
donc non spoofable). Sans header valide → seule la page d'accueil publique minimale est accessible ;
toutes les routes `/registre`, `/profils`, `/recommander`, `/alertes` et les form actions exigent un
utilisateur. En dev local, `DEV_USER` simule le header (documenté, ignoré en production).
- **CSP via la config SvelteKit** (`kit.csp`, mode `nonce`, dans `vite.config.ts` — SvelteKit ≄ 2.62
n'utilise plus `svelte.config.js`) :
`default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;
font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self';
object-src 'none'` + `X-Content-Type-Options: nosniff`, `Referrer-Policy: strict-origin-when-cross-origin`.
**Aucun script inline** — `theme.js` est un fichier externe.
- **Sous-chemin :** l'app doit fonctionner servie sous `https://domaine.tld/veille` :
`paths.relative: false` dans `svelte.config.js`, base injectée par variable d'env au build/runtime,
et tous les liens via `resolve()`/`asset()`.
- **`POST /api/alerts`** : token `ALERTS_TOKEN` (header `Authorization: Bearer`), généré à l'install
YunoHost, jamais commité. Aucune route mutative sans vérification (SSO ou token).
- Toutes les entrées (YAML, formulaires, webhooks) validées par **Zod** cÎté serveur avant tout traitement.
- Journaux : `console` structuré (JSON), récupéré par journald via systemd.
## 8. QualitĂ© — dĂ©finition de « terminĂ© » (chaque commit)
- `npm run check` (svelte-check) : **0 erreur, 0 warning**.
- `npm run lint` (eslint + prettier --check) : propre.
- `npm run test` (vitest) : vert — moteur de recommandation couvert (cas nominaux + cas limites :
licence `null`, seuil de revenus dépassé, modÚle statut `mort`, champ `null` pénalisé).
- `npm run build` : vert.
- Grep de vérité avant livraison : zéro domaine tiers dans le build, zéro emoji dans le HTML buildé.
- Revue clavier + `prefers-reduced-motion` sur les pages ajoutées.
- Commits git **atomiques et explicites, en français** ; `README.md` à jour à chaque phase.
## 9. Explicitement hors périmÚtre (app privée)
- SEO, OG/Twitter cards, sitemap, robots — pas d'indexation, app derriùre SSO.
- PWA / service worker / mode hors-ligne.
- Motion riche (Lenis, GSAP, WebGL, kinetic type) — la charte motion OKI s'applique en version sobre (§4).
- Imagerie afrofuturiste, footer fĂ©dĂ©rĂ© public — interface utilitaire ; le favicon OKI suffit.
- Multi-utilisateurs, notifications push natives (PRD §10).