first commit

This commit is contained in:
2026-07-21 16:50:58 +02:00
commit 256599626e
407 changed files with 30489 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
# Activer Code scanning (CodeQL) sur GitHub
Le workflow [.github/workflows/security.yml](../.github/workflows/security.yml) exécute lanalyse CodeQL pour JavaScript/TypeScript.
## Comportement actuel
L’étape `analyze` utilise `upload: false` pour éviter les erreurs lorsque **GitHub Advanced Security / Code scanning** nest pas encore activé sur le dépôt, ou lorsque les permissions dupload SARIF ne sont pas disponibles.
## Activer les résultats dans lUI GitHub
1. Sur GitHub : **Settings****Code security** (ou **Security****Code scanning** selon linterface).
2. Activez **Code scanning** avec le moteur CodeQL (ou laissez les workflows recommandés détecter `.github/workflows/security.yml`).
3. Éditez `security.yml` et **supprimez** la ligne suivante dans l’étape `analyze` :
```yaml
upload: false
```
Le comportement par défaut rétablit lupload des résultats SARIF vers GitHub.
## Pull requests depuis des forks
Sur les PR issues de forks, lupload SARIF peut être restreint pour des raisons de permissions ; le workflow conserve dans ce cas une analyse locale utile sans bloquer la contribution.
+76
View File
@@ -0,0 +1,76 @@
# Checklist lancement V1 (Ben-to)
Document opérationnel aligné sur le dépôt : contenu embarqué, qualité, déploiement web, Android / Play Store.
## 1. Contenu et assets
- Finaliser les `.bentext` et visuels sous `ressources/` (sprites ingrédients : `ingredient-sprites.bentext` + `ingredients.png`, voir README).
- `npm run pipeline:recipes` puis `npm run build:web` — vérifier `apps/web/dist/recipes/catalog.json`.
## 2. Qualité (gel avant release)
À la racine du dépôt :
```bash
npm run pipeline:recipes
npm run test:ci
npm run check:quality
```
Prérequis e2e : navigateurs Playwright (`npx playwright install chromium` si besoin).
## 3. Web — image Docker et smoke tests
Build image (identique à une prod type CapRover servant le `dist`) :
```bash
docker build -t ben-to-web:local .
docker run --rm -p 8080:3000 ben-to-web:local
```
Smoke manuel (adapter le port) :
- Page daccueil charge le builder.
- Une URL recette du type `http://localhost:8080/r/<baseId>/<variantId>` ouvre la fiche (IDs présents dans `catalog.json`).
- Pages légales : `/a-propos`, `/cgu`, `/mentions-legales`, `/confidentialite`.
- SEO fichiers statiques : `/robots.txt` et `/sitemap.xml` pointent vers **[https://ben-to.fr](https://ben-to.fr)** ; les langues passent par lUI (pas dURL `/fr/` dédiées).
## 4. Android — build et signature release
Chaîne locale :
```bash
npm run pipeline:recipes
npm run build:web
npm run mobile:sync
```
Signature Play Store :
1. Créer un keystore (une fois), le placer sous `apps/mobile/android/` (ex. `release.keystore`).
2. Copier `apps/mobile/android/keystore.properties.example``keystore.properties` et renseigner mots de passe / alias (fichier non versionné).
3. Dans Android Studio : **Build > Generate Signed Bundle / APK** ou en CLI `./gradlew bundleRelease` depuis `apps/mobile/android`.
Sans `keystore.properties`, le type `release` napplique pas la signature upload — utiliser `bundleDebug` pour tests internes uniquement.
Tests physiques recommandés : navigation builder, **partager** (plugin Capacitor), lien `/r/...` après navigation vers la recette.
Retirer tout mode `server.url` / cleartext dans `apps/mobile/capacitor.config.ts` utilisé pour le dev LAN avant publication.
## 5. Google Play — métadonnées
- Compte développeur Play Console.
- Descriptions, captures d’écran téléphone, icône / feature graphic selon gabarits Google.
- Formulaire **sécurité des données** : refléter la réalité (ex. analytics dans `apps/web/src/infra/analytics.ts`, stockage local).
- URL **politique de confidentialité** si la déclaration lexige.
- Incrémenter `versionCode` / `versionName` dans `apps/mobile/android/app/build.gradle` pour chaque dépôt Play.
## 6. CI — artefact Android
Le workflow `.github/workflows/mobile-release.yml` construit un **AAB debug** (sans keystore Play) pour valider la chaîne Gradle ; la publication production reste signée localement ou via secrets CI ultérieurs.
## Hors périmètre V1 assumé
- Pas de compte cloud ni synchro serveur utilisateur : catalogue embarqué, mises à jour par nouvelles versions.
- iOS : traiter dans une release dédiée (compte Apple Developer, Xcode).
+46
View File
@@ -0,0 +1,46 @@
# Matomo et consentement cookies
## Activation Matomo
Le chargement de `matomo.js` et toute mesure daudience ne sont possibles que si **les deux** variables Vite suivantes sont définies au build :
| Variable | Exemple | Rôle |
| --------------------- | -------------------------------- | ---------------------------------------------------------------------------------------- |
| `VITE_MATOMO_URL` | `https://analytics.example.com/` | URL du répertoire hébergeant `matomo.js` (slash final recommandé ; il est ajouté sinon). |
| `VITE_MATOMO_SITE_ID` | `1` | Identifiant du site dans Matomo. |
Sans ces variables, aucune mesure Matomo nest initialisée (même après acceptation du bandeau).
Le **bandeau cookies** saffiche toutefois dès quaucun choix nest stocké dans `localStorage`, afin de recueillir le consentement avant toute activation possible du tracker.
Copier `apps/web/.env.example` vers `apps/web/.env.local` pour les essais locaux avec Matomo réel.
### Image Docker / CapRover
Les mêmes variables doivent être disponibles **pendant** `npm run build:web`. Le `Dockerfile` expose des `ARG` (valeurs vides par défaut) puis les recopie en `ENV` avant le build, sur le même principe que `PUBLIC_*` sur un autre projet.
Exemple en local :
```bash
docker build \
--build-arg VITE_MATOMO_URL=https://analytics.example.com/ \
--build-arg VITE_MATOMO_SITE_ID=1 \
-t ben-to-web .
```
Sur CapRover, configurer les **build arguments** équivalents pour limage (pas seulement les variables denvironnement runtime du conteneur).
## Comportement
1. Premier chargement : si aucun choix nest enregistré dans `localStorage` (clé `ben-to-cookie-consent-v1`), le **bandeau** saffiche.
2. **Accepter** : enregistrement du consentement ; si Matomo est configuré, chargement asynchrone de `matomo.js`, puis suivi des pages vues (SPA incluse) et des événements (`trackEvent` dans `apps/web/src/infra/analytics.ts`).
3. **Refuser** : pas de chargement du script ; pas de cookies / hits Matomo.
Le choix est persisté localement ; pour retester le bandeau, supprimer la clé dans les outils développeur ou utiliser une fenêtre privée.
## Liens utiles
- Package consent pur : `@ben-to/privacy/cookie-consent`
- Issue produit / conformité : [#39](https://github.com/kazerlelutin/ben-to/issues/39)
+99
View File
@@ -0,0 +1,99 @@
# En-têtes de sécurité et Content-Security-Policy
L'application web est durcie par défaut via le plugin Vite [`apps/web/vite-plugin-csp.ts`](../apps/web/vite-plugin-csp.ts). Il injecte une **Content-Security-Policy** dans le HTML de chaque page (et un `serve.json` en build) et émet plusieurs en-têtes de sécurité complémentaires.
## Vue d'ensemble
| En-tête | Valeur | Émis par |
| --- | --- | --- |
| `Content-Security-Policy` | `default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; connect-src 'self'; media-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; frame-src 'none'` | Plugin Vite (dev + build) |
| `X-Content-Type-Options` | `nosniff` | Plugin Vite |
| `Referrer-Policy` | `strict-origin-when-cross-origin` | Plugin Vite |
| `X-Frame-Options` | `DENY` | Plugin Vite |
| `Permissions-Policy` | `accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()` | Plugin Vite |
En **build de production**, le plugin écrit en plus `dist/serve.json` ; le serveur statique `serve` (cf. [`Dockerfile`](../Dockerfile) : `CMD ["serve", "-s", "dist", "-l", "3000"]`) applique les en-têtes déclarés.
En **dev** (Vite), le plugin injecte la CSP via `<meta http-equiv="Content-Security-Policy">` **et** pose les en-têtes de réponse via un middleware Connect. La CSP dev assouplit `script-src` et `style-src` avec `'unsafe-inline'` + `'unsafe-eval'`, et `connect-src` avec `ws:` / `wss:` pour ne pas casser HMR.
## Adapter la CSP à Matomo
Le serveur Matomo est optionnel (cf. [matomo-analytics.md](matomo-analytics.md)). Quand `VITE_MATOMO_URL` est défini au build, son origine est ajoutée à `script-src`, `connect-src` et `img-src` :
```bash
# Build local avec Matomo
VITE_MATOMO_URL=https://analytics.example.com/ \
npm run build:web
```
La CSP générée ressemblera à :
```
default-src 'self';
script-src 'self' 'https://analytics.example.com';
style-src 'self';
img-src 'self' 'https://analytics.example.com';
font-src 'self';
connect-src 'self' 'https://analytics.example.com';
media-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
frame-src 'none'
```
Sans `VITE_MATOMO_URL`, **aucune origine externe** n'est ajoutée : la surface d'attaque est minimale.
> **Pourquoi `script-src` autorise `'self'` sans `unsafe-inline` en prod ?** Vite produit un bundle compilé référencé via `<script type="module" src="/assets/...">` (même origine). Aucun script inline n'est généré. Le plugin Matomo injecte un script dynamiquement (cf. [`apps/web/src/infra/matomo.ts`](../apps/web/src/infra/matomo.ts)) : l'URL cible `https://<host Matomo>/matomo.js`, couverte par l'origine ajoutée à `script-src`.
>
> **Pourquoi `style-src` autorise `'unsafe-inline'` en prod ?** Solid pose des styles inline (`<span style="...">`) sur des nœuds existants (cf. `SpriteTile.tsx`). La directive `style-src` vise les `<style>` statiques et les `style="..."` du HTML, donc l'autorisation est nécessaire. Si on veut durcir davantage à l'avenir : extraire les styles inline vers des classes CSS et passer à `style-src 'self'` strict, ou utiliser des hash/nonces `style-src-attr 'sha256-…'`.
## Vérification manuelle
```bash
# 1) Build et smoke test local
npm run pipeline:recipes
npm run build:web
npx serve -s apps/web/dist -l 3000 &
sleep 1
# 2) Vérifier les en-têtes
curl -sI http://localhost:3000/ | grep -Ei 'content-security-policy|x-frame|x-content|referrer|permissions'
# 3) Vérifier la balise meta dans une page recette
curl -s http://localhost:3000/r/base%3Aonigiri/variant%3Aonigiri-kimchi-mozza \
| grep -i 'http-equiv="Content-Security-Policy"'
# 4) Vérifier que dist/serve.json est correct
cat apps/web/dist/serve.json
```
Côté navigateur, onglet **Network** → sélectionner la requête du document → panneau **Headers** : la section « Response Headers » doit afficher les cinq en-têtes ci-dessus. Aucune erreur CSP ne doit apparaître dans la console pour les actions courantes (navigation, partage, fermeture du bandeau cookies).
## Tests automatisés
- `apps/web/vite-plugin-csp.unit.test.ts` — helpers purs (construction CSP, sérialisation, injection HTML, `serve.json`).
- `apps/web/vite-plugin-csp.int.test.ts` — scénarios Gherkin `@int-csp-*` (`@int-csp-html-injection`, `@int-csp-matomo-origin`, `@int-csp-serve-json`).
- `tests/e2e/security-headers.e2e.ts` — vérifie en dev (Playwright + Vite middleware) la présence de la CSP et des en-têtes sur `/` et `/r/...`, et la balise meta dans le HTML rendu.
Lancer toute la chaîne :
```bash
npm run check:quality
npm run test:ci
```
## Hors scope (V1)
- Subresource Integrity (SRI) sur les bundles Vite — l'absence de CDN rend l'attaque peu probable, mais à étudier dans un ticket dédié.
- `report-uri` / `report-to` — pas de collecteur configuré sur la V1.
- CSP par nonce / hash pour autoriser des styles Solid inline en prod — non requis tant que la directive `style-src 'self'` n'engendre pas de violation observable.
## Références
- Plugin Vite : [`apps/web/vite-plugin-csp.ts`](../apps/web/vite-plugin-csp.ts)
- Plugin SEO recette (doit passer par `transformIndexHtml`) : [`apps/web/vite-plugin-recipe-seo.ts`](../apps/web/vite-plugin-recipe-seo.ts)
- Issue : [#104](https://github.com/kazerlelutin/ben-to/issues/104)
- Spec : [MDN — Content Security Policy (CSP)](https://developer.mozilla.org/docs/Web/HTTP/CSP)
- Spec : [MDN — Permissions Policy](https://developer.mozilla.org/docs/Web/HTTP/Headers/Permissions-Policy)
+30
View File
@@ -0,0 +1,30 @@
# SEO et aperçus sociaux des URLs recette (`/r/...`)
## Contrainte
Les robots des réseaux (Facebook, X, LinkedIn, etc.) lisent le **HTML de la première réponse HTTP**. Une SPA qui ne met à jour `<title>` et les balises Open Graph **quaprès exécution du JavaScript** voit souvent des aperçus génériques ou incorrects.
## Stratégie retenue : pré-rendu statique au build + middleware en développement
| Phase | Comportement |
|--------|----------------|
| **`vite build`** | Après génération de `dist/index.html`, le plugin `recipeSeoPlugin` (`apps/web/vite-plugin-recipe-seo.ts`) lit `dist/recipes/catalog.json`, construit pour **chaque couple** (`baseId`, `variantId`) du catalogue un fichier `dist/r/<baseEnc>/<variantEnc>/index.html` avec les mêmes scripts/CSS que la page daccueil, mais un `<head>` dédié (`og:*`, `twitter:*`, `canonical`, etc.). Les segments de chemin utilisent `encodeURIComponent`, comme le routeur Solid. |
| **`vite` (dev)** | Un middleware HTTP intercepte les GET `/r/:base/:variant`, recharge `public/recipes/catalog.json` et `index.html`, puis applique la même injection de métadonnées. Les tests Playwright et le débogage local voient donc des balises correctes sans produire un build. |
Ce nest **pas** une solution « meta uniquement côté client » : le HTML servu pour ces URLs contient déjà les bonnes balises.
### Déploiement
Le serveur statique ou CDN doit **servir le fichier correspondant à lURL** lorsquil existe (ex. essai `try_files $uri $uri/ ...` côté nginx, ou équivalent). Les chemins `%` dans les noms de dossiers reflètent les IDs catalogue encodés (`base:…``base%3A…`).
### Variable denvironnement
- **`BEN_TO_SITE_ORIGIN`** : origine canonique pour `og:url`, `canonical` et URLs absolues des images (défaut **`https://ben-to.fr`** au build ; en dev, défaut **`http://localhost:<port>`** sauf si cette variable est définie).
### Validation manuelle (critères ticket)
Après déploiement, utiliser par exemple [Facebook Sharing Debugger](https://developers.facebook.com/tools/debug/) ou loutil cartes Twitter/X pour une URL `/r/...` et vérifier titre, description et image recette.
### Données
Les titres et descriptions utilisent la locale **`fr`** (alignée sur `lang="fr"` du document). Limage privilégiée est `recipeCoverImageUrl`, puis `coverImageUrl`, puis le logo sous `/branding/onigiri-logo.png`.
+14
View File
@@ -0,0 +1,14 @@
# Tickets de travaux (post-MVP)
Les corps dissues prêts à lemploi sont dans ce dossier (`issue-*.body.md`).
## Créer les issues sur GitHub
1. Sauthentifier : `gh auth login`
2. Depuis la racine du dépôt : `./scripts/github/create-post-mvp-issues.sh`
Le script utilise `gh issue create` et les fichiers `docs/tickets/issue-*.body.md`.
Sans CLI : **New issue** sur GitHub et choisir un des gabarits sous « Refactor Builder SOLID », « Architecture sans barrels index », « SEO recettes OG » — ou copier-coller le contenu des fichiers `.body.md`.
Issues créées sur le dépôt de référence : [#19](https://github.com/kazerlelutin/ben-to/issues/19), [#20](https://github.com/kazerlelutin/ben-to/issues/20), [#21](https://github.com/kazerlelutin/ben-to/issues/21).
+25
View File
@@ -0,0 +1,25 @@
## Contexte
La logique et lUI du parcours builder sont concentrées dans `apps/web/src/ui/BuilderFlow.tsx` (fichier très volumineux). Le domaine pur existe déjà dans `packages/features/builder/src/domain.ts`.
## Objectif
Réduire la surface « tout-en-un » du flux builder et clarifier les frontières entre UI, état du wizard, et domaine (`@ben-to/builder`).
## Pistes techniques
- Extraire par **responsabilité unique** : étapes (base / variante / recette), barre dactions, feuille recette, navigation « Continuer », synchronisation reset (`builder-reset-bus`).
- Introduire des **hooks / ports** testables (ex. `useBuilderSteps`, `useRecipeSheet`) pour isoler les effets et la lecture du catalogue.
- **DIP** : lUI consomme le domaine (`domain.ts`) et des interfaces stables pour le catalogue, sans détails de parsing dispersés.
- Conserver / étendre les tests (`*.unit.test.ts`, e2e, Gherkin `@int-builder-*`).
## Critères dacceptation
- [ ] Aucun fichier du flux builder > ~250300 lignes sans justification documentée en commentaire de module.
- [ ] Tests unitaires / intégration et e2e verts ; pas de régression sur le parcours dans `specs/features/builder.feature`.
## Références
- `apps/web/src/ui/BuilderFlow.tsx`
- `packages/features/builder/src/domain.ts`
- `specs/features/builder.feature`
@@ -0,0 +1,28 @@
## Contexte
Les packages exposent des barrels `src/index.ts` avec `export * from "..."`. Les alias Vite dans `apps/web/vite.config.ts` pointent vers ces fichiers. Ce pattern complique le suivi des imports réels et la détection de code inutilisé.
## Objectif
Supprimer les barrels `index.ts`, migrer vers des imports **explicites**, et **verrouiller** la réintroduction du pattern (règles projet + CI).
## Travail technique
1. Remplacer les alias Vite (et chemins TypeScript si besoin) par des modules canoniques nommés (ex. `@ben-to/builder/domain`, `@ben-to/recipes/catalog`) ou par des sous-chemins `package.json` **`exports`** sans fichier nommé `index.ts`.
2. Mettre à jour tous les imports (`apps/web`, `packages/**`).
3. Supprimer les anciens `**/src/index.ts` après migration.
## Règles / outillage
- Étendre `.cursor/rules/ben-to-engineering.mdc` : pas de nouveau barrel `index.ts` ; imports depuis modules nommés.
- Optionnel : ESLint `no-restricted-imports` ou script `scripts/quality/` qui échoue si un `**/src/index.ts` barrel réapparaît.
## Critères dacceptation
- [ ] Plus de `export * from` dans des `index.ts` à la racine des packages features/shared concernés.
- [ ] CI (`check:quality` ou lint) empêche la réintroduction du pattern (selon le niveau de durcissement retenu).
## Références
- `apps/web/vite.config.ts`
- `packages/features/*/src/index.ts`, `packages/shared/*/src/index.ts`
+30
View File
@@ -0,0 +1,30 @@
## Contexte
Les métadonnées Open Graph et Twitter sont définies une seule fois dans `apps/web/index.html`. Les URLs de recette `/r/...` doivent exposer **titre**, **description** et **image** adaptés au partage.
**Contrainte SPA** : modifier `<title>` ou les meta **uniquement en JavaScript après chargement** ne suffit souvent pas pour les robots daperçu (Facebook, X, LinkedIn) : ils lisent le HTML initial.
## Objectif
Pour chaque recette partageable, fournir des métadonnées correctes : `og:title`, `og:description`, `og:image`, `og:url`, `twitter:card` (idéalement `summary_large_image` si visuel adapté), éventuellement `canonical`.
## Options à trancher (documenter le choix dans README ou `docs/`)
| Approche | Effort | Aperçus réseaux |
|----------|--------|-----------------|
| Pré-rendu statique des routes `/r/*` au build (catalogue / pipeline recettes) | Moyen | Bon pour crawlers statiques |
| Middleware / route serveur (Worker, reverse proxy, petite app Node) injectant le HTML minimal avec meta | Variable | Très bon si bien déployé |
| Meta uniquement côté client (Solid) | Faible | Insuffisant pour la majorité des partages |
Données : titres localisés, description courte, images de couverture produites par le pipeline recettes.
## Critères dacceptation
- [ ] Partager une URL `/r/...` affiche un aperçu avec **titre et image recette** (validation manuelle : Meta Sharing Debugger / Twitter Card Validator ou équivalent).
- [ ] Balises OG/Twitter cohérentes (`og:type`, `og:url`, etc.).
- [ ] Documentation de la stratégie retenue (build vs serveur).
## Références
- `apps/web/index.html`
- Parcours e2e et URL partagée dans `tests/e2e/builder.e2e.ts`