# Guide de développement

Ce document couvre les conventions de code, les règles de sécurité et la procédure de publication du package `@zen/core`.

Pour les conventions de rédaction : [LANGUE.md](./dev/LANGUE.md) et [REDACTION.md](./dev/REDACTION.md).

Pour les décisions de design et l'identité visuelle : [DESIGN.md](./DESIGN.md).

Pour l'architecture partagée (composants, icônes) : [ARCHITECTURE.md](./dev/ARCHITECTURE.md).

Pour la procédure de publication du package : [PUBLICATION.md](./dev/PUBLICATION.md).

Pour les conventions de commit : [COMMITS.md](./dev/COMMITS.md).

Pour la création de modules externes `@zen/module-*` : [MODULES.md](./MODULES.md).

> **Contexte projet** : les utilisateurs finaux créent leur projet via `npx @zen/start`, qui génère automatiquement une structure Next.js avec la plateforme déjà intégré. Lors d'une assistance ou d'une revue, partez du principe que le projet cible est déjà structuré de cette façon.

---

## Standards de code

**On corrige la cause racine, jamais les symptômes.** Si le même type d'erreur réapparaît dans un deuxième fichier après avoir corrigé le premier, c'est le signe que le vrai fix est ailleurs. Remonter la chaîne d'imports ou d'appels jusqu'à comprendre *pourquoi* ce code est atteint dans ce contexte, puis corriger à cet endroit. Patcher les fichiers un par un (whack-a-mole) masque le problème et garantit qu'un troisième cas surgira.

**Les promesses ne s'ignorent pas.** Chaque `Promise` est `await`ée ou `.catch()`ée. Une promesse silencieuse qui échoue est un bug invisible.

**Les variables d'environnement et la documentation se mettent à jour avec le code.** Toute variable ajoutée, renommée ou supprimée doit être reflétée dans `.env.example`. Toute décision architecturale ou convention nouvelle doit être documentée dans le fichier `docs/` concerné.

**Une fonction, une responsabilité.** Si elle fait deux choses, c'est deux fonctions. Si elle ne tient pas sur un écran, la découper.

**Le flux de contrôle se lit de haut en bas.** Pas de récursion non bornée, pas de callbacks imbriqués à plus de deux niveaux. Quelqu'un qui lit le code pour la première fois doit pouvoir suivre l'exécution sans se perdre.

**Les données entrantes sont suspectes.** On valide en entrée de fonction. On ne suppose pas que l'appelant a fait le travail.

**La portée des variables est minimale.** On déclare au plus près de l'usage. Pas de variables réutilisées pour deux rôles différents.

**ESLint passe sans avertissement.** Un warning ignoré aujourd'hui est un bug non détecté demain.

**Les commentaires reflètent toujours le comportement réel du code.** Un commentaire obsolète est pire qu'un commentaire absent — il induit en erreur. Quand on modifie une fonction, on met à jour son commentaire. Un commentaire qui contredit le code est un bug de documentation.

---

## Conventions d'arborescence

Une feature (`src/features/<nom>/` ou `src/core/<nom>/`) suit la règle **flat + un barrel** :
un `index.js` qui ré-exporte, des fichiers plats côte à côte pour l'implémentation. Pas de sous-dossier `lib/`, `middleware/`, `actions/` quand il ne contient qu'un ou deux fichiers — remonter directement au niveau du dossier feature.

Les sous-dossiers sont autorisés uniquement quand ils contiennent plusieurs fichiers du même rôle : `components/`, `pages/`, `templates/`, `widgets/`.

### Suffixes de runtime

Tout fichier épinglé à une frontière Next.js porte le suffixe dans son nom :

- `.server.js` → code serveur strict (peut importer `pg`, `fs`, etc.)
- `.client.js` → débute par `'use client'`
- pas de suffixe → module neutre, utilisable des deux côtés
- `actions.js` → débute par `'use server'` (server actions Next.js)

