# Conventions de facto

> Conventions **observées** et répétées dans le code (2026-07-30). Les incohérences
> ne sont pas ici : elles sont signalées dans `STATE.md` (section dette).

## Langue
- **Domaine, doc, commentaires, messages de commit** : français.
- **Identifiants de code** (classes, méthodes, propriétés) : anglais technique
  (`CompanySourcingService`, `getSiren()`), concepts métier parfois francisés (`FrenchGeo`, `NafLabels`).

## Git
- Un commit atomique par unité logique ; branche par feature (`develop` courant, PR vers `main`).
- Message : `Feat : …` / `Fix : …` / `Docs : …`, en français, à l'impératif/descriptif.
  Préfixe de module quand pertinent : `Fix Prospect : …`, `Docs Prospect : …`.
- **Jamais `git add -A`** (dette CRLF) : ajouter les chemins explicitement.

## Backend PHP / Symfony
- **PSR-4** : `App\` → `src/`. Un module = un sous-namespace (`App\Service\Prospect\…`),
  répliqué à l'identique dans `Command/`, `Controller/`, `Entity/`, `Message/`, `MessageHandler/`,
  `Repository/`, `Service/`, et dans `tests/`.
- **Entités Doctrine** : attributs PHP 8 (`#[ORM\Entity(...)]`, `#[ORM\Table(name: 'snake_case')]`,
  `#[ORM\UniqueConstraint(...)]`, `#[ORM\Index(...)]`). Repository lié via `repositoryClass:`.
- **Identifiants** : `Symfony\Component\Uid\Uuid` (`#[ORM\Column(type: 'uuid', unique: true)]`),
  pas d'auto-increment sur les entités récentes.
- **Colonnes** : `Doctrine\DBAL\Types\Types::*` ; JSON → `jsonb` côté PostgreSQL.
- **Nommage tables/colonnes** : `snake_case`. Classes : `PascalCase`. Services : suffixe `Service`
  (sauf value objects comme `Lead`, `FrenchGeo`). Repositories : suffixe `Repository`.
  Commands : suffixe `Command`. CRUD admin : suffixe `CrudController`.
- **Colonnes sensibles** : chiffrées via `EncryptionService` — dimensionner la colonne sur la
  **valeur chiffrée**, pas la valeur claire (cf. `Fix 7c81df5`).
- **Async** : intentions via `Message/` (DTO) + `MessageHandler/` (handler), pipeline idempotent.
- **Commandes console** : verbe métier (`prospect:mine`, `crawl:run`), appelées par les crons.

## Base de données
- **MySQL** = EntityManager principal (core, billing, admin, auth, files, thinking).
- **PostgreSQL distant (VPS)** = modules `Prospect` et `Search` (DSN dédiés).
- **Migrations** : Doctrine pour MySQL (`migrations/VersionXXX.php`), **à la main** pour
  PostgreSQL (`migrations/postgres/*.sql`). Le registre Doctrine est **unique** et vit sur MySQL :
  une migration PostgreSQL écrite en `VersionXXX.php` y est inscrite puis tentée sur MySQL. C'est
  arrivé, et ça a bloqué la file trois semaines. Voir `migrations/postgres/README.md`.
- **Interdit** : `doctrine:schema:update --force` (voir `CLAUDE.md` / règles dures).

### ⚠️ Nommage des entités — le partage par namespace est un partage par **préfixe de chaîne**

`doctrine.yaml` répartit les entités ainsi :

| Namespace | Base |
|---|---|
| `App\Entity\Search` | PostgreSQL `ldico` (VPS) |
| `App\Entity\Prospect` | PostgreSQL `prospect` (VPS) |
| `App\Entity` | MySQL (produit) |

**Doctrine compare des chaînes, pas des segments de namespace.** Une classe
`App\Entity\SearchThread` commence par `App\Entity\Search` : elle est revendiquée par
l'EntityManager PostgreSQL, dont le répertoire ne la contient pas. Elle finit **mappée nulle
part** — sans exception, sans erreur au démarrage, sans test rouge. Les seuls symptômes sont
indirects : `mapping:info` l'ignore, et `schema:update` propose de **supprimer sa table**.

> **Règle : aucune classe placée directement dans `src/Entity/` ne doit commencer par `Search`
> ni par `Prospect`.** Rencontré en direct le 2026-08-05 ; `tests/Entity/NommageDesEntitesTest.php`
> le garde désormais.

## Frontend (`frontend/`)
- React 19 + TypeScript strict + Vite. Composants `PascalCase.tsx`.
- État global : stores Zustand (`src/stores`). Appels API : axios (`src/services`).
- Tests : Vitest + Testing Library (`*.test.tsx`), `npm run test`.
- Build : `tsc -b && vite build`. Lint : ESLint (`npm run lint`).
- **CSS** : jetons de `styles/tokens.css` uniquement, jamais de valeur en dur — c'est ce qui fait
  suivre le thème sombre sans une ligne de plus. Une feuille par composant, préfixe de classe
  propre (`pv-`, `tc-`, `sr-`…) ; `styles/index.css` (8 000 lignes) est **historique**, on n'y
  ajoute que ce qui touche un écran qu'il porte déjà.
- **Contenu rédigé : `styles/prose.css`**, classe `.prose`. Titres, listes, tableaux, citations,
  images et code d'un texte long y vivent **une seule fois**, pour les trois surfaces qui en
  rendent — viewer de page, réponse du chat, éditeur du Screen. L'appelant compose
  (`className="prose pv-article"`) et ne déclare que ses écarts, chacun avec sa raison. Trois
  feuilles auraient divergé au premier ajustement : c'est déjà arrivé dans ce dépôt avec
  l'extraction de texte (`HtmlText`, 2026-08-07).

## Secrets & config
- Valeurs par défaut (non secrètes) dans `.env` versionné (convention Symfony) ;
  **secrets réels dans `.env.local`** (gitignoré). ⚠️ non respecté aujourd'hui — voir dette.
- Variables par module : `MISTRAL_*`, `STRIPE_*`, `PROSPECT_DATABASE_URL`, `SEARCH_DATABASE_URL`,
  `OCR_REMOTE_URL`, `STAAN_*`/`MOJEEK_*`/`SEARXNG_*` (cascade de recherche).

## Tests
- PHPUnit côté backend (`bin/phpunit`, `make test`), miroir de `src/` sous `tests/`.
- Vitest côté frontend.
- Boucle de vérification obligatoire avant de rendre la main (voir `CLAUDE.md`).
