Inclut SPECS_SVELTE.md : conventions Svelte 5/SvelteKit pour veille-ia, consolidées depuis doc2sveltekit-transition (playbook, charte OKI, best practices).
226 lines
14 KiB
Markdown
226 lines
14 KiB
Markdown
# 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).
|