Aller au contenu

Skills Celuga

celuga-shadcnblock-integration

Adapter un bloc shadcnblocks en bloc ou présentation du page builder.

Quand : Adapter un bloc shadcnblocks en bloc ou présentation du page builder.

Portée : machine (~/.agents/skills) · Fichiers : 5

Copier le bloc et le coller dans un terminal. Il recrée tous les fichiers du skill.

dir="$HOME/.agents/skills/celuga-shadcnblock-integration"
mkdir -p "$dir"
cat > "$dir/SKILL.md" <<'CELUGA_SKILL_EOF'
---
name: celuga-shadcnblock-integration
description: Adapte un bloc shadcnblocks (hero, feature, team, pricing, testimonial, faq, cta, timeline…) en bloc ou présentation du page builder Celuga — catalogue partagé, champs Payload, rendu React SSR, fixture, parité SEO et vignette admin. Use when adding `@shadcnblocks/*`, running `bunx shadcn add`, browsing shadcnblocks.com, creating or extending a Payload layout block, or wiring a new section into the block renderers.
---
# Intégration shadcnblocks Celuga
Un shadcnblock est une **maquette de référence**, jamais du code de production : on en extrait la mise en page, puis on reconstruit le rendu avec les contrats Celuga : champs Payload éditables, React **SSR**, jetons de `DESIGN.md`, zéro JavaScript client sauf besoin prouvé.
Lire `DESIGN.md`, le catalogue et la procédure `.agents/skills/celuga-add-block` du projet : **cette procédure prime** pour le branchement. Ce skill couvre ce qui lui manque : partir d'une référence shadcn.
| Référence | Quand la lire |
| --- | --- |
| [`references/catalog.md`](references/catalog.md) | étape 0 — trouver l'existant, nommer, traduire une référence |
| [`references/field-mapping.md`](references/field-mapping.md) | étapes 2 à 4 — constantes → champs → rendu |
| [`references/examples.md`](references/examples.md) | deux parcours complets |
Charger **celuga-media-react** pour tout champ `upload`, **celuga-seo** si le bloc porte une FAQ ou des entités, **shadcn** pour la CLI.
## 0. Réutiliser, étendre ou créer
1. Lire `blockCatalog` et les rendus existants. Un besoin couvert devient une **présentation** (`select`) ou un nouveau rendu d'une combinaison bloc/présentation existante, pas un nouveau bloc.
2. **Sans schéma d'abord** : un nouveau composant front sur des champs existants ne demande ni migration ni risque pour une base partagée. Signaler une petite différence de mise en page plutôt que d'ajouter une option si l'utilisateur veut éviter le schéma.
3. Nouveau bloc seulement si la structure éditoriale diffère. Fixer `slug` (intention en français, camelCase, sans accent ni numéro shadcn), libellés singulier et pluriel, groupe, composant.
4. **Conserver les slugs persistés** : Payload dérive des tables des slugs. Un renommage demandé doit migrer et prouver la conservation des données.
**Terminé quand :** décision écrite, avec slug, libellés, groupe, composant, et `rg "<slug>" packages/site-contracts/src/content.ts` ne trouve rien pour un bloc neuf.
## 1. Lire la référence
Depuis `apps/web/` (registre `@shadcnblocks` dans `components.json`, clé `SHADCNBLOCKS_API_KEY`) :
```bash
bunx shadcn add @shadcnblocks/feature61
```
Travailler dans un scratch, ou inspecter puis retirer ce que la CLI a ajouté. Si le dépôt garde un instantané sous `docs/references/`, le lire plutôt que d'appeler le registre. Noter constantes, médias, dépendances (`framer-motion`, `swiper`, `embla`…), `'use client'`, CSS inline.
**Terminé quand :** l'inventaire des données éditables et des comportements est noté, et `git status` ne montre ni fichier shadcn ni dépendance nouvelle.
## 2. Cartographier
Chaque constante devient un champ Payload ou une décision de design justifiée dans le code ([`field-mapping.md`](references/field-mapping.md)). Chaque comportement client trouve un équivalent natif : animation → système d'apparition du site et CSS, carrousel → scroll-snap, onglets et accordéons → `<details>`.
**Terminé quand :** aucune ligne de l'inventaire n'est sans destination, et chaque dépendance JS a un remplaçant natif ou une justification d'hydratation.
## 3. Catalogue et champs (si nouveau bloc)
- Entrée dans `blockCatalog` (`packages/site-contracts/src/content.ts`) : `label`, `plural`, `group`, et `cache: [...]` si le bloc lit une autre collection.
- Champs de même identifiant côté CMS (`apps/cms/src/fields/index.ts` ou fichier de `blocks/`), avec `text`, `link`, `image` des primitives. Options de section, vignette et libellés de lignes sont ajoutés automatiquement.
- Libellés et aides en français ; `admin.description` si l'effet n'est pas évident ; `admin.condition` pour les champs propres à une présentation. Localiser les seules feuilles textuelles.
- `bun run generate`, puis migration (procédure `celuga-change-schema`) : un bloc persisté crée des tables. Sur une base partagée, ne rien intégrer avant que la migration soit appliquée.
**Terminé quand :** catalogue et champs ont les mêmes identifiants, et le bloc apparaît dans l'admin sous le bon groupe.
## 4. Rendu React
`apps/web/src/components/<Nom>/index.tsx`. Commencer le fichier par la traçabilité :
```tsx
/** Mise en page : @shadcnblocks/feature61. Rendu : charte du site. */
```
- composant serveur : pas de `'use client'`, de hook ni de dépendance d'animation ;
- `return null` si rien d'affichable ; filtrer les items incomplets ;
- `<section aria-labelledby>`, titres cohérents, listes en `<ul>/<li>` ;
- images `<Media>` avec `sizes` du slot réel ; liens et texte riche par les composants du site ;
- jetons et composants `ui` : le lint refuse valeurs arbitraires, styles inline, couleurs brutes et restyle ;
- pas de `lucide-react` si le site a une autre famille d'icônes ; contraste vérifié, focus visible, `prefers-reduced-motion`.
**Terminé quand :** le composant compile, sans donnée éditable en dur, et son HTML SSR ne dépend d'aucune hydratation.
## 5. Brancher
- Table des rendus par slug (`BlockRenderers`), ou branche de la présentation concernée si le projet a plusieurs styles de page.
- Données annexes dans `prepareBlocks`, dégradation limitée au bloc, tag au catalogue (**celuga-astro-payload-cache**).
- Îlot seulement si une interaction l'exige, avec directive explicite dans `RenderBlocks/index.astro`.
- Selon le bloc : petites images embarquées, adaptateur SEO (`apps/cms/src/seo/document.ts`), règle de traduction, chemins localisés de la preview, slug de vignette `apps/cms/public/block-thumbnails/<slug>.svg`.
**Terminé quand :** le bloc s'affiche sur une page publique et dans la live preview.
## 6. Vérifier
Fixture dans `tests/fixtures/page-blocks.ts`, test de contrat catalogue → fixture → rendu → analyse, puis les contrôles du dépôt et `bun run lint`. Contrôler visuellement à 390, 768, 1024 et 1440 px, plus un grand écran : site, live preview, administration (insertion du bloc), clavier seul. Un geste éditorial particulier s'ajoute au guide éditorial.
**Terminé quand :** les commandes passent et les largeurs sont conformes. Inspecter le diff généré (types, import map, migration).
CELUGA_SKILL_EOF
mkdir -p "$dir/agents"
cat > "$dir/agents/openai.yaml" <<'CELUGA_SKILL_EOF'
interface:
display_name: "Celuga shadcnblock integration"
short_description: "Adapter un shadcnblock en bloc du page builder Payload"
default_prompt: "Utilise $celuga-shadcnblock-integration pour adapter ce shadcnblock en bloc Payload + section Celuga."
CELUGA_SKILL_EOF
mkdir -p "$dir/references"
cat > "$dir/references/catalog.md" <<'CELUGA_SKILL_EOF'
# Catalogue du page builder
Source de vérité : `blockCatalog` dans `packages/site-contracts/src/content.ts`, complété par les modules actifs. Ne pas tenir de liste ici : les blocs varient selon les projets.
```bash
rg -n "label:" packages/site-contracts/src/content.ts packages/site-contracts/src/modules
sed -n 1,40p apps/web/src/components/BlockRenderers/index.ts
ls apps/cms/public/block-thumbnails
```
Hors page builder : haut de page (`hero` des contenus), en-tête et pied de page (globals). Un shadcnblock `hero*`, `navbar*` ou `footer*` modifie ces surfaces, pas le catalogue.
## Règles de nommage
| Élément | Règle | Exemple |
| --- | --- | --- |
| `slug` / `blockType` | intention, français de préférence, camelCase sans accent ni numéro ; **immuable une fois en base** | `miseEnAvant` |
| libellés | français métier, singulier et pluriel | `Mise en avant` / `Mises en avant` |
| composant | `components/<Nom>/index.tsx`, nommé par l'intention ou par la référence shadcn adaptée | `Highlight`, `Feature61` |
| présentation | `select` nommé par le rendu, pas par le numéro shadcn | `presentation: "card"` |
| vignette | `apps/cms/public/block-thumbnails/<slug>.svg`, légère et sans texte | `miseEnAvant.svg` |
| traçabilité | commentaire en tête du composant | `/** Mise en page : @shadcnblocks/feature61. … */` |
Un slug historique numéroté reste tel quel : ne pas le migrer pour l'esthétique.
## Lire un fichier shadcnblocks
| Signal dans la référence | Traduction Celuga |
| --- | --- |
| `const items = [...]` | `array` Payload avec `minRows`, `labels` |
| URL CDN d'image | `upload` → `media`, rendu `<Media>` |
| tableau dupliqué pour une boucle infinie | une seule liste CMS ; défilement natif |
| `framer-motion`, `motion` | système d'apparition du site, CSS, `prefers-reduced-motion` |
| `swiper`, `embla-carousel` | scroll-snap ; îlot seulement si indispensable |
| onglets, accordéon | `<details>`/`<summary>` natifs |
| `<style>` inline, valeurs `[...]`, polices custom | utilitaires Tailwind + jetons ; refusés par le lint dans les `.tsx` |
| `'use client'` | supprimé ; rendu SSR |
| icônes `lucide-react` | famille d'icônes du site |
| `rounded-*`, ombres, couleurs par défaut | charte de `DESIGN.md` |
| graphique (`recharts`) | îlot `client:visible` justifié, exception de lint limitée à son fichier |
CELUGA_SKILL_EOF
mkdir -p "$dir/references"
cat > "$dir/references/examples.md" <<'CELUGA_SKILL_EOF'
# Parcours types
## A. Nouvelle présentation sans schéma
Une référence `@shadcnblocks/feature61` couvre le besoin d'un bloc « Image et texte » existant.
| Étape | Décision |
| --- | --- |
| existant proche | bloc image + texte : mêmes champs → aucune migration |
| composant | `apps/web/src/components/Feature61/index.tsx` |
| branchement | branche de la table de rendu, ou rendu associé à une valeur de `presentation` déjà en base |
| largeurs alternées, fond | code |
| animation d'apparition | utilitaire du site |
Fichiers touchés : le composant, la table de rendu, une fixture si la présentation est nouvelle. Aucun fichier du CMS. Vérifier : HTML SSR, live preview, quatre largeurs, parité SEO inchangée.
## B. Nouveau bloc
Une référence de frise sans équivalent : structure éditoriale différente.
| | Valeur |
| --- | --- |
| référence | `@shadcnblocks/timeline9` |
| `slug` | `frise` |
| libellés, groupe | Frise chronologique / Frises chronologiques, Contenu |
| composant | `apps/web/src/components/Timeline9/index.tsx` |
| variante | `presentation` en `select`, plutôt qu'un second bloc |
| Référence | Destination |
| --- | --- |
| titre, sous-titre | `title`, `intro` |
| { date, titre, texte } | `items[]` avec `minRows: 1`, champs localisés |
| ligne verticale, puces | code |
| animation | utilitaire d'apparition du site, sans `framer-motion` |
```text
packages/site-contracts/src/content.ts # entrée du catalogue
apps/cms/src/fields/index.ts ou src/blocks/ # champs de même identifiant
packages/site-contracts/src/generated/payload-types.ts # bun run generate
apps/cms/src/app/_payload/importMap.js # bun run generate, si composants admin
apps/cms/src/migrations/ # migration relue, index.ts
apps/cms/public/block-thumbnails/frise.svg # vignette
apps/web/src/components/Timeline9/index.tsx # rendu
apps/web/src/components/BlockRenderers/index.ts # table de rendu
apps/cms/src/seo/document.ts # extraction SEO
packages/site-contracts/src/i18n.ts # règle de traduction, si champs requis
apps/web/src/lib/preview-request.ts # chemins localisés de la live preview
tests/fixtures/page-blocks.ts # fixture
```
Selon le bloc s'ajoutent : `lib/page-builder.ts` et `cache` au catalogue (données d'une autre collection), `RenderBlocks/index.astro` (îlot ou petites images), `tutorial/questions.ts` (geste particulier).
## Pièges
- Le test de contrat compare catalogue et fixtures : un bloc sans fixture le fait échouer.
- Une feuille localisée absente des chemins de preview s'affiche vide dans la live preview, alors que la page publiée est correcte : toujours tester les deux.
- Un bloc absent de l'adaptateur SEO signale une couverture incomplète dans l'assistant.
CELUGA_SKILL_EOF
mkdir -p "$dir/references"
cat > "$dir/references/field-mapping.md" <<'CELUGA_SKILL_EOF'
# Cartographie shadcnblock → Payload → rendu
Inventorier **chaque** constante et chaque comportement de la référence avant d'écrire les champs.
## Par type de contenu
| Dans la référence | Champ Payload | Rendu |
| --- | --- | --- |
| titre | `text("title", "Titre", true)` | `<h2>` lié par `aria-labelledby` |
| surtitre, badge | `text`, « Laisser vide pour masquer » | rendu conditionnel |
| introduction simple | `textarea` localisé | `<p>` |
| paragraphe riche | `richText` localisé | `RichText` |
| bouton, lien | `link()` des primitives | bouton du site, si `linkURL` donne une adresse |
| image | `image` ou `upload` → `media` | `<Media size sizes>` |
| logo, picto | `upload` → `media` | `<Media size="icon" inlineIfSmall>` et embarquement côté serveur |
| liste homogène | `array` + `minRows`/`maxRows` + `labels` | `<ul>` de `<li>` filtrés |
| chiffre + libellé | `array` { `value`, `label` } | chiffres tabulaires |
| variante visuelle | `select` avec `defaultValue` | classes conditionnelles, jamais un second bloc |
| champ propre à une variante | `admin.condition` | testé dans le rendu |
| formulaire | `relationship` → `forms` | bloc formulaire existant |
| contenu d'une autre collection | `select` de source + `relationship` | `prepareBlocks` + tag au catalogue |
Le `array` reçoit automatiquement `initCollapsed` et un libellé de ligne lisible par la construction de `blocks` dans `fields/index.ts`.
Les items sans lesquels une ligne ne s'affiche pas sont `required: true`. Localiser `value`, `label`, `title`, `text` ; ne pas localiser relations ni structure.
## Reste côté code (pas en CMS)
- grilles, gaps, alternances de colonnes ;
- hauteurs de logos, ratios d'image, découpes ;
- animations et leurs délais ;
- nombre de colonnes par breakpoint.
Un éditeur choisit du contenu et, au plus, une présentation nommée ; il ne règle pas la mise en page.
## Mouvement
Ne pas recopier les durées d'une maquette AOS ou d'une référence : elles sont souvent trop lentes. Passer par l'utilitaire d'apparition du site. Pas d'animation d'entrée sur le haut de page.
## Pattern de rendu
```tsx
/** Mise en page : @shadcnblocks/feature61. Rendu : charte du site. */
export function Feature61({ block, locale = "fr" }: { block: Row; locale?: Locale }) {
const items = (block.items || []).filter((item: Row) => item.title?.trim());
if (!items.length) return null;
const titleId = domId("feature-titre", block.id);
return (
<Section labelledBy={titleId}>
<h2 id={titleId} className="text-balance">{block.title}</h2>
<ul className="m-0 grid list-none gap-3 p-0">
{items.map((item: Row, index: number) => (
<li key={item.id || index}>{/* … */}</li>
))}
</ul>
</Section>
);
}
```
- Vérifier qu'une relation est peuplée avant de rendre une image.
- `alt` vide pour une image décorative ; le composant `Media` le gère.
- Les grilles réagissent à leur conteneur (`@container`, variantes `@min-[...]`) quand le bloc peut changer de largeur.
## Îlot client (exception)
Uniquement si une interaction React est indispensable :
1. branche explicite dans `RenderBlocks/index.astro`, avec `client:visible` ;
2. props sérialisées minimales ;
3. un composant SSR sans directive pour tout le reste du bloc.
Un test de contrat vérifie que les blocs rendus sans cet adaptateur ne contiennent ni `astro-island` ni `<script>`.
CELUGA_SKILL_EOF
mkdir -p "$HOME/.claude/skills" "$HOME/.codex/skills"
[ -e "$HOME/.claude/skills/celuga-shadcnblock-integration" ] || ln -s "$dir" "$HOME/.claude/skills/celuga-shadcnblock-integration"
[ -e "$HOME/.codex/skills/celuga-shadcnblock-integration" ] || ln -s "$dir" "$HOME/.codex/skills/celuga-shadcnblock-integration"
SKILL.md
---
name: celuga-shadcnblock-integration
description: Adapte un bloc shadcnblocks (hero, feature, team, pricing, testimonial, faq, cta, timeline…) en bloc ou présentation du page builder Celuga — catalogue partagé, champs Payload, rendu React SSR, fixture, parité SEO et vignette admin. Use when adding `@shadcnblocks/*`, running `bunx shadcn add`, browsing shadcnblocks.com, creating or extending a Payload layout block, or wiring a new section into the block renderers.
---
# Intégration shadcnblocks Celuga
Un shadcnblock est une **maquette de référence**, jamais du code de production : on en extrait la mise en page, puis on reconstruit le rendu avec les contrats Celuga : champs Payload éditables, React **SSR**, jetons de `DESIGN.md`, zéro JavaScript client sauf besoin prouvé.
Lire `DESIGN.md`, le catalogue et la procédure `.agents/skills/celuga-add-block` du projet : **cette procédure prime** pour le branchement. Ce skill couvre ce qui lui manque : partir d'une référence shadcn.
| Référence | Quand la lire |
| --- | --- |
| [`references/catalog.md`](references/catalog.md) | étape 0 — trouver l'existant, nommer, traduire une référence |
| [`references/field-mapping.md`](references/field-mapping.md) | étapes 2 à 4 — constantes → champs → rendu |
| [`references/examples.md`](references/examples.md) | deux parcours complets |
Charger **celuga-media-react** pour tout champ `upload`, **celuga-seo** si le bloc porte une FAQ ou des entités, **shadcn** pour la CLI.
## 0. Réutiliser, étendre ou créer
1. Lire `blockCatalog` et les rendus existants. Un besoin couvert devient une **présentation** (`select`) ou un nouveau rendu d'une combinaison bloc/présentation existante, pas un nouveau bloc.
2. **Sans schéma d'abord** : un nouveau composant front sur des champs existants ne demande ni migration ni risque pour une base partagée. Signaler une petite différence de mise en page plutôt que d'ajouter une option si l'utilisateur veut éviter le schéma.
3. Nouveau bloc seulement si la structure éditoriale diffère. Fixer `slug` (intention en français, camelCase, sans accent ni numéro shadcn), libellés singulier et pluriel, groupe, composant.
4. **Conserver les slugs persistés** : Payload dérive des tables des slugs. Un renommage demandé doit migrer et prouver la conservation des données.
**Terminé quand :** décision écrite, avec slug, libellés, groupe, composant, et `rg "<slug>" packages/site-contracts/src/content.ts` ne trouve rien pour un bloc neuf.
## 1. Lire la référence
Depuis `apps/web/` (registre `@shadcnblocks` dans `components.json`, clé `SHADCNBLOCKS_API_KEY`) :
```bash
bunx shadcn add @shadcnblocks/feature61
```
Travailler dans un scratch, ou inspecter puis retirer ce que la CLI a ajouté. Si le dépôt garde un instantané sous `docs/references/`, le lire plutôt que d'appeler le registre. Noter constantes, médias, dépendances (`framer-motion`, `swiper`, `embla`…), `'use client'`, CSS inline.
**Terminé quand :** l'inventaire des données éditables et des comportements est noté, et `git status` ne montre ni fichier shadcn ni dépendance nouvelle.
## 2. Cartographier
Chaque constante devient un champ Payload ou une décision de design justifiée dans le code ([`field-mapping.md`](references/field-mapping.md)). Chaque comportement client trouve un équivalent natif : animation → système d'apparition du site et CSS, carrousel → scroll-snap, onglets et accordéons → `<details>`.
**Terminé quand :** aucune ligne de l'inventaire n'est sans destination, et chaque dépendance JS a un remplaçant natif ou une justification d'hydratation.
## 3. Catalogue et champs (si nouveau bloc)
- Entrée dans `blockCatalog` (`packages/site-contracts/src/content.ts`) : `label`, `plural`, `group`, et `cache: [...]` si le bloc lit une autre collection.
- Champs de même identifiant côté CMS (`apps/cms/src/fields/index.ts` ou fichier de `blocks/`), avec `text`, `link`, `image` des primitives. Options de section, vignette et libellés de lignes sont ajoutés automatiquement.
- Libellés et aides en français ; `admin.description` si l'effet n'est pas évident ; `admin.condition` pour les champs propres à une présentation. Localiser les seules feuilles textuelles.
- `bun run generate`, puis migration (procédure `celuga-change-schema`) : un bloc persisté crée des tables. Sur une base partagée, ne rien intégrer avant que la migration soit appliquée.
**Terminé quand :** catalogue et champs ont les mêmes identifiants, et le bloc apparaît dans l'admin sous le bon groupe.
## 4. Rendu React
`apps/web/src/components/<Nom>/index.tsx`. Commencer le fichier par la traçabilité :
```tsx
/** Mise en page : @shadcnblocks/feature61. Rendu : charte du site. */
```
- composant serveur : pas de `'use client'`, de hook ni de dépendance d'animation ;
- `return null` si rien d'affichable ; filtrer les items incomplets ;
- `<section aria-labelledby>`, titres cohérents, listes en `<ul>/<li>` ;
- images `<Media>` avec `sizes` du slot réel ; liens et texte riche par les composants du site ;
- jetons et composants `ui` : le lint refuse valeurs arbitraires, styles inline, couleurs brutes et restyle ;
- pas de `lucide-react` si le site a une autre famille d'icônes ; contraste vérifié, focus visible, `prefers-reduced-motion`.
**Terminé quand :** le composant compile, sans donnée éditable en dur, et son HTML SSR ne dépend d'aucune hydratation.
## 5. Brancher
- Table des rendus par slug (`BlockRenderers`), ou branche de la présentation concernée si le projet a plusieurs styles de page.
- Données annexes dans `prepareBlocks`, dégradation limitée au bloc, tag au catalogue (**celuga-astro-payload-cache**).
- Îlot seulement si une interaction l'exige, avec directive explicite dans `RenderBlocks/index.astro`.
- Selon le bloc : petites images embarquées, adaptateur SEO (`apps/cms/src/seo/document.ts`), règle de traduction, chemins localisés de la preview, slug de vignette `apps/cms/public/block-thumbnails/<slug>.svg`.
**Terminé quand :** le bloc s'affiche sur une page publique et dans la live preview.
## 6. Vérifier
Fixture dans `tests/fixtures/page-blocks.ts`, test de contrat catalogue → fixture → rendu → analyse, puis les contrôles du dépôt et `bun run lint`. Contrôler visuellement à 390, 768, 1024 et 1440 px, plus un grand écran : site, live preview, administration (insertion du bloc), clavier seul. Un geste éditorial particulier s'ajoute au guide éditorial.
**Terminé quand :** les commandes passent et les largeurs sont conformes. Inspecter le diff généré (types, import map, migration).
agents/openai.yaml
interface:
display_name: "Celuga shadcnblock integration"
short_description: "Adapter un shadcnblock en bloc du page builder Payload"
default_prompt: "Utilise $celuga-shadcnblock-integration pour adapter ce shadcnblock en bloc Payload + section Celuga."
references/catalog.md
# Catalogue du page builder
Source de vérité : `blockCatalog` dans `packages/site-contracts/src/content.ts`, complété par les modules actifs. Ne pas tenir de liste ici : les blocs varient selon les projets.
```bash
rg -n "label:" packages/site-contracts/src/content.ts packages/site-contracts/src/modules
sed -n 1,40p apps/web/src/components/BlockRenderers/index.ts
ls apps/cms/public/block-thumbnails
```
Hors page builder : haut de page (`hero` des contenus), en-tête et pied de page (globals). Un shadcnblock `hero*`, `navbar*` ou `footer*` modifie ces surfaces, pas le catalogue.
## Règles de nommage
| Élément | Règle | Exemple |
| --- | --- | --- |
| `slug` / `blockType` | intention, français de préférence, camelCase sans accent ni numéro ; **immuable une fois en base** | `miseEnAvant` |
| libellés | français métier, singulier et pluriel | `Mise en avant` / `Mises en avant` |
| composant | `components/<Nom>/index.tsx`, nommé par l'intention ou par la référence shadcn adaptée | `Highlight`, `Feature61` |
| présentation | `select` nommé par le rendu, pas par le numéro shadcn | `presentation: "card"` |
| vignette | `apps/cms/public/block-thumbnails/<slug>.svg`, légère et sans texte | `miseEnAvant.svg` |
| traçabilité | commentaire en tête du composant | `/** Mise en page : @shadcnblocks/feature61. … */` |
Un slug historique numéroté reste tel quel : ne pas le migrer pour l'esthétique.
## Lire un fichier shadcnblocks
| Signal dans la référence | Traduction Celuga |
| --- | --- |
| `const items = [...]` | `array` Payload avec `minRows`, `labels` |
| URL CDN d'image | `upload` → `media`, rendu `<Media>` |
| tableau dupliqué pour une boucle infinie | une seule liste CMS ; défilement natif |
| `framer-motion`, `motion` | système d'apparition du site, CSS, `prefers-reduced-motion` |
| `swiper`, `embla-carousel` | scroll-snap ; îlot seulement si indispensable |
| onglets, accordéon | `<details>`/`<summary>` natifs |
| `<style>` inline, valeurs `[...]`, polices custom | utilitaires Tailwind + jetons ; refusés par le lint dans les `.tsx` |
| `'use client'` | supprimé ; rendu SSR |
| icônes `lucide-react` | famille d'icônes du site |
| `rounded-*`, ombres, couleurs par défaut | charte de `DESIGN.md` |
| graphique (`recharts`) | îlot `client:visible` justifié, exception de lint limitée à son fichier |
references/examples.md
# Parcours types
## A. Nouvelle présentation sans schéma
Une référence `@shadcnblocks/feature61` couvre le besoin d'un bloc « Image et texte » existant.
| Étape | Décision |
| --- | --- |
| existant proche | bloc image + texte : mêmes champs → aucune migration |
| composant | `apps/web/src/components/Feature61/index.tsx` |
| branchement | branche de la table de rendu, ou rendu associé à une valeur de `presentation` déjà en base |
| largeurs alternées, fond | code |
| animation d'apparition | utilitaire du site |
Fichiers touchés : le composant, la table de rendu, une fixture si la présentation est nouvelle. Aucun fichier du CMS. Vérifier : HTML SSR, live preview, quatre largeurs, parité SEO inchangée.
## B. Nouveau bloc
Une référence de frise sans équivalent : structure éditoriale différente.
| | Valeur |
| --- | --- |
| référence | `@shadcnblocks/timeline9` |
| `slug` | `frise` |
| libellés, groupe | Frise chronologique / Frises chronologiques, Contenu |
| composant | `apps/web/src/components/Timeline9/index.tsx` |
| variante | `presentation` en `select`, plutôt qu'un second bloc |
| Référence | Destination |
| --- | --- |
| titre, sous-titre | `title`, `intro` |
| { date, titre, texte } | `items[]` avec `minRows: 1`, champs localisés |
| ligne verticale, puces | code |
| animation | utilitaire d'apparition du site, sans `framer-motion` |
```text
packages/site-contracts/src/content.ts # entrée du catalogue
apps/cms/src/fields/index.ts ou src/blocks/ # champs de même identifiant
packages/site-contracts/src/generated/payload-types.ts # bun run generate
apps/cms/src/app/_payload/importMap.js # bun run generate, si composants admin
apps/cms/src/migrations/ # migration relue, index.ts
apps/cms/public/block-thumbnails/frise.svg # vignette
apps/web/src/components/Timeline9/index.tsx # rendu
apps/web/src/components/BlockRenderers/index.ts # table de rendu
apps/cms/src/seo/document.ts # extraction SEO
packages/site-contracts/src/i18n.ts # règle de traduction, si champs requis
apps/web/src/lib/preview-request.ts # chemins localisés de la live preview
tests/fixtures/page-blocks.ts # fixture
```
Selon le bloc s'ajoutent : `lib/page-builder.ts` et `cache` au catalogue (données d'une autre collection), `RenderBlocks/index.astro` (îlot ou petites images), `tutorial/questions.ts` (geste particulier).
## Pièges
- Le test de contrat compare catalogue et fixtures : un bloc sans fixture le fait échouer.
- Une feuille localisée absente des chemins de preview s'affiche vide dans la live preview, alors que la page publiée est correcte : toujours tester les deux.
- Un bloc absent de l'adaptateur SEO signale une couverture incomplète dans l'assistant.
references/field-mapping.md
# Cartographie shadcnblock → Payload → rendu
Inventorier **chaque** constante et chaque comportement de la référence avant d'écrire les champs.
## Par type de contenu
| Dans la référence | Champ Payload | Rendu |
| --- | --- | --- |
| titre | `text("title", "Titre", true)` | `<h2>` lié par `aria-labelledby` |
| surtitre, badge | `text`, « Laisser vide pour masquer » | rendu conditionnel |
| introduction simple | `textarea` localisé | `<p>` |
| paragraphe riche | `richText` localisé | `RichText` |
| bouton, lien | `link()` des primitives | bouton du site, si `linkURL` donne une adresse |
| image | `image` ou `upload` → `media` | `<Media size sizes>` |
| logo, picto | `upload` → `media` | `<Media size="icon" inlineIfSmall>` et embarquement côté serveur |
| liste homogène | `array` + `minRows`/`maxRows` + `labels` | `<ul>` de `<li>` filtrés |
| chiffre + libellé | `array` { `value`, `label` } | chiffres tabulaires |
| variante visuelle | `select` avec `defaultValue` | classes conditionnelles, jamais un second bloc |
| champ propre à une variante | `admin.condition` | testé dans le rendu |
| formulaire | `relationship` → `forms` | bloc formulaire existant |
| contenu d'une autre collection | `select` de source + `relationship` | `prepareBlocks` + tag au catalogue |
Le `array` reçoit automatiquement `initCollapsed` et un libellé de ligne lisible par la construction de `blocks` dans `fields/index.ts`.
Les items sans lesquels une ligne ne s'affiche pas sont `required: true`. Localiser `value`, `label`, `title`, `text` ; ne pas localiser relations ni structure.
## Reste côté code (pas en CMS)
- grilles, gaps, alternances de colonnes ;
- hauteurs de logos, ratios d'image, découpes ;
- animations et leurs délais ;
- nombre de colonnes par breakpoint.
Un éditeur choisit du contenu et, au plus, une présentation nommée ; il ne règle pas la mise en page.
## Mouvement
Ne pas recopier les durées d'une maquette AOS ou d'une référence : elles sont souvent trop lentes. Passer par l'utilitaire d'apparition du site. Pas d'animation d'entrée sur le haut de page.
## Pattern de rendu
```tsx
/** Mise en page : @shadcnblocks/feature61. Rendu : charte du site. */
export function Feature61({ block, locale = "fr" }: { block: Row; locale?: Locale }) {
const items = (block.items || []).filter((item: Row) => item.title?.trim());
if (!items.length) return null;
const titleId = domId("feature-titre", block.id);
return (
<Section labelledBy={titleId}>
<h2 id={titleId} className="text-balance">{block.title}</h2>
<ul className="m-0 grid list-none gap-3 p-0">
{items.map((item: Row, index: number) => (
<li key={item.id || index}>{/* … */}</li>
))}
</ul>
</Section>
);
}
```
- Vérifier qu'une relation est peuplée avant de rendre une image.
- `alt` vide pour une image décorative ; le composant `Media` le gère.
- Les grilles réagissent à leur conteneur (`@container`, variantes `@min-[...]`) quand le bloc peut changer de largeur.
## Îlot client (exception)
Uniquement si une interaction React est indispensable :
1. branche explicite dans `RenderBlocks/index.astro`, avec `client:visible` ;
2. props sérialisées minimales ;
3. un composant SSR sans directive pour tout le reste du bloc.
Un test de contrat vérifie que les blocs rendus sans cet adaptateur ne contiennent ni `astro-island` ni `<script>`.