# PROGRESS.md — État d'avancement TCA Industries

> Source de vérité de l'avancement du projet. Mis à jour à la fin de chaque session de travail.
> Pour les règles projet : voir `CLAUDE.md`. Pour le détail des pages : `docs/pages.md`. Pour la stratégie SEO : `docs/seo-strategy.md`. Pour les migrations DB en attente : `MIGRATIONS_NEEDED.md`.

---

## Dernière session — 2026-08-02 — Réécriture des 22 articles de blog (phase 2)

**Les 22 articles sont réécrits, contrôlés et en base, tous en brouillon.** Rien n'est publié, le sitemap ne contient aucune URL d'article.

**Bloc A, correctifs de socle**
- `CLAUDE.md` : la règle de longueur §6.6 renvoie désormais à `docs/redaction-articles.md`, source unique du standard. Quatre autres contradictions corrigées (séparateur de title en cadratin §6.1, plafond H2 §6.4, longueur des réponses FAQ §6.12, longueurs cibles §7.2 et §7.3).
- `docs/client.md` : zone d'intervention corrigée (la Lozère n'est pas limitrophe du Puy-de-Dôme, 6 limitrophes + Lozère), rattachement des villes fixé (Vichy dans l'Allier, Brioude en Haute-Loire). Même correction dans `docs/faq.md`, `docs/seo-strategy.md` (horaires GBP) et 2 templates de pages piliers.
- `docs/faq.md` : les 31 réponses de plus de 4 phrases ramenées au format, plus une erreur technique corrigée (mégohmmètre et court-circuit entre spires).
- ⚠️ **Contradiction non tranchée, documentée dans `client.md` §1** : « depuis 1983 » (lot client juin 2026), « 30 ans » (fiche d'origine) et création en 2019 sont incompatibles. Aucune ancienneté n'est écrite dans les nouveaux articles en attendant l'arbitrage TCA.
- 🆕 Champs `Article::metaTitle`, `metaDescription`, `reviewNotes` + `Version20260802094500` (appliquée en local, voir `MIGRATIONS_NEEDED.md`).
- 🆕 `templates/blog/_review_panel.html.twig` : encart de relecture TCA, rendu hors production uniquement.
- 🆕 `app:article:sync` : upsert par slug depuis les fixtures, sans purge, dry run par défaut.
- 🆕 Style des tableaux d'article dans `public/css/tca/site.css`, wrapper `.tca-table-wrap`, défilement horizontal sur mobile et première colonne collante sous 640 px.
- `BlogController` : hors production un brouillon est servi sur son URL directe pour la relecture, en production il reste en 404.

**Bloc B, réécriture**
- Plan de répartition des requêtes dans `var/seo/plan-requetes.md`. 9 articles changent de requête principale, les 8 qui cannibalisaient leur pilier plus A12.
- 3 doublons traités en différenciation : A03 et A12, A14 et A17, A13 et A18.
- Maillage recadré, plus aucun article orphelin (4 l'étaient).
- Corps entre 1 523 et 1 975 mots, 22 articles sur 22 conformes à la grille (longueur, réponse directe 40-80 mots, sections H2 au-dessus de 150 mots, zéro cadratin, zéro deux-points séparateur, 1 lien pilier + 2 à 3 liens blog, FAQ 3 à 5 paires, title et meta dans les bornes).
- Les 14 anomalies techniques bloquantes de l'audit sont corrigées, dont la règle de couplage étoile/triangle inversée.
- **257 points techniques à valider et 109 questions à l'atelier** sont dans les encarts de relecture, visibles en haut de chaque article hors production.

**À faire côté TCA** : relire les 22 encarts, répondre aux 109 questions, trancher l'ancienneté. **À faire côté Gabin** : arbitrer la page pilier locale `/clermont-ferrand-auvergne/` (conditionne A17), puis passer les articles en publié.

---

## Session — 2026-08-01 — Audit de couverture des 22 articles de blog (phase 1)

Audit seul, **aucun article modifié**, aucune réécriture engagée. En attente de validation de Gabin avant la phase 2.

- Nouvelle commande `app:article:export` (`src/Command/ExportArticlesCommand.php`) : exporte chaque article en Markdown dans `var/seo/articles/` (slug, titre, meta, catégorie, pillarRouteName, alt image, FAQ, contenu). Lecture seule.
- Rapport livré : `var/seo/audit-articles.md`. Tableau récapitulatif, 3 analyses transversales (cannibalisation, trous de couverture, cohérence des clusters), constats transverses au corpus, vérification factuelle, chiffres non validés consolidés, questionnaire TCA consolidé, ordre de passage recommandé, 22 fiches détaillées par cluster.
- Verdict : **22 articles sur 22 en catégorie C**, réécriture complète. Deux conditions se déclenchent partout, termes techniques non expliqués et contenu interchangeable avec un concurrent.
- **14 anomalies techniques bloquantes**, dont une règle de couplage étoile/triangle inversée dans `branchement-moteur-triphase-380v` qui détruit un moteur si un lecteur l'applique. Ces articles ne doivent pas être publiés en l'état.
- Deux fichiers de contexte à corriger avant la phase 2 : `docs/client.md` (Lozère listée à tort comme département limitrophe du Puy-de-Dôme, erreur reprise dans 4 articles) et `docs/faq.md` (31 réponses sur 101 dépassent 4 phrases).
- Contradiction à arbitrer : `CLAUDE.md` §6.6 fixe les articles à 800-1200 mots, `docs/redaction-articles.md` §7 à 1100-1600. L'audit a appliqué le second. 8 articles changent de statut selon l'arbitrage.

---

## Dernière session — 2026-07-28 (suite) — Correctifs front mobile + sommaire d'article

Cinq points remontés par Gabin, tous corrigés et vérifiés au navigateur (Playwright, 320 / 375 / 414 px).

1. **Dates d'article en anglais** → `|date('d F Y')` n'est pas localisé. Remplacé par `|format_date(pattern='dd MMMM y', locale='fr')` (`twig/intl-extra`, déjà installé). Rend « Publié le 02 mai 2026 · Mis à jour le 28 juillet 2026 ».
2. **Sommaire d'article** → `.tca-article-toc` n'avait **aucun style** : il héritait des puces et des liens soulignés du corps d'article. Encart gris à filet doré, libellé en capitales, entrées numérotées (compteur CSS), 2 colonnes ≥ 640 px. Contrastes vérifiés au calcul WCAG : le doré sur fond clair ne donnait que 2,6:1, donc libellé en pétrole (10,4:1) et numéros en gris 600 (7:1) ; l'accent de marque passe par la bordure. Ajout de `scroll-margin-top` pour que les ancres n'atterrissent pas sous le header collant — les 7 ancres testées tombent à 16 px (mobile) / 19 px (desktop) sous le header.
3. **Menu mobile rognant le header** → le drawer était figé à `inset: 64px` alors que le header mobile fait 111 px. Le script de `_nav.html.twig` aligne désormais le bord haut du drawer sur la hauteur mesurée du header (au chargement, au redimensionnement et à l'ouverture) ; la variable `--tca-header-h-mobile` ne sert plus que de repli avant exécution du JS.
4. **Bouton d'appel du menu mobile** → `.tca-mobile-nav a` s'appliquait aussi au CTA et écrasait le bouton (`display:block`, padding horizontal supprimé, bordure). Règle limitée à `.tca-mobile-nav ul a`. Deux corrections annexes : `.tca a { color: inherit }` (0,1,1) l'emportait sur `.tca-btn--gold` (0,1,0) et rendait le libellé blanc sur doré ; et le bouton flottant « intervention » (z-index 950) recouvrait le CTA — masqué via `body.tca-nav-open`.
5. **Scroll horizontal en mobile** → deux causes distinctes. Le cadre décoratif `.tca-split__image::before` débordait de 24 px alors que la marge du conteneur mobile n'est que de 20 px (+4 px sur toutes les pages services) : décalage passé à 12 px sous 768 px. Et sur `/contact/`, `white-space: nowrap` hérité de `.tca-btn` imposait sa largeur au formulaire (+18 px à 320 px) : les boutons `--block` peuvent désormais replier leur libellé.

**Bonus corrigé au passage** : le `<img>` du logo déclarait `width="220" height="56"` alors que le viewBox du SVG est `817.64 × 476.18`. La boîte réservée avant chargement était au mauvais format (CLS). Ratio corrigé dans le header et le footer ; rendu inchangé (197×115 en desktop).

Vérifié : **16 pages × 3 largeurs = 48 contrôles, zéro débordement horizontal** (contre +4 px sur les services et +18 px sur contact avant correctifs, écart reproduit en A/B). Desktop inchangé (header 151 px, logo 197×115).

⚠️ **Point d'attention hors périmètre** : `.tca-eyebrow` est en doré (`#D8AB65`) et descend à **2,6:1** sur les fonds clairs — échec du critère 4,5:1 de CLAUDE.md §6.16, présent sur tout le site (accueil, services, à-propos). À arbitrer : assombrir le doré des eyebrows sur fond clair, ou les passer en pétrole comme je l'ai fait pour le sommaire. Je n'ai rien touché en dehors du sommaire.

---

## Session — 2026-07-28 — Blog : FAQ d'article éditable en texte simple dans le BO

Les FAQ d'articles étaient collées en HTML dans le champ « Contenu » ; le JSON-LD `FAQPage` était reconstitué en reparsant ce HTML. Elles ont désormais leur propre champ, comme les pages services.

- **Colonne `article.faq`** (JSON) : liste de paires `{q, a}` en texte brut. Le setter normalise (CRLF → LF, lignes vides supprimées, ré-indexation).
- **BO** : carte « FAQ de l'article » avec lignes répétables (ajouter / supprimer / monter / descendre), question en `input` et réponse en `textarea`. Aucun HTML à coller ; une ligne vide dans la réponse crée un paragraphe.
- **Front** : accordéon rendu par `blog/article.html.twig` avec exactement les classes `tca-faq` / `tca-faq__item` / `tca-faq__question` / `tca-faq__answer` des pages services, dans `<section id="faq">` (l'ancre du sommaire reste valide). JSON-LD `FAQPage` via `schema_faq(article.faq)`, avec repli sur l'extraction HTML pour un article pas encore migré.
- **Anti-doublon** : filtre `strip_faq_section` — si `article.faq` est rempli, l'ancien bloc FAQ resté dans le HTML est retiré au rendu.
- **Migration du contenu** : commande `app:article:extract-faq` (dry run par défaut, `--force` pour écrire) + extraction au chargement des fixtures.
- **Dirty gate** conservé : `faq` au snapshot serveur, signature de collection côté JS (détecte ajout, suppression et réordonnancement). Un piège corrigé au passage — MySQL réordonne les clés des objets JSON stockés, donc `Article::getFaq()` réordonne chaque paire, sinon le `===` du snapshot voyait une modification à chaque enregistrement.

**Migration exécutée en session** (exception ponctuelle à CLAUDE.md §3.1, à la demande explicite de Gabin ; sauvegarde préalable dans `var/backup/tcaindustries-avant-faq.sql`) : `Version20260728142850` + `app:article:extract-faq --force` → **22 articles migrés, 101 paires, 0 résidu HTML**, 22 articles intacts dont 6 publiés.

Vérifié en rendu réel : les 6 articles publiés en 200 avec 1 `<h1>`, 1 `<section id="faq">` et exactement 1 JSON-LD `FAQPage` ; accueil, `/blog/`, `/contact/`, `/a-propos/`, `/sitemap.xml`, pages services en 200. Formulaire BO rendu authentifié (4 questions en texte brut, prototype, bouton d'ajout) et aller-retour d'enregistrement testé : POST identique → « Aucune modification détectée », POST +1 question → enregistrée avec ligne vide ignorée, CRLF normalisé et double paragraphe conservé.

⚠️ **Deux dérives préexistantes relevées** (non traitées, voir `MIGRATIONS_NEEDED.md`) : `DATABASE_URL` déclare MariaDB alors que le serveur est MySQL 8.4.7 ; toutes les tables sont en MyISAM, d'où les deux clés étrangères fantômes que chaque `make:migration` repropose.

---

## Session — 2026-07-02 (suite 2) — Photos : cartes « Autres services » + garniture

Lot de retouches photo (mêmes sources déjà à la racine ; comptage haut→bas / gauche→droite) :
- **Blog** : demandé Photo 1=IMG_6636, 2=IMG_6656, 3=IMG_6655 → **déjà conforme** (conséquence des vignettes changées à la session précédente + article piscine passé sur la Hayward). Aucun changement.
- **Photo de contenu** : `/pompe-piscine-relevage/` position 3 (bloc `prestations_image`) → nouvelle **garniture mécanique** (`atelier/atelier-garniture-mecanique-pompe-tca.webp`, converti de `Garniture_mécanique.jpeg`, 1100 px, 60 Ko).
- **Cartes « Autres services » (positions 6/7/8) rendues surchargeables par page** : ces cartes viennent d'un catalogue partagé (`service.html.twig`), or le client veut la **même carte avec des photos différentes selon la page**. Ajout d'un map optionnel `related_services_images` (slug → image) par page, avec repli sur le catalogue. Surcharges posées :
  - `/pompes-industrielles/` : carte piscine → Kripsol (image_7865), carte immergée → inox flotteur (IMG_6636)
  - `/pompe-piscine-relevage/` : carte pompes indus → motopompe (IMG_7037), carte immergée → chantier crépine (image_2348)
  - `/reparation-pompe-immergee/` : carte pompes indus → motopompe (IMG_7037), carte piscine → chantier crépine (image_2348)
  - `/maintenance-industrielle/` : carte pompes indus → Wilo verte (IMG_2782)
- Toutes les images de cartes réutilisent des WebP déjà convertis (98–139 Ko, ≤ limite 150 Ko), paysage → pas de déformation (`object-fit: cover`). Vérifié en rendu réel : chaque position correspond exactement à la demande, lint Twig OK.

⚠️ **Actions Gabin (session photos)** :
1. **Doublon sur `/reparation-pompe-immergee/`** : la photo demandée en position 7 (image_2348, chantier crépine) est **déjà la photo 2 (intro)** de cette même page → elle apparaît 2× sur la page (haut + bas). Fait comme demandé ; si tu veux éviter la répétition, dis-moi quelle autre photo mettre sur cette carte.
2. Si tu préfères **une seule photo par carte** partout (au lieu de la variation par page), dis-le : je retire les surcharges et on fixe une image par service dans le catalogue.
3. Sources JPG/JPEG/HEIC toujours à la racine de `public/images/` (dont `Garniture_mécanique.jpeg`) — à sortir du webroot après validation visuelle.

---

## Session — 2026-07-02 (suite) — Blog : FAQ complètes, photo piscine, 8 nouveaux articles

Trois demandes client traitées (branche `gabin/modifs`) :
- **Photo article piscine** (`/blog/reparation-pompe-piscine-diagnostic-remise-en-etat/`) → Hayward (`hero-pompe-piscine-hayward-tca.webp`, ex IMG_6655), fixtures + base. Résout au passage le doublon Kripsol sur l'accueil (carte service 5 = Kripsol, vignette blog = Hayward).
- **FAQ des 14 articles existants rédigées** (les placeholders « ⚠️ à compléter » sont tous remplacés) : 3 à 5 Q/R par article, réponses 40-80 mots, accordéon `<details>` identique aux pages services, questions dédupliquées des FAQ services et complémentaires au corps. Production par 44 agents (rédacteur + vérificateur adversarial par article) avec contrôles déterministes (mentions interdites, longueurs, liens). **JSON-LD `FAQPage` ajouté aux articles** : extraction serveur depuis l'accordéon (`SchemaService::faqFromContentHtml()` + `schema_faq_from_content()` dans `blog/article.html.twig`) — validé en rendu réel.
- **8 nouveaux articles** (batch4 des fixtures, `is_published = false`, calendrier complété à 3/mois juil→déc, voir `docs/seo-strategy.md` §4.8) : moteur qui chauffe (18/07), garniture mécanique (19/09), hivernage pompe piscine (03/10), courroie de transmission (17/10), pompe vide-cave (07/11), roulement moteur (14/11), graissage roulements (21/11), gel des pompes (05/12). 800-1200 mots hors FAQ, structure complète (sommaire, h2 ancrés, maillage pilier + 2-3 articles antérieurs, FAQ). Décembre sera complété par les 2 sujets data GSC (articles 16 et 18).

Corrections de contenu **existant** au passage (contraintes client 2026-06-11) : excerpts bobinage (« classes d'isolation », « fourchettes de prix ») et piscine (« tarifs indicatifs ») réécrits ; « incontournable » retiré de l'article variateur ; promesses de délais inventées purgées des articles visserie et Puy-de-Dôme (« le jour même », « dans l'heure », « 24 à 48 heures », « déplacement dans la journée ») ; **anciens horaires en dur corrigés dans l'article visserie** ; **sitemap corrigé** — les articles publiés n'y figuraient pas (`SitemapController` ne lisait que `PageSeo`), désormais inclus automatiquement avec lastmod/changefreq/priority.

Base synchronisée depuis les fixtures (upsert par slug : 8 créés, 14 mis à jour — jamais `fixtures:load`, qui purgerait la base). `docs/faq.md` complété avec les 22 FAQ d'articles. Vérifié : `php -l` OK, lint Twig OK, rendu réel des 6 articles publiés + 1 nouvel article (publication temporaire) : accordéon, JSON-LD (4 blocs valides/page), 1 seul h1, sitemap 24 URLs.

**Liens blog « vers le futur » — résolu proprement (filtre self-healing)** : plusieurs articles publiés pointent vers des articles au calendrier plus tardif (ex. branchement→variateur, 220V→dépannage). Comme le 1er article ne peut, à sa date, lier que « vers le futur » (règle §6.8 des 2-3 articles liés), on ne peut pas simplement supprimer/repointer. Solution : filtre Twig `resolve_blog_links` (`src/Twig/BlogLinkExtension.php` + `ArticleRepository::findPublishedSlugs()`) appliqué à `article.content` dans `blog/article.html.twig`. Il neutralise en texte simple (`<span class="tca-link-pending">`, CSS neutre) tout lien vers un article non publié, et le **réactive automatiquement** dès que la cible passe en ligne. Vérifié en live : 0 lien 404 sur les 6 articles publiés, réactivation testée en publiant/dépubliant variateur. Aucun contenu d'article modifié, donc pas de resync nécessaire.

⚠️ **Actions Gabin (session blog)** :
1. **Incohérence de délai bobinage** (préexistant) : l'article dit « 3 à 7 jours ouvrés », la page service « 5 à 10 jours » (et sa FAQ affiche une fourchette 350-700 € — hors contrainte piscine donc tolérée, mais à confirmer). Quel délai est le bon ? *(tu m'as dit que tu verras avec le client et modifieras à la main.)*
2. Les 8 nouveaux articles sont dépubliés : relire, ajuster les dates si besoin, puis publier via le BO (`/security/dashboard/articles`) le moment venu. Le filtre ci-dessus fait que leurs liens croisés s'activent d'eux-mêmes au fil des publications.
3. Mots-clés des 8 nouveaux sujets choisis par cohérence de cluster (l'export Ahrefs initial est épuisé) — à confronter aux données GSC dès que possible.

---

## Session — 2026-07-02 — lot de modifs client (textes, photos, horaires)

Neuf demandes client traitées d'un coup (branche `gabin/modifs`) :
- **Accueil, 3 métiers** : liste comptoir réordonnée (roulements, paliers, courroies, poulies, chaînes, pignons, joints, lubrifiants, outillage, EPI) ; « équilibrage dynamique » retiré de la carte atelier ; zone (« toute l'Auvergne et 7 départements limitrophes ») retirée de la carte dépannage.
- **Bouton « Demander un devis gratuit » → « Demander un devis »** (hero accueil + wrapper service). Restent volontairement (hors périmètre bouton, à valider) : « Devis gratuit sous 24 h » (hero bobinage), « Devis gratuit » (CTA articles blog), « devis gratuit » (contenus articles en base).
- **Bloc urgence** : « le jour même… Auvergne et Massif Central » → « dans les meilleurs délais en fonction de la criticité et de l'urgence » (composant partagé accueil + 10 pages services).
- **Réducteurs** : « Analyse vibratoire et acoustique. » retiré du processus.
- **Photos remplacées** (14 nouvelles photos client converties en WebP ≤150 Ko, héros ≤300 Ko, noms kebab-case, alt réécrits) :
  - `/reparation-moteur-electrique/` hero → stator multimètre (`hero-stator-bobinage-test-multimetre-tca.webp`, ex 0502)
  - `/pompes-industrielles/` : hero → motopompe MEB (ex IMG_7037.heic), intro → Wilo verte (ex IMG_2782), prestations → surpresseur (ex « Surpresseur 000020477 »), pourquoi → pompe relevage fonte (ex IMG_6656)
  - `/pompe-piscine-relevage/` : hero → Hayward (ex IMG_6655), intro → multicellulaire pressostat (ex image_2354), pourquoi → relevage fonte (IMG_6656 réutilisée)
  - `/reparation-pompe-immergee/` : hero → Tsurumi chantier (ex IMG_3458), intro → chantier crépine (ex image_2348), pourquoi → immergée chantier palette (ex IMG_5641). ⚠️ le message client disait « pompe-piscine-relevage » deux fois (items 7 et 8) — l'item 8 a été affecté à la page pompe immergée d'après le contenu des photos (pompes de chantier), à confirmer.
  - **Blog accueil (photos 11/12/13)** : images des 3 derniers articles publiés mises à jour **en base + fixtures** — #36 immergée → pompe inox flotteur (ex IMG_6636), #35 relevage → relevage fonte (IMG_6656), #34 piscine → Kripsol (ex image_7865).
  - **Accueil, grille « Domaines d'expertise »** (précision client reçue en cours de session) : carte 4 pompes industrielles → motopompe MEB (variante carte allégée `products/groupe-motopompe-centrifuge-tca.webp`, 98 Ko), carte 5 pompes particuliers → Kripsol (ex image_7865), carte 9 fournitures industrielles → rayonnage roulements FAG (ex 0028). ⚠️ La Kripsol apparaît 2× sur l'accueil (carte 5 + vignette article #34) — voir actions.
  - **Photo triée en réserve** (convertie, non branchée) : `products/pignons-chaine-cartes-visite-tca.webp` (ex 0052).
- **Horaires magasin** : lun–jeu 8h–17h en continu, ven 8h–12h. Appliqué : footer, page contact (2 blocs), à-propos, JSON-LD `SchemaService` (OpeningHoursSpecification), `docs/client.md`, `ArticleFixtures` + contenus articles 34 et 44 en base (SQL REPLACE ciblé, pas de migration).
- Vérifié : lint Twig 103 fichiers OK, `php -l` OK, rendu réel testé (serveur PHP local : accueil, 4 pages services, réducteurs, contact, JSON-LD).

⚠️ **Actions Gabin** :
1. Les originaux (JPG/JPEG/HEIC, ~35 Mo) sont toujours dans `public/images/` à la racine — à supprimer après validation visuelle (pas de suppression sans accord).
2. « Équilibrage dynamique » retiré de l'accueil/moteur/à-propos, mais toujours présent sur la page bobinage (schema, subtitle, FAQ) : TCA fait-il encore l'équilibrage en interne ? À clarifier avec le client.
3. Hero accueil inchangé (« cartes de visite ») conformément à la consigne ; la nouvelle photo pignons/cartes (ex 0052) pourrait le remplacer si souhaité.
4. ~~La photo Kripsol est visible 2× sur l'accueil~~ → **Résolu (session suivante)** : vignette blog #34 passée sur la Hayward à la demande du client. Reste ouvert : les cartes « Autres services » (catalogue de `pages/service.html.twig`) affichent encore les anciennes photos pour ces 3 services — à synchroniser si souhaité.

---

## Session — 2026-06-11 (suite) — réponses client intégrées (2e lot)

Le client a répondu aux 17 questions. Corrections appliquées :
- **Q1 Compte-rendu écrit : NON** — affirmation retirée partout (moteur, bobinage, réducteurs, pompes indus, maintenance).
- **Q2 Diagnostic : aucune mention gratuit/payant** — toutes les mentions « diagnostic gratuit » remplacées par du neutre (« diagnostic en atelier ou sur site ») sur 6 pages + fixtures SEO. FAQ « Le diagnostic est-il payant ? » supprimée (pages moteur + piscine). « Devis gratuit » conservé (non contesté, standard).
- **Q10 Prix piscine** — fourchettes de prix supprimées, réponse « déterminé après diagnostic ».
- **Q13/14 Variateurs : vente + réparation uniquement, aucune marque citée** — page entièrement réécrite (hero, process, cartes, prestations, FAQ, zone), vignettes accueil/grille corrigées, fixtures SEO mises à jour.
- **Q15 Classes d'isolation : tout supprimé** — pages bobinage/moteur/à-propos + article blog bobinage (section entière retirée) + article réparation 220V + SEO.
- **Q16 Courroies : catégories au lieu des modèles** — « trapézoïdales, crantées, striées et plates » + invitation à appeler en cas de doute.
- **Bonus Pompes à vide : pas du tout** — 3 mentions génériques restantes retirées (schema, hero, carte).
- **Blog : 2 articles supprimés des fixtures** — « Maintenance pont roulant » et « Dépannage pompe à chaleur » (services non proposés) + liens croisés purgés dans 3 autres articles + article variateur corrigé (« sur site » → atelier, vente). **15 articles restants.**

⚠️ **Actions Gabin** :
1. Si la table `article` est déjà chargée en base : supprimer les 2 articles dans le BO (`/security/dashboard/articles`) ou `doctrine:fixtures:load`. Idem pour les `PageSeo` modifiés (reload fixtures ou édition via `/admin/seo`).
2. ~~FAQ pompe piscine tombée à 3 Q/A~~ → **Résolu** : Q/A « Peut-on changer les roulements d'une pompe de piscine ? » ajoutée (basée sur les recherches associées Google fournies par Gabin : « changer roulement pompe piscine » ×4, « turbine bloquée »). À reporter dans `docs/faq.md` à sa création.
3. ~~Rappel : mail de Vincent → logos marques~~ → **Traité (2026-06-12)** : 24 logos identifiés et convertis en WebP 400×200 (`public/images/marques/`), `MarqueFixtures` réécrit (24 marques actives dans l'ordre client + 5 anciennes masquées sans logo : Sermes, Pedrollo, DAB, Koyo, Weicon), `width`/`height` ajoutés au composant marquee (CLS). Recharger les fixtures pour voir le bandeau. **Avis clients gérables en BO (option 2 validée par Gabin)** : entité `Testimonial` + CRUD complet architecture Bart sous `/security/dashboard/avis` (liste drag & drop, toggle affiché/masqué, modal suppression, exports CSV/SQL, formulaires AJAX avec dirty-gate), fonction Twig `testimonials()`, lien sidebar. Homepage branchée dessus avec fallback : tant qu'aucun avis actif, les placeholders actuels restent. Fixtures : 5 brouillons masqués à compléter avec les vrais avis Google puis activer. ⚠️ **Migration requise** (table `testimonial`) — voir MIGRATIONS_NEEDED.md.

**Marques texte (GO Gabin)** : phrase « Nous travaillons également avec les marques SEW Usocome, Leroy-Somer, KSB, Flygt, Grindex et Facom » ajoutée sous le bandeau (`marques-distribuees.html.twig`) — visible accueil + à-propos + 10 pages services. ⚠️ Orthographe « Flygt » à confirmer par le client (il écrit « FLYGHT »). **Reste** : sortir `public/logo_bandeau_tca/` (2,7 Mo de PNG sources) du webroot. `docs/faq.md` créé avec les 4 Q/R piscine sourcées.
4. `docs/seo-strategy.md` : article 9 « Maintenance pont roulant » officiellement annulé.

---

## Session — 2026-06-11 (retour client — 1er lot de corrections)

Le client a envoyé un compte-rendu de corrections (~30 points). Triage réalisé page par page.

**Corrections sûres appliquées** (instructions claires et confirmées) :
- **Maintenance pont roulant supprimée** (TCA ne le fait pas) — page Réducteurs : schema, hero, intro, carte « Pourquoi choisir » (remplacée par « Devis comparatif »), FAQ pont roulant retirée. ⚠️ Contredisait `docs/client.md` — le client prime. Article blog 9 « Maintenance pont roulant » du calendrier éditorial **à annuler**.
- **Pompes à vide à anneau liquide et à palettes** retirées de la page Pompes industrielles.
- **Pompes à chaleur (PAC) entièrement supprimées** — page Pompes particuliers (schema, titre hero, intro, lead, prestation, FAQ PAC) + vignettes accueil et grille services.
- **Courroies trapézoïdales** : formulation élargie (plus limitée à SPZ/SPA/SPB/SPC).

**En attente du retour client** (message de questions préparé, pas encore traité) : diagnostic gratuit/payant (règle globale), compte-rendu écrit oui/non, multicellulaires, « faut-il amener la pompe », installations difficiles à arrêter, coût pompe piscine, process pompe immergée + « remonter la pompe vous-même », **contradiction variateurs (installe ou non)**, marques variateurs, classes B/F/H (portée), contenu bloc « Pourquoi choisir réducteurs ».

**Point ouvert à arbitrer** : il reste 3 mentions génériques « à vide » sur la page Pompes industrielles (schema, hero, carte). Vérifier avec le client si TCA traite OUI ou NON les pompes à vide en général.

**Photos à fournir par le client** : rebobinage, types de réducteur, « pourquoi choisir réducteurs », toutes pompes industrielles (actuellement photos de moteurs), pompe piscine, toutes pompes immergées, « pourquoi confier maintenance ».

**Non reproduit** : « bug d'affichage après avis clients » → aucun bug détecté, point écarté.

**Back-office (même session)** :
- FAB « Intervention sur site » masqué dans tout le BO (`block fab_intervention` vidé dans `admin/_layout.html.twig`, même principe que navbar/footer/cookie_banner).
- **Page marques alignée sur l'architecture Bart** (`/security/dashboard/social` comme modèle) : liste avec drag & drop reorder AJAX, toggles affiché/masqué AJAX, recherche + filtre statut + tri, modal de suppression, exports CSV/SQL ; formulaires new/edit refaits (adm-form-card, upload zone + cropper pour le logo, soumission AJAX, dirty-form gate avec bouton grisé) ; fiche show alignée. `MarqueType` réduit à name/category/websiteUrl (position = drag & drop, statut = toggle, logo = cropper base64). Nouveaux JS : `admin-marque-list.js`, `admin-marque-edit.js`, `admin-marque-new.js`. Cause du rendu « HTML brut » d'avant : classes CSS inexistantes (`adm-btn--primary`, `adm-form__grid`).
- **Page articles alignée sur l'architecture Bart** : contrôleur déplacé de `/mon-espace-client/articles` vers `/security/dashboard/articles` (noms de routes inchangés), branches AJAX new/edit (collectErrors 422, dirty-gate JSON), upload image à la une via cropper base64 (`uploads/articles/`, champ chemin manuel conservé pour les photos `/images/`), liste avec recherche/filtres/tri côté client (plus de rechargement), toasts + rollback sur le toggle publier. **Bug corrigé** : la modal de suppression utilisait la classe `active` au lieu de `open` → le bouton supprimer ne faisait rien (index + show). Nouveaux JS : `admin-article-list.js`, `admin-article-edit.js`, `admin-article-new.js`.

---

## Dernière session — 2026-06-03

**Démarrage** : 12 pages piliers livrées, 1 article placeholder, templates blog OK mais SEO articles manquant.
**Fin de session** : 16 articles du calendrier éditorial écrits (articles 1–15 + 17 ; articles 16 et 18 data-driven GSC) + fix SEO template article (meta description, canonical, OG, Twitter via block head_extra) + block head_extra ajouté dans base.html.twig.

---

## Dernière session — 2026-05-19

**Démarrage** : projet vide (Vultek moule Symfony 7.4 + thème Skeleton).
**Fin de session** : 12 pages piliers TCA toutes en ligne (HTTP 200) + Contact refondu + Blog complet (entité + templates + fixture) + pages légales TCA + design system Luciole Studio appliqué partout.

---

## Pages livrées

### Pages piliers (12/12) — toutes en ligne

| # | URL | Type | Priorité | Statut | Route name |
|---|---|---|---|---|---|
| 01 | `/` | T1 Accueil | P1 | ✅ Livrée + visual check OK | `app_home` |
| 02 | `/reparation-moteur-electrique/` | T2 Service | P1 | ✅ Livrée + visual check OK | `app_service_reparation_moteur_electrique` |
| 03 | `/bobinage-moteur-electrique/` | T2 Service | P1 | ✅ Livrée | `app_service_bobinage_moteur_electrique` |
| 04 | `/reducteurs-motoreducteurs/` | T2 Service | P1 | ✅ Livrée | `app_service_reducteurs_motoreducteurs` |
| 05 | `/pompes-industrielles/` | T2 Service | P1 | ✅ Livrée | `app_service_pompes_industrielles` |
| 06 | `/pompe-piscine-relevage/` | T2 Service | P1 | ✅ Livrée | `app_service_pompe_piscine_relevage` |
| 07 | `/variateurs-de-vitesse/` | T2 Service | P2 | ✅ Livrée | `app_service_variateurs_de_vitesse` |
| 08 | `/transmissions-mecaniques/` | T2 Service | P2 | ✅ Livrée | `app_service_transmissions_mecaniques` |
| 09 | `/reparation-pompe-immergee/` | T2 Service | P1 | ✅ Livrée | `app_service_reparation_pompe_immergee` |
| 10 | `/fournitures-industrielles/` | T2 Service | P2 | ✅ Livrée | `app_service_fournitures_industrielles` |
| 11 | `/maintenance-industrielle/` | T2 Service | P2 | ✅ Livrée | `app_service_maintenance_industrielle` |
| 12 | `/a-propos/` | T3 Institutionnelle | P3 | ✅ Livrée + visual check OK | `app_about` |

### Pages fonctionnelles

| # | URL | Type | Statut |
|---|---|---|---|
| 13 | `/contact/` | T4 Contact | ✅ Refondue (sujet ChoiceType, bandeau urgence, infos NAP, Google Maps) + visual check OK |
| 14 | `/blog/` | T5 Listing | ✅ Template livré (état vide tant que `Article` table non migrée) |
| 15 | `/blog/{slug}/` | T6 Article | ✅ Template livré + entité `App\Entity\Article` + 1 fixture placeholder |
| 16 | `/mentions-legales/` | Légal | ✅ Body HTML TCA réécrit (chargement après reload fixtures) |
| 17 | `/politique-confidentialite/` | Légal | ✅ Body HTML TCA réécrit (chargement après reload fixtures) |
|   | `/cgu/` | Légal | ✅ Body HTML TCA réécrit (chargement après reload fixtures) |
|   | `/politique-cookies/` | Légal | ✅ Body HTML TCA réécrit (chargement après reload fixtures) |
| 18 | 404 | Erreur | ✅ Réutilise `ErrorPage` entité moule + fixture TCA |
|   | `/sitemap.xml` | SEO système | ✅ Existant moule, alimenté automatiquement |
|   | `/robots.txt` | SEO système | ✅ Nouveau `RobotsController` |

---

## Infrastructure technique livrée

### Charte graphique
- **Polices** Kaelia SemiBold + Urbanist 400/500/600/700 converties OTF/TTF → WOFF2 (gain moyen ~60%) sous `/public/fonts/`
- **`@font-face` + preload** wirés dans `templates/base.html.twig`
- **`public/css/variables-front.css`** réécrit avec les 4 couleurs officielles Luciole Studio (Anthracite, Bleu Pétrole, Doré, Blanc) + variables typo/espacements/radius
- **`public/css/tca/fonts.css`** (5 `@font-face`) + **`public/css/tca/site.css`** (~1500 lignes, namespace `.tca-*` pour ne pas interférer avec le style.css du moule)
- **Google Fonts retirées** du `base.html.twig` (avant : Cormorant Garamond + DM Sans)

### Photos
- **118/119 photos** brutes converties en WebP, renommées kebab-case descriptif, rangées par dossier (`hero/`, `atelier/`, `services/`, `showroom/`, `products/`, `team/`)
- **`IMAGES_REPORT.md`** produit à la racine avec mapping complet et 6 photos manquantes signalées pour Gabin
- **2 photos avec visages identifiables** flaguées (CLAUDE.md §3.3) — autorisation TCA requise avant publication
- Script de conversion : `scripts/process_images.py` (idempotent)

### Logos
- Réutilisation des SVG officiels Luciole : `/public/logos/HORIZONTALE BASELINE/LOGO-TCA-RVB_HORIZONTALE-BASELINE-COULEUR.svg` (header) et `RESERVE.svg` (footer fond pétrole)

### Services Symfony SEO créés
- **`App\Service\SchemaService`** — génère LocalBusiness, Organization, WebSite, Service, FAQPage, BreadcrumbList, Article (JSON-LD)
- **`App\Service\BreadcrumbService`** — fil d'Ariane dynamique selon `routeName` courant
- **`App\Twig\SchemaExtension`** — fonctions Twig `schema_*()`, `breadcrumb()`, `json_ld()`
- **`App\Controller\RobotsController`** — `/robots.txt`
- LocalBusiness JSON-LD **injecté automatiquement** sur toutes les pages via `partials/_seo.html.twig`
- BreadcrumbList JSON-LD injecté automatiquement sur toutes les pages sauf accueil

### Controllers de pages créés
- `ServiceController` (10 routes avec `defaults: ['slug' => ...]` distincts → 10 `routeName` uniques pour mapping PageSeo)
- `AboutController`
- `BlogController` (listing + article avec fallback gracieux si table absente)

### Composants Twig réutilisables
- `templates/_components/breadcrumb.html.twig`
- `templates/pages/service.html.twig` — wrapper hérité par les 10 templates services (hero, intro, sections, FAQ accordion, contact CTA, autres services)

### Fixtures réécrites (TCA-branding)
- `App\DataFixtures\SiteParameterFixtures` — nom, adresse, téléphone, logo TCA
- `App\DataFixtures\SeoFixtures` — SEO de 24 routes (12 piliers + Contact + About + Blog + 4 légal + 6 auth) avec title/meta/OG/Twitter optimisés
- `App\DataFixtures\ArticleFixtures` — **16 articles complets** (articles 1–15 + 17 du calendrier éditorial ; articles 16 et 18 data-driven GSC à écrire après M+5). Articles 1–6 isPublished=true, articles 7–17 isPublished=false (à activer mensuellement)
- HTML bodies légaux (`mentions.html`, `privacy.html`, `cgu.html`, `cookies.html`) réécrits TCA, avec zones ⚠️ marquées pour les infos confidentielles à compléter

### Routes
- Toutes les routes du moule **migrées vers trailing slash** (`/contact/`, `/cgu/`, etc.)
- Route privacy renommée `/confidentialite` → `/politique-confidentialite/` (route name `app_legal_privacy` inchangée)
- 10 nouvelles routes services + `app_about` + `app_blog_index` + `app_blog_article` + `app_robots`

---

## ⚠️ Actions Gabin requises avant la mise en ligne

Cf. `MIGRATIONS_NEEDED.md` pour le détail. En résumé :

```powershell
# 1. Vider le cache (routes ont changé)
php bin/console cache:clear

# 2. Générer la migration pour la nouvelle entité Article
php bin/console make:migration
php bin/console doctrine:migrations:migrate --no-interaction

# 3. Recharger les fixtures (TCA branding + 4 bodies légaux + 16 articles blog)
php bin/console doctrine:fixtures:load --no-interaction
```

Tant que ces commandes ne sont pas exécutées :
- Le nom de site affiché en header / footer / OG sera encore "Skeleton" (siteParams pas rechargé)
- Le numéro de téléphone sera encore l'ancien 0750244861
- Les pages légales afficheront l'ancien contenu avec `****`
- Les meta tags / titles afficheront le SEO Skeleton
- Le blog listing renverra l'état vide (mais HTTP 200 grâce au fallback gracieux)

**Une fois les commandes exécutées**, le site sera complètement TCA-isé.

---

## Points en attente côté TCA (CLAUDE.md §3.3 — ne pas inventer)

Documentés dans `docs/client.md`. À récupérer auprès de TCA avant la mise en prod :

- Email de contact officiel (siteParams.contactEmail)
- Numéro SIRET, capital social, RCS, TVA intracommunautaire (mentions légales)
- Nom du directeur de la publication (mentions légales)
- Hébergeur retenu (mentions légales)
- Liste précise des marques distribuées et réparées
- Liste de l'équipe avec rôles + accord pour publication des noms / portraits
- Liste de clients référents avec accord de citation
- Certifications, labels, agréments officiels
- 5 avis Google à intégrer (avec accord)
- Texte exact de la promesse "devis gratuit" et "dépannage dans la journée"
- Liste des prestations refusées (ce que TCA ne fait pas)

## Points en attente côté Vultek

- Conversion icône SVG → PNG 16/32/192/512 + .ico pour favicon multi-tailles (Phase 5)
- Configuration GA4 (clé via SiteIntegration en back-office)
- Configuration Google Search Console (token via back-office)
- Configuration du serveur production (gzip/brotli, HTTP/2, headers de sécurité — `docs/technical.md §11`)
- Production des **18 articles** du calendrier éditorial (`docs/seo-strategy.md §4`) — 1 placeholder en attendant
- Production des **OG images photos** TCA (actuellement SVG TCA-brandés générés par fixture)
- Validation des autorisations photos individuelles équipe (2 photos flaguées dans `IMAGES_REPORT.md`)
- Récupération photos manquantes (façade, pompe immergée seule, intervention sur site, etc. — cf. `IMAGES_REPORT.md`)

---

## Prochaines sessions — pistes ouvertes

1. **Polish visuel** une fois fixtures rechargées : revoir desktop + mobile de chaque page avec les vraies données TCA
2. **18 articles blog** du calendrier éditorial (1 par sprint approximativement)
3. **Page erreurs 403/404/500** : vérifier que le rendu TCA fonctionne avec les nouvelles classes (`ErrorPage` entité moule)
4. **Audit Lighthouse complet** sur chaque page : objectif 100/100 toutes catégories
5. **Audit Rich Results Test** Google pour chaque schema JSON-LD
6. **Audit WAVE / axe DevTools** pour conformité WCAG 2.1 AA + RGAA 4.1 AA
7. **OG images photos** (1200×630) pour chaque page pilier — remplacer les SVG placeholder par de vraies compositions photo TCA
8. **Compléter les FAQ** : remplacer les placeholders dans les 11 pages services par les vraies questions de `docs/faq.md` (Gabin remplit en parallèle)
9. **CTA bandeau sticky mobile** (numéro de téléphone toujours visible sur mobile) — proposition d'amélioration UX
10. **Stickyness header au scroll** : tester et ajuster sur mobile (potentiellement réduire la hauteur header en sticky)