Ces suffixes ne sont pas cosmétiques : **le build les utilise comme source de vérité**. Le build compile l'intégralité de `src/` avec `bundle: false` — chaque fichier reste un module séparé, ce qui permet à Next.js de respecter les frontières RSC et `'use client'` sans que le bundler ne fusionne les modules.

**Tout fichier qui `import 'pg'`, `'fs'`, `'net'`, `'node:*'` doit porter `.server.js`.** Sans ce suffixe, le fichier est réputé neutre — un barrel client peut le ré-exporter par mégarde, et Turbopack/Webpack tracent la chaîne d'imports statiques jusqu'à `pg` dans le bundle browser. L'erreur `Module not found: Can't resolve 'dns'` côté client est typiquement causée par un fichier serveur sans suffixe atteint via un barrel mixte.

---

## Build et configuration tsup

`tsup.config.js` compile **tous les fichiers `.js` et `.jsx` de `src/`** avec `bundle: false`. Chaque fichier devient un module standalone dans `dist/` ; les imports relatifs sont préservés tels quels. La structure `dist/` reflète exactement `src/` grâce à `outbase: 'src'`.

- **Ajouter un module public = éditer seulement `package.json#exports`.** L'entry list se régénère au prochain build via un walk de `src/`.
- **Pas de bundling des fichiers internes** : les modules de registre (`registry.js`, etc.) sont des singletons — les bundler créerait une copie inline distincte et casserait le partage d'état entre les pages et les widgets.
- Les self-imports `@zen/core/*` sont générés automatiquement à partir des clés de `exports` et restent toujours dans la liste `external`.
- Les fichiers `.js` et `.jsx` sont tous traités comme du JSX via esbuild (`loader: { '.js': 'jsx' }`, `jsx: 'automatic'`) — pas besoin d'extension `.jsx` pour écrire du JSX.

---

## Étendre l'admin

L'admin utilise un **registre runtime** pour permettre aux projets consommateurs d'ajouter des widgets, des entrées de navigation, et des pages sans modifier le core.

> Pour distribuer ces extensions sous forme de package npm réutilisable plutôt que de les écrire en local, voir [MODULES.md](./MODULES.md) — chaque module `@zen/module-*` installé est auto-découvert et activé.

```js
// app/zen.extensions.js — projet consommateur
import {
  registerWidget,
  registerWidgetFetcher,
  registerNavItem,
  registerNavSection,
  registerPage,
} from '@zen/core/features/admin';
import OrdersWidget from './admin/OrdersWidget';
import OrdersPage from './admin/OrdersPage';
import { countOrders } from './admin/orders.server';

registerWidgetFetcher('orders', async () => ({ total: await countOrders() }));
registerWidget({ id: 'orders', Component: OrdersWidget, order: 20 });

registerNavSection({ id: 'commerce', title: 'Commerce', icon: 'ShoppingBag03Icon', order: 30 });
registerNavItem({ id: 'orders', label: 'Commandes', icon: 'ShoppingBag03Icon', href: '/admin/orders', sectionId: 'commerce', permission: 'orders.view' });

registerPage({ slug: 'orders', Component: OrdersPage, title: 'Commandes' });
```

L'item actif dans la sidebar et le fil d'Ariane sont calculés automatiquement à partir d'un `basePath` déduit de `href` (si `href` finit par `/list`, on retire le suffixe). Une convention `slug:edit` / `slug:new` permet de personnaliser les labels du breadcrumb sur les sous-routes — voir [src/features/admin/README.md](../src/features/admin/README.md#item-actif-et-fil-dariane) pour le détail.

```js
// app/layout.js — un seul import suffit ; les side effects enregistrent tout.
import './zen.extensions';
```

---

## Sécurité

**Données entrantes** : toute donnée externe est considérée malveillante par défaut. On valide côté serveur uniquement.

**Base de données** : uniquement des requêtes paramétrées — jamais de SQL construit par concaténation de chaînes.

**Secrets** : aucun token, clé API ou mot de passe dans le code. Tout passe par des variables d'environnement, jamais commitées.

**Erreurs exposées** : pas de stack trace, nom de table ou requête SQL retournés au client. On log côté serveur, on renvoie un message générique côté client.
