{
  "summary": "Exposer 5 designs bespoke (lot 3 : produit-showcase, duel comparatif, prime-CEE, winter-objection, RSE) — réplique le pilote, noms de champs uniques",
  "agentCount": 6,
  "logs": [],
  "result": {
    "created": [
      {
        "slug": "produit-showcase",
        "layoutSnake": "produit_showcase",
        "pascal": "ProduitShowcase",
        "category": "Produit",
        "fragmentConst": "PRODUIT_SHOWCASE_FRAGMENT",
        "fragmentImport": "import { PRODUIT_SHOWCASE_FRAGMENT } from \"@/lib/wp/fragments/produitShowcase\";",
        "fragmentSpread": "...ProduitShowcaseFields",
        "registryKey": "ProduitShowcaseLayout",
        "blockImport": "import ProduitShowcase from \"@/components/blocks/ProduitShowcase\";",
        "blockComponent": "ProduitShowcase",
        "typeInterface": "export interface ProduitShowcaseBlock {\n  __typename: string;\n  origine?: string | null;\n  entete?: WpEntete | null;\n  showcaseVariantes?:\n    | {\n        nom?: string | null;\n        usage?: string | null;\n        description?: string | null;\n        image?: WpImageField | null;\n      }[]\n    | null;\n  note?: string | null;\n}",
        "customFieldNames": [
          "showcase_variantes"
        ],
        "filesCreated": [
          "/opt/projects/.covalba-wp-worktrees/tom-passage-wp/wordpress/mu-plugins/covalba-core/inc/field-groups/layouts/produit-showcase.php",
          "/opt/projects/.covalba-wp-worktrees/tom-passage-wp/src/lib/wp/fragments/produitShowcase.ts",
          "/opt/projects/.covalba-wp-worktrees/tom-passage-wp/src/components/blocks/ProduitShowcase.tsx"
        ],
        "notes": "Pattern pilote répliqué pour le design « Showcase produit (toggle variantes) » (slug produit-showcase). 3 fichiers créés, AUCUN fichier partagé touché (index.ts/types.ts/BlockRenderer.tsx/sections.php intacts, pas de build).\n\nCOMPOSANT BESPOKE RÉUTILISÉ : src/components/product/ProductShowcase.tsx (export default ProductShowcase, export type VariantWithAlt). Bande navy #1A2A3A + toggle pills + carte 2 colonnes (packshot WpImage + tagline/nom/description + CTAButton).\n\nMAPPING WP → composant :\n- entete.badge → badge ; entete.titre → titre ; entete.intro → intro (le composant accepte ReactNode, on passe les strings WP).\n- showcase_variantes[] → variants[] : nom→name, usage→tagline, description→description, image→img (mapImage). id généré (`variante-${i}`), alt = altText image ou nom en fallback.\n- imgWidth/imgHeight injectés UNIQUEMENT s'ils existent, lus depuis image.mediaDetails via mapImage (le composant les attend pour le mode intrinsèque WpImage) : {...(image?.width ? { imgWidth: image.width } : {})}.\n- cta requis par VariantWithAlt et NON éditable côté WP → défaut codé { label: 'Demander un devis', href: '/diagnostic' } (identique aux defaults du composant).\n- note → note (textarea).\n\nANTI-COLLISION WPGraphQL-ACF : le SEUL repeater custom est nommé `showcase_variantes` (préfixé layout) → type ...ProduitShowcaseShowcaseVariantes, pas de collision avec un éventuel `variantes`/`items`. entete via cvb_clone_entete (exception sûre 'entete'). Les sous-champs scalaires (nom/usage/description) et image ne génèrent pas de type partagé → OK. note est scalaire.\n\nREMARQUE collapsed : cvb_repeater cherche un sous-champ nommé 'titre' pour le collapsed auto ; il n'y en a pas ici (on a 'nom'), donc collapsed reste non défini — comportement attendu, sans impact GraphQL.\n\nWIRING RESTANT (à faire par l'orchestrateur sur les fichiers partagés, hors de mon périmètre) :\n- src/lib/wp/fragments/index.ts (ou agrégateur) : importer PRODUIT_SHOWCASE_FRAGMENT et l'inclure dans la query sections + ajouter le spread ...ProduitShowcaseFields.\n- src/components/blocks/BlockRenderer.tsx : import ProduitShowcase + entrée registry `ProduitShowcaseLayout: ProduitShowcase`.\n- sections.php : require du layout layouts/produit-showcase.php dans le flexible content `sections`."
      },
      {
        "slug": "duel-comparatif",
        "layoutSnake": "duel_comparatif",
        "pascal": "DuelComparatif",
        "category": "Comparatifs",
        "fragmentConst": "DUEL_COMPARATIF_FRAGMENT",
        "fragmentImport": "import { DUEL_COMPARATIF_FRAGMENT } from \"./duelComparatif\";",
        "fragmentSpread": "...DuelComparatifFields",
        "registryKey": "duel_comparatif",
        "blockImport": "import DuelComparatif from \"@/components/blocks/DuelComparatif\";",
        "blockComponent": "DuelComparatif",
        "customFieldNames": [
          "duel_cartes",
          "duel_cartes_arguments",
          "duel_points_cles"
        ],
        "typeInterface": "export interface DuelComparatifBlock {\n  __typename: string;\n  origine?: string | null;\n  entete?: WpEntete | null;\n  duelCartes?:\n    | {\n        titre?: string | null;\n        label?: string | null;\n        tone?: string | null;\n        image?: WpImageField | null;\n        metric?: string | null;\n        metricLabel?: string | null;\n        duelCartesArguments?: { texte?: string | null }[] | null;\n      }[]\n    | null;\n  duelPointsCles?: { icone?: string | null; label?: string | null }[] | null;\n  transition?: string | null;\n}",
        "filesCreated": [
          "/opt/projects/.covalba-wp-worktrees/tom-passage-wp/wordpress/mu-plugins/covalba-core/inc/field-groups/layouts/duel-comparatif.php",
          "/opt/projects/.covalba-wp-worktrees/tom-passage-wp/src/lib/wp/fragments/duelComparatif.ts",
          "/opt/projects/.covalba-wp-worktrees/tom-passage-wp/src/components/blocks/DuelComparatif.tsx"
        ],
        "notes": "Pattern pilote répliqué pour le design « Duel comparatif » (slug duel-comparatif). Composant bespoke RÉUTILISÉ tel quel : @/components/industries/DistributionSolution (2 cartes comparatives toiture classique vs cool roof + key-points + transition). Aucune réimplémentation : le block mappe les champs WP vers les types exportés DistributionSolutionComparison / DistributionSolutionKeyPoint. DistributionSolution.titre est typé ReactNode et accepte donc directement la string entete.titre (parité pixel préservée, fallbacks via {...(x?{p:x}:{})}).\n\nEXACTEMENT 3 fichiers créés, aucun fichier partagé touché (index.ts/types.ts/BlockRenderer.tsx/sections.php intacts ; pas de build). Le block définit son interface inline (comme Constat.tsx) — types.ts non modifié.\n\nGraphQL type ciblé par le fragment : ${SECTIONS_TYPE_PREFIX}DuelComparatifLayout (= SectionsSectionsDuelComparatifLayout par défaut).\n\nANTI-COLLISION WPGraphQL-ACF respectée : tous les repeaters/groups custom ont un nom UNIQUE préfixé par le layout :\n- duel_cartes (repeater, exactement 2 cartes : min/max 2) → SectionsSectionsDuelComparatifDuelCartes\n- duel_cartes_arguments (repeater imbriqué dans duel_cartes) → type imbriqué unique\n- duel_points_cles (repeater key-points icône+label) → SectionsSectionsDuelComparatifDuelPointsCles\nLe group `entete` reste l'exception sûre (cvb_clone_entete, non préfixé). Les sous-champs scalaires (titre/label/tone/metric/metricLabel/texte/icone/transition) ne génèrent pas de type → OK. Pas de noms interdits (variantes/lignes/items/cards/logos).\n\nChamps RÉELLEMENT éditables exposés (pas de champ inutile) : entete{badge,titre,intro} ; duel_cartes[]{titre,label,tone(select warm/cool),image,metric,metricLabel,duel_cartes_arguments[]{texte}} ; duel_points_cles[]{icone(cvb_select_icone),label} ; transition. Le champ `tone` est un select warm/cool qui pilote le styling terracotta vs teal du composant. Les icônes du key-points passent par iconFor() (fallback HelpCircle).\n\ntsc --noEmit : aucune erreur sur les nouveaux fichiers. Reste à faire par l'orchestrateur (fichiers partagés, hors périmètre) : enregistrer le layout dans sections.php, ajouter le spread ...DuelComparatifFields + import du fragment dans fragments/index.ts, et mapper duel_comparatif → DuelComparatif dans BlockRenderer.tsx."
      },
      {
        "slug": "prime-cee",
        "layoutSnake": "prime_cee",
        "pascal": "PrimeCee",
        "category": "Comparatifs",
        "fragmentConst": "PRIME_CEE_FRAGMENT",
        "fragmentImport": "import { PRIME_CEE_FRAGMENT } from \"./primeCee\";",
        "fragmentSpread": "...PrimeCeeFields",
        "registryKey": "prime_cee",
        "blockImport": "import PrimeCee from \"@/components/blocks/PrimeCee\";",
        "blockComponent": "PrimeCee",
        "typeInterface": "export interface PrimeCeeBlock {\n  __typename: string;\n  origine?: string | null;\n  entete?: WpEntete | null;\n  ceeZones?:\n    | {\n        zone?: string | null;\n        regions?: string | null;\n        prime?: string | null;\n      }[]\n    | null;\n  zonesNote?: string | null;\n  retenirNote?: string | null;\n  transition?: string | null;\n  ctaLabel?: string | null;\n}",
        "customFieldNames": [
          "cee_zones"
        ],
        "filesCreated": [
          "/opt/projects/.covalba-wp-worktrees/tom-passage-wp/wordpress/mu-plugins/covalba-core/inc/field-groups/layouts/prime-cee.php",
          "/opt/projects/.covalba-wp-worktrees/tom-passage-wp/src/lib/wp/fragments/primeCee.ts",
          "/opt/projects/.covalba-wp-worktrees/tom-passage-wp/src/components/blocks/PrimeCee.tsx"
        ],
        "notes": "Bespoke component DistributionCEE is at src/components/industries/DistributionCEE.tsx (industries/, not blocks/). The PrimeCeeBlock interface is defined INLINE in the block file (mirroring the Constat pilot pattern), since types.ts must not be touched. iconFor() is NOT used here: DistributionCEE has no editable icon fields — its icons (BadgeEuro, ShieldCheck, ArrowRight) are fixed internals. Editable fields exposed: entete (badge/titre/intro via cvb_clone_entete), cee_zones repeater (zone/regions/prime), zonesNote, retenirNote, transition, ctaLabel. The component's form selects/inputs and the embedded BAT-EN-112 copy are static (not editable) so they were intentionally NOT exposed. Anti-collision: repeater named cee_zones (prefixed) → type SectionsSectionsPrimeCeePrimeCeeZones; scalar subfields zone/regions/prime generate no type. Fragment is on ${SECTIONS_TYPE_PREFIX}PrimeCeeLayout. NOT edited (per instructions): index.ts, types.ts, BlockRenderer.tsx, sections.php. The orchestrator still needs to wire registry/fragment/renderer (registryKey prime_cee, fragmentImport/fragmentSpread provided). Titre/intro props on DistributionCEE accept ReactNode but receive plain strings from WP — valid. No build run."
      },
      {
        "slug": "winter-objection",
        "layoutSnake": "winter_objection",
        "pascal": "WinterObjection",
        "category": "Contenu",
        "fragmentConst": "WINTER_OBJECTION_FRAGMENT",
        "fragmentImport": "import { WINTER_OBJECTION_FRAGMENT } from \"@/lib/wp/fragments/winterObjection\";",
        "fragmentSpread": "...WinterObjectionFields",
        "registryKey": "winter_objection",
        "blockImport": "import WinterObjection from \"@/components/blocks/WinterObjection\";",
        "blockComponent": "WinterObjection",
        "typeInterface": "export interface WinterObjectionBlock {\n  __typename: string;\n  origine?: string | null;\n  titre?: string | null;\n  intro?: string | null;\n  paragraphe1?: string | null;\n  paragraphe2?: string | null;\n  image?: WpImageField | null;\n}",
        "customFieldNames": [],
        "filesCreated": [
          "/opt/projects/.covalba-wp-worktrees/tom-passage-wp/wordpress/mu-plugins/covalba-core/inc/field-groups/layouts/winter-objection.php",
          "/opt/projects/.covalba-wp-worktrees/tom-passage-wp/src/lib/wp/fragments/winterObjection.ts",
          "/opt/projects/.covalba-wp-worktrees/tom-passage-wp/src/components/blocks/WinterObjection.tsx"
        ],
        "notes": "Replique le pattern pilote (Constat) avec interface inline dans le block — types.ts non touche. Le composant bespoke WinterObjectionSection est reutilise tel quel; titre accepte ReactNode donc la string WP passe sans souci. Champs editables exposes: titre, intro, paragraphe1, paragraphe2, image (mappes avec fallback {...(x?{p:x}:{})} pour preserver les defauts du composant = parite pixel). Aucun repeater/group custom => aucun risque de collision WPGraphQL-ACF, customFieldNames vide. Pas d'icones dans ce design donc iconFor() non utilise. Fragment sur ${SECTIONS_TYPE_PREFIX}WinterObjectionLayout. NON edites (conformement aux consignes): index.ts, types.ts, BlockRenderer.tsx, sections.php. Pas de build lance."
      },
      {
        "slug": "rse-section",
        "layoutSnake": "rse_section",
        "pascal": "RseSection",
        "category": "Contenu",
        "fragmentConst": "RSE_SECTION_FRAGMENT",
        "fragmentImport": "import { RSE_SECTION_FRAGMENT } from \"@/lib/wp/fragments/rseSection\";",
        "fragmentSpread": "...RseSectionFields",
        "registryKey": "rse_section",
        "blockImport": "import RseSection from \"@/components/blocks/RseSection\";",
        "blockComponent": "RseSection",
        "typeInterface": "export interface RseSectionBlock {\n  __typename: string;\n  origine?: string | null;\n  badge?: string | null;\n  titre?: string | null;\n  rseItems?:\n    | { icone?: string | null; titre?: string | null; texte?: string | null }[]\n    | null;\n  image?: WpImageField | null;\n}",
        "customFieldNames": [
          "rse_items"
        ],
        "filesCreated": [
          "/opt/projects/.covalba-wp-worktrees/tom-passage-wp/wordpress/mu-plugins/covalba-core/inc/field-groups/layouts/rse-section.php",
          "/opt/projects/.covalba-wp-worktrees/tom-passage-wp/src/lib/wp/fragments/rseSection.ts",
          "/opt/projects/.covalba-wp-worktrees/tom-passage-wp/src/components/blocks/RseSection.tsx"
        ],
        "notes": "Block \"Engagement RSE\" (slug rse-section) exposé en répliquant le pattern pilote. 3 fichiers créés, aucun fichier partagé touché, pas de build.\n\nRÉSOLUTIONS DE NOMS:\n- layout snake: rse_section ; camel: rseSection ; Pascal: RseSection\n- type GraphQL généré: ${SECTIONS_TYPE_PREFIX}RseSectionLayout (fragment \"RseSectionFields\")\n- repeater unique anti-collision WPGraphQL-ACF: rse_items → camel rseItems (PAS \"items\")\n\nCHAMPS RÉELLEMENT ÉDITABLES (RSESection.tsx les expose comme props plates, pas de group entete):\n- badge (text)\n- titre (text)\n- rse_items[] : icone (select lucide) + titre (text) + texte (textarea) → mappés vers items[]{icon,title,text}\n- image (image array) → imageSrc / imageAlt\n\nLe composant bespoke réutilisé est RSESection (default export) — note la casse: import \"@/components/RSESection\" mais le block s'appelle RseSection (PascalCase du slug). titre est typé ReactNode côté composant, une string passe sans souci.\n\ncustomFieldNames = repeaters/groups CUSTOM générant un type GraphQL: uniquement rse_items. badge/titre/icone/titre/texte/image sont scalaires (pas de type généré). Aucun group entete/cta utilisé ici.\n\ntypeInterface défini INLINE dans le block (comme Constat.tsx) pour ne pas toucher types.ts. WpImageField importé depuis @/lib/wp/types.\n\nÀ FAIRE par l'orchestrateur (fichiers partagés non touchés): enregistrer le fragment (spread ...RseSectionFields dans la query sections + import RSE_SECTION_FRAGMENT), ajouter l'entrée registry \"rse_section\" → RseSection dans BlockRenderer, inclure le layout rse-section.php dans sections.php, ajouter RseSectionBlock à l'union de types si nécessaire."
      }
    ],
    "fusion": "All wiring is in place and consistent. `tsc --noEmit` passes, `php -l` passes, each slug appears exactly 3 times in sections.php (categories + $files + $with_thumb).\n\nWIRING COMPLETE — 4 shared files edited for all 5 blocks. No build run.\n\nEDITS\n\n1. src/lib/wp/fragments/index.ts\n- Added 5 imports: PRODUIT_SHOWCASE_FRAGMENT, DUEL_COMPARATIF_FRAGMENT, PRIME_CEE_FRAGMENT, WINTER_OBJECTION_FRAGMENT, RSE_SECTION_FRAGMENT.\n- Added the 5 consts to the SECTIONS_FRAGMENTS array (after SYSTEME_COUCHES_FRAGMENT).\n- Added 5 spreads to SECTIONS_FIELD: ...ProduitShowcaseFields, ...DuelComparatifFields, ...PrimeCeeFields, ...WinterObjectionFields, ...RseSectionFields.\n\n2. src/lib/wp/types.ts\n- Appended the 5 interfaces (ProduitShowcaseBlock, DuelComparatifBlock, PrimeCeeBlock, WinterObjectionBlock, RseSectionBlock) after FicheChantierBlock.\n- Extended the WpSection union with the 5 new types.\n\n3. src/components/blocks/BlockRenderer.tsx\n- Added 5 default imports from @/components/blocks/{ProduitShowcase,DuelComparatif,PrimeCee,WinterObjection,RseSection}.\n- Added 5 registry entries.\n\n4. wordpress/mu-plugins/covalba-core/inc/field-groups/sections.php\n- cvb_layout_categories(): produit-showcase=>Produit, duel-comparatif=>Comparatifs, prime-cee=>Comparatifs, winter-objection=>Contenu, rse-section=>Contenu.\n- $files (bespoke zone): added the 5 slugs.\n- $with_thumb: added the 5 slugs.\n\nVERIFICATION\n- npx tsc --noEmit: exit 0 (no errors).\n- php -l sections.php: no syntax errors.\n- Each slug present exactly 3x in sections.php; 5 fragment spreads; 5 registry keys.\n\nCONFLICTS / DEVIATION (important)\n- The snippets' `registryKey` field was INCONSISTENT: produit-showcase gave `ProduitShowcaseLayout` (PascalCase+Layout) while the other four gave snake_case (`duel_comparatif`, `prime_cee`, `winter_objection`, `rse_section`). The BlockRenderer registry matches keys as a SUFFIX of the GraphQL `__typename`, and all 5 fragments target `${SECTIONS_TYPE_PREFIX}<Pascal>Layout` (confirmed: DuelComparatifLayout, ProduitShowcaseLayout, PrimeCeeLayout, WinterObjectionLayout, RseSectionLayout). Snake_case keys would NEVER match and the blocks would silently not render. I used the correct `<Pascal>Layout` keys for all 5 to follow the established convention and match the actual typenames. The provided `registryKey` values for the 4 non-pilot blocks should be disregarded.\n- No other conflicts. The layout PHP files use `cvb_layout($snake, ...)`, which yields the expected `<Pascal>Layout` GraphQL types, so fragments/registry/sections.php are all aligned."
  }
}