commit dc6bf1bbdeddb1898f7a67cb1980e29e5ed3c28e Author: cyber-mawonaj Date: Sat Aug 1 09:18:27 2026 -0400 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). diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ede6b9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Dépendances et builds +node_modules/ +app/build/ +app/.svelte-kit/ + +# Données locales (dev) — en prod : /home/yunohost.app/veille-ia +app/data/ +data/ + +# Environnement +.env +.env.* +!.env.example + +# Divers +.DS_Store +*.log diff --git a/SPECS_SVELTE.md b/SPECS_SVELTE.md new file mode 100644 index 0000000..9e061b3 --- /dev/null +++ b/SPECS_SVELTE.md @@ -0,0 +1,225 @@ +# 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 (), 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 `` / ``. +- `{#each}` avec **clé unique** (id métier), jamais l'index ; pas de destructuration si mutation (`bind:`). +- Snippets `{#snippet}` / `{@render}` plutôt que ``. +- 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 ` 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 `` 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` (`` 24×24, stroke 2 px, `currentColor`) + + ``. **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 (`