# Sites connectés : le standard Chamans

> Studio : https://studio.chamans.io · connecteur `@weapzy/astro` 0.3.9

Un site Astro (ou n'importe quel site statique) construit **hors du CMS** se gère dans Studio comme un site WordPress :
pages, articles, menus, images, référencement, favicon, leads, performance. Ce document fixe les règles à suivre dès la
création du site pour qu'il se branche **en trois étapes** et qu'il **apparaisse tout seul dans Studio à sa première
mise en ligne**. Aucune de ces règles ne change le rendu visuel du site.

> Connecteur : `@weapzy/astro`, paquet npm de l'agence. Les modifications de Studio s'appliquent au HTML construit ; le HTML
> qui n'est pas modifié est conservé octet pour octet.

---

## 1. Ce que Studio gère sur un site connecté

| Écran de Studio | Ce qu'on y fait | Mise en ligne |
|---|---|---|
| **Éditeur visuel** | Textes, images, liens, apparence d'un bloc (couleurs, tailles, marges, arrondi, masquer), texte alternatif, recherche et remplacement dans tout le site, historique ; **structure** : supprimer, dupliquer, déplacer un élément, ajouter des blocs (titre, texte, bouton, image, liste, séparateur, espace) à n'importe quel niveau (§ 3.10) | Directe* |
| **Pages** | Toutes les pages réelles par langue ; page d'accueil, page d'erreur et redirections repérées ; titre, description, image de partage et **visibilité dans Google** page par page | Directe* |
| **Articles** | Articles du site par rubrique et par date, avec vignette ; référencement de chacun ; **écrire, programmer et retirer des articles** dans Studio (§ 3.4) | Directe* |
| **Menus** | Menus de l'en-tête, de la navigation et du pied de page : **ordre des entrées** (glisser-déposer, aussi dans l'éditeur visuel), libellé, destination, nouvel onglet, masquer une entrée | Directe* |
| **Médias** | Images réellement affichées (et pages qui les emploient) : remplacement et texte alternatif valables pour tout le site ; bibliothèque des fichiers envoyés | Directe* |
| **Paramètres › Identité et SEO** | Favicon, nom du site au partage (`og:site_name`), image de partage par défaut | Directe* |
| **Leads** | Demandes envoyées par les formulaires du site (§ 3.8) | Immédiate |
| **Performance, SEO** | Audit hebdomadaire du site en ligne (Lighthouse, SEO page par page, disponibilité) | — |

\* **Directe** : publiée sur le serveur du site sans reconstruire le site, si Studio a un accès restreint à ce serveur
(§ 4.2). Sinon, au déploiement suivant du site (le build réapplique toutes les modifications). Quand le code du site est
dans le dépôt des sites, Studio **reconstruit** le site à chaque mise en ligne, modifications comprises (§ 4.3).

**Ce qui passe encore par le code du site** : créer ou supprimer une page, changer la mise en page d'ensemble (grilles,
nouvelles sections dessinées), supprimer un article écrit dans le code, mettre à jour les données structurées (JSON-LD)
d'un contenu retiré ou ajouté dans l'éditeur. Le développeur modifie le code et redéploie : les modifications faites dans Studio sont
réappliquées automatiquement à chaque déploiement.

---

## 2. Brancher un site en trois étapes

### Le plus rapide : un agent IA

Studio › **Hub › Sites › Nouveau site › Avec un agent IA** (ou, pour un site déjà dans Studio, sa fiche › **Connexion** ›
« Laisser un agent IA faire le branchement ») : Studio donne **une ligne à coller** dans votre agent (Claude Code,
Cursor, Codex…), ouvert dans le dépôt du site. L'agent lit ses instructions sur Studio (`/install/<lien>`), ajoute le
site, installe le connecteur, déclare `weapzy()`, met le HTML au standard avec `weapzy-check` (zéro avertissement),
**sans changer le rendu**, range les variables dans chaque chemin de mise en ligne de production, met le site en ligne
avec l'accord de la personne et prépare la publication directe. Studio lit aussitôt les pages en ligne du site (elles
apparaissent dans le CMS sans attendre un déploiement) et affiche la progression de l'agent en direct. **L'installation
n'est terminée que lorsque Studio a vérifié que le site de production sert le build branché et qu'il peut y publier
les modifications** : un build local ou de préproduction ne suffit pas. Le lien vaut 24 heures et n'ajoute qu'un site. Pour un agent qui part de zéro : `https://studio.chamans.io/install.md`
(index pour les agents : `/llms.txt`).

Les étapes ci-dessous décrivent le même branchement à la main.

### Étape 1 — Les clés

Deux façons de faire, au choix :

- **Le site existe déjà dans Studio** (Hub › Sites › Nouveau site › « Site Astro connecté ») : sa fiche › **Connexion**
  donne une **clé de lecture** et une **clé de déploiement**, propres à ce site.
- **Enregistrement automatique** (une seule clé pour tous les sites de l'agence) : Studio › **Clés API** › **Générer une
  clé** › Portée « Tous mes sites » (« Toute la plateforme » pour le super-administrateur), Droits **« Connexion des
  sites (au déploiement) »**. Au premier déploiement, le site est retrouvé par son domaine ou créé, au nom de la
  personne qui a créé la clé et dans son périmètre.

Les variables se rangent dans l'environnement de **déploiement** (secrets de la CI, fichier `.env.weapzy` du script de
mise en ligne), **jamais** dans le dépôt ni dans un `.env` de développement : un build local enverrait à Studio des
pages qui ne sont pas en ligne.

```sh
WEAPZY_API_URL=https://studio.chamans.io/api/delivery/v1
WEAPZY_API_KEY=wzk_…       # clé de lecture du site
WEAPZY_DEPLOY_KEY=wzk_…    # clé de déploiement du site
# ou, pour l'enregistrement automatique :
WEAPZY_CONNECT_KEY=wzk_…
```

### Étape 2 — Le connecteur dans le site

`@weapzy/astro` est publié sur npm par l'agence. Installer la version que sert Studio (celle de l'onglet **Connexion**) :

```sh
npm install @weapzy/astro@0.3.9
```

Un site branché avant la publication garde sa copie dans `vendor/weapzy-astro`, qui continue de fonctionner ; pour passer
au paquet npm : `npm uninstall @weapzy/astro && rm -rf vendor/weapzy-astro`, puis la commande ci-dessus. Ensuite, pour un
**site Astro**, déclarer l'intégration avec
l'adresse publique **exacte** du site dans `site` (`www` compris s'il en a un : c'est celle des canoniques) :

```js
// astro.config.mjs
import { defineConfig } from 'astro/config';
import { weapzy } from '@weapzy/astro';

export default defineConfig({
  site: 'https://www.exemple.fr',
  output: 'static',
  integrations: [/* …autres intégrations… */ weapzy()], // en dernier
});
```

**Autre site statique** (HTML écrit à la main, Eleventy, Hugo…) : après le build, avant l'envoi sur le serveur :

```sh
WEAPZY_SITE_URL=https://www.exemple.fr npx --no-install weapzy-apply dist
```

Sans les variables, le build fonctionne normalement : le connecteur signale « Build sans Studio » et ne fait rien
(`weapzy-apply` sort en 0).

### Aperçu local des modifications de Studio

Pour voir en local le site tel qu'il sera en ligne, avec les modifications faites dans Studio, construire avec la seule
**clé de lecture** : le build lit les modifications et les applique, sans rien envoyer à Studio.

```sh
WEAPZY_API_URL=https://studio.chamans.io/api/delivery/v1 WEAPZY_API_KEY=wzk_… npm run build && npx astro preview
```

Ne jamais ajouter `WEAPZY_DEPLOY_KEY` ni `WEAPZY_CONNECT_KEY` à un build local : ce build enverrait à Studio des pages
qui ne sont pas en ligne.

### Étape 3 — Déployer

À la mise en ligne, le build :

1. envoie à Studio l'adresse du site et ses pages d'origine ;
2. Studio **retrouve le site par son domaine** (avec ou sans `www`), ou **le crée** comme site connecté, nommé d'après
   son `og:site_name` (sinon son titre) ;
3. Studio renvoie les modifications faites dans le CMS, que le build applique avant l'envoi sur le serveur.

Le site apparaît dans Studio › Sites avec ses pages, ses articles, ses menus et ses images. Si Studio refuse (domaine
rattaché à un autre compte, site construit dans Studio, clé révoquée), **le build s'arrête** sans toucher au site.

### Étape 4 (recommandée) — Publication directe

Pour que les modifications partent en ligne en quelques secondes, sans attendre un déploiement : Studio › fiche du site ›
**Déploiement** › **Serveur du site** (voir § 4.2).

---

## 3. Le contrat HTML

Studio lit le site **construit** : tout ce qui suit doit être présent dans le HTML livré au navigateur (pas seulement
rendu par JavaScript).

### 3.1 En-tête de chaque page

```html
<html lang="fr">
<head>
  <meta charset="utf-8">
  <title>Titre de la page | Nom du site</title>
  <meta name="description" content="Une ou deux phrases, 70 à 160 caractères.">
  <link rel="canonical" href="https://www.exemple.fr/la-page">
  <meta property="og:type" content="website">            <!-- « article » pour un article -->
  <meta property="og:title" content="Titre de la page">
  <meta property="og:description" content="…">
  <meta property="og:image" content="https://www.exemple.fr/media/partage.jpg">
  <meta property="og:site_name" content="Nom du site">
  <meta name="twitter:card" content="summary_large_image">
  <link rel="icon" href="/favicon.svg" type="image/svg+xml">
</head>
```

| Élément | Règle |
|---|---|
| `<html lang>` | Obligatoire : Studio range les pages et les menus par langue. |
| `<title>`, `description` | Obligatoires ; titre de 30 à 60 caractères, description de 70 à 160. |
| `rel="canonical"` | Obligatoire, **adresse absolue de la page telle que le public la voit**. Indispensable si le serveur réécrit les adresses (`menu.html` servie sous `/la-carte`) : c'est par elle que Studio relie la page à son fichier. |
| `og:*` | Recommandés. Studio les modifie s'ils existent et les ajoute s'ils manquent. Les cartes `twitter:*` présentes suivent leurs balises `og:`. |
| `og:site_name` | Recommandé : c'est le nom du site dans Studio à sa création. |
| `rel="icon"` | Recommandé. Le favicon choisi dans Studio remplace toutes les icônes (`icon`, `shortcut icon`, `apple-touch-icon`). |
| `name="robots"` | Optionnel : Studio le pose ou le retire page par page (« Visible dans Google »). |

### 3.2 Structure de page

- Un `<header>`, un `<main>`, un `<footer>` par page ; la navigation dans `<nav aria-label="…">`.
- **Un seul `<h1>`** par page : c'est le nom de la page dans Studio.
- Le contenu éditable est du **texte dans le HTML** : titres, paragraphes, boutons, liens, listes. Un texte injecté par
  JavaScript après le chargement n'est pas modifiable.
- Ce qui est dans `<header>`, `<nav>` et `<footer>` est modifié **pour tout le site** (un menu se renomme partout) ; le
  reste, page par page.

### 3.3 Menus

**Facultatif.** Sans repère, Studio reconnaît seul les menus (vérifier avec `weapzy-check dist --explain`). Les nommer
avec `data-weapzy-menu` seulement s'ils sont mal découpés ; le repère n'a aucun effet visuel.

```html
<nav aria-label="Menu principal">
  <ul data-weapzy-menu="Menu principal">
    <li><a href="/">Accueil</a></li>
    <li><a href="/services">Services</a></li>
    <li><a href="/contact">Contact</a></li>
  </ul>
</nav>

<footer>
  <div data-weapzy-menu="Informations">
    <a href="/mentions-legales">Mentions légales</a>
    <a href="/cookies">Cookies</a>
  </div>
</footer>
```

- **Dès qu'un repère `data-weapzy-menu` existe sur une page, seuls les conteneurs marqués comptent** : marquer alors
  chaque liste de liens de l'en-tête, du menu mobile et du pied de page (réseaux sociaux et liens isolés compris), d'un
  nom **différent** par liste. Une liste oubliée disparaît de Studio ; `weapzy-check` signale un marquage incomplet.
- Sans repère, Studio reconnaît seul : listes (`ul`/`ol`) et zones `nav` de l'en-tête et du pied de page, colonnes
  « titre + liens » (`div > h2 + a`), étiquette tirée de `aria-label`, d'un titre ou d'un court paragraphe placé juste
  avant la liste ; le logo est écarté ; les liens vers Instagram, Facebook, TikTok… forment le menu « Réseaux sociaux ».
- Un menu répété à l'identique (version bureau et version mobile) n'apparaît qu'une fois dans Studio, et chaque
  changement s'applique aux deux copies. Deux menus presque identiques (le menu mobile a une entrée de plus) restent
  distincts, mais Studio propose de garder leur ordre et leurs libellés alignés.
- **Ordre des entrées** (`@weapzy/astro` 0.3.4 et plus pour un site reconstruit par son propre déploiement) : Studio
  déplace les enfants du plus proche conteneur commun des liens du menu (les `li` d'une liste, ou les liens d'un `nav`).
  Un élément qui regroupe plusieurs liens (icônes de réseaux sociaux dans un `span`) se déplace d'un bloc. Un menu dont
  un lien figure deux fois, ou dont les copies ne sont pas découpées de la même façon, ne se réordonne pas : chaque
  entrée doit être un enfant direct de la liste, fermé par sa balise (`</li>`).
- Une entrée en icône a un texte accessible (`aria-label` ou texte masqué visuellement) : c'est son nom dans Studio.
- Un texte réservé aux lecteurs d'écran est ignoré s'il est marqué `aria-hidden="true"` (« EN » visible + « English
  version » masqué → « English version »).
- Pour masquer une entrée, Studio masque l'élément de liste qui ne contient qu'elle (`<li>`) : une entrée = un `<li>`.

### 3.4 Articles

Un article est reconnu à ses **données structurées** :

```html
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "Titre de l'article",
  "datePublished": "2026-10-13",
  "articleSection": "Mariages",
  "image": "https://www.exemple.fr/media/article.jpg",
  "author": { "@type": "Organization", "name": "Nom du site" }
}
</script>
```

- Types reconnus : `BlogPosting`, `Article`, `NewsArticle` (aussi dans un `@graph`), ou `og:type` = `article`.
- **Rubrique** = premier segment de l'adresse (`/actualites/mon-article` → « actualites »). Son nom affiché vient, dans
  l'ordre : de `articleSection` (ou de la balise `<meta property="article:section">`) **s'il est le même pour tous les
  articles du dossier** ; sinon du `<h1>` de la page qui porte cette adresse (`/actualites` → « Le journal ») ; sinon de
  l'adresse elle-même (« Actualites »). Un nom donné par l'équipe dans Studio (écran Articles) l'emporte toujours. Sur
  un site déjà dans Studio, ne pas ajouter `articleSection` : la rubrique y a déjà son nom.
  Une langue en préfixe est ignorée (`/en/news/x` → rubrique « news »).
- `datePublished` (format ISO) classe les articles ; `image` donne la vignette.

**Articles écrits dans Studio.** Studio › Articles › **Nouvel article** : l'article est rendu dans le modèle d'un vrai
article du site (même en-tête, pied de page, styles), à l'adresse `/<rubrique>/<slug>`, et sa carte s'ajoute en tête de
la liste des articles, dans le plan du site (sitemap) et dans le flux RSS ou Atom, sur le modèle d'un article existant ;
ses images sortent comme celles d'Astro (§ 3.5). Il part en ligne par la publication directe, **sans reconstruire le
site**, ou par le build de Studio (§ 4.3) ; les builds du site le régénèrent ensuite à l'identique (`weapzy()`,
`weapzy-apply`, connecteur 0.3.5 et plus pour le sitemap, le flux et les images). L'image de la page et de la carte est
l'image à la une, sinon l'image de partage ; sans l'une ni l'autre, Studio refuse la mise en ligne (le modèle montrerait
la photo d'un autre article). La carte prend pour modèle une carte de la même rubrique (filtres de la liste compris). Un
article repris du site et modifié dans Studio met aussi à jour son image et son titre dans les autres listes de cartes
du site (« À lire aussi », accueil) — connecteur 0.3.6 et plus ; s'il garde l'image du site, ses cartes gardent la leur
et sa page son texte alternatif (0.3.8). Le texte alternatif d'une photo de Studio est celui saisi dans Médias, sinon le
titre. Les cartes ajoutées par Studio portent `data-weapzy-ignore` : l'éditeur visuel travaille sur la liste d'origine,
ses modifications restent sur leurs cartes (0.3.8). Pour un rendu fiable, marquer le modèle :

```html
<!-- page d'article -->
<h1 data-weapzy-article="title">…</h1>
<time data-weapzy-article="date" datetime="2026-10-13">13 octobre 2026</time>
<img data-weapzy-article="image" src="…" alt="…">
<div class="prose" data-weapzy-article="body">…</div>

<!-- page de liste : chaque enfant est une carte (lien, titre, image, <time>, extrait) -->
<ul data-weapzy-articles>
  <li><a href="/actualites/mon-article"><img …><h2>…</h2></a><time datetime="…">…</time><p class="excerpt">…</p></li>
</ul>
```

Sans repère, Studio prend : le premier `<h1>`, le corps `.prose` (ou `.article-body`, `.post-content`,
`.entry-content`, `itemprop="articleBody"`, sinon le bloc qui contient le plus de paragraphes), la première image avant
le corps, la date écrite en toutes lettres avant le corps, et la plus grande liste de cartes menant à des articles
(4 cartes au moins : un bloc « À lire aussi » n'est pas touché).

- La **rubrique** d'un article est choisie parmi celles du site ; elle est fixée tant que l'article est en ligne. Changée
  après un passage en brouillon, l'ancienne adresse renvoie vers la nouvelle (jamais deux pages en ligne).
- Un article **planifié** ne laisse rien en ligne avant sa date ; à sa date, Studio lance la mise en ligne du site (avec
  ses autres changements en attente ; une publication soumise à la validation de l'agence l'attend toujours).
- « Publier » dans l'éditeur d'article enregistre l'article et ouvre la fenêtre de mise en ligne ; tant que le site n'a
  pas été mis en ligne depuis, l'éditeur affiche « Publié, pas encore en ligne ».
- Un article **retiré** (dépublié ou mis à la corbeille) devient une page qui renvoie vers sa rubrique, exclue de Google :
  Studio ne supprime jamais de fichier sur le serveur. Sans page de rubrique, le renvoi mène à la liste des articles,
  sinon à l'accueil ; un article repris retiré quitte aussi les listes de cartes du site (connecteur 0.3.8).
- Sans aucune page d'article dans le site, « Nouvel article » est désactivé : il faut un premier article dans le code
  pour servir de modèle.

**Articles déjà dans le site.** Un clic sur un article existant (écran Articles, tableau de bord) l'ouvre dans le même
éditeur : Studio en reprend le titre, la date, l'image en tête, la description, la rubrique et le corps, tels qu'ils
sont en ligne (retouches de l'éditeur visuel comprises). L'adresse et la rubrique ne changent pas. Tant que l'article
n'est pas modifié (l'enregistrer sans rien changer ne compte pas), sa page reste celle du site ; modifié, elle est
réécrite à la même adresse à la publication suivante, et sa carte mise à jour dans la liste (aucune carte en plus). Le
corps passe par l'éditeur de texte : une mise en forme propre au site (classes, encadrés, colonnes) peut s'y simplifier.
**Revenir à la version du site** (bandeau de l'éditeur) abandonne la copie de Studio : la page d'origine revient à la
publication suivante. **Retirer du site** (ex-corbeille) le retire : son adresse renvoie vers sa rubrique.
Prise en charge à partir de `@weapzy/astro` 0.3.2 pour les builds (`weapzy()`, `weapzy-apply`) ; une version plus
ancienne laisse simplement la page d'origine en place.

### 3.5 Images

- `<img src="…" alt="…" width="…" height="…">` : `alt` décrit l'image ; `alt=""` seulement pour une image décorative.
- L'adresse `src` identifie l'image dans Studio : la remplacer dans le code crée une nouvelle image (son ancien
  remplacement ou texte alternatif ne s'applique plus).
- Les images de fond CSS et celles injectées par JavaScript ne sont pas modifiables.
- Une image remplacée dans Studio sort **comme une image d'Astro** : mêmes largeurs (`srcset`) et mêmes formats que
  l'image d'origine (sources AVIF/WebP d'un `<picture>`), `sizes` et `width`/`height` gardés (hauteur recalculée sur le
  ratio de la nouvelle image), fichiers écrits dans `/_astro/` sous un nom d'empreinte. Le site n'appelle jamais Studio
  pour afficher une image. Côté build du site, il faut `sharp` (installé avec Astro) et le connecteur 0.3.5 ou plus.
- Une photo du site lui-même choisie dans Studio (onglet « Photos du site », adresse absolue avec ou sans `www`) est
  lue dans le site construit et servie depuis le site, en variantes comme les autres : une adresse absolue sur l'autre
  forme du domaine serait bloquée par une CSP `img-src 'self'` (connecteur 0.3.7 et plus).

### 3.6 Identité des éléments : garder le balisage stable

Une modification est liée à son élément par une **empreinte** : balise + `id` + classes + début du texte (la source
pour une image), et son rang parmi les éléments de même empreinte.

- Classes **stables et lisibles** (BEM, utilitaires figés). Les classes de portée d'Astro (`astro-xxxx`) et les
  attributs `data-astro-cid-*` sont ignorés.
- Changer dans le code le texte d'origine, la balise ou les classes d'un élément rend sa modification **orpheline** :
  elle est signalée dans l'éditeur et au build (« modification sans texte correspondant »), jamais appliquée ailleurs.
- `data-weapzy-ignore` exclut un élément et son contenu de l'éditeur : bandeau de cookies, widgets, compteurs,
  contenus générés.

```html
<section class="consent" data-weapzy-ignore>…</section>
```

### 3.7 Pages et adresses

- Une page = un fichier HTML (`dist/services/index.html` ou `dist/services.html`) ; une page `404.html`.
- Redirections : pages de redirection d'Astro (`redirects` dans la config, `meta http-equiv="refresh"`) ou règles du
  serveur ; Studio les liste à part et ne les modifie pas.
- Réécriture serveur (`.htaccess`, nginx) : chaque page porte sa canonique absolue (§ 3.1).
- Le plan du site (`/sitemap.xml` ou `/sitemap-index.xml`) sert aux audits et au sélecteur de pages.

### 3.8 Formulaires → Leads

Les formulaires envoient leurs demandes à Studio, qui les range dans **Leads** et prévient par e-mail :

```html
<form method="post" action="https://studio.chamans.io/api/public/forms/<identifiant du site>">
  <input name="nom" required>
  <input name="email" type="email" required>
  <textarea name="message"></textarea>
  <input name="_gotcha" tabindex="-1" autocomplete="off" hidden> <!-- piège à robots : laisser vide -->
  <input type="hidden" name="_redirect" value="https://www.exemple.fr/merci/"> <!-- page de remerciement du site -->
  <button>Envoyer</button>
</form>
```

- L'identifiant du site est dans Studio › fiche du site › **Connexion**.
- **Jamais de coupure** : chaque demande reçue est envoyée par e-mail à l'adresse de Studio › Paramètres
  (notifications). Tant que Studio n'envoie pas d'e-mails, un formulaire qui fonctionne reste tel quel ; l'agent
  d'installation indique seulement à Studio l'adresse qui reçoit aujourd'hui les demandes (`POST /api/install/v1/forms`).
- En `fetch` (JSON), la réponse est `202 { ok: true }` ; un formulaire HTML classique est renvoyé vers `_redirect`
  (une page du site lui-même, sur son domaine : toute autre adresse est ignorée), à défaut vers une page de
  remerciement de Studio. 40 champs au plus, 2 000 caractères par champ, 16 Ko par envoi, 5 envois par minute et par visiteur.

### 3.9 Langues

- `<html lang>` sur chaque page ; l'accueil de chaque langue à `/` et `/<langue>` (`/en`).
- Les menus sont lus sur l'accueil de chaque langue ; Studio propose une langue à la fois.

### 3.10 Structure modifiée dans l'éditeur (connecteur 0.3.9)

L'éditeur visuel retire, déplace et ajoute des éléments ; le build applique ces changements au HTML construit, comme les
autres modifications :

- **Supprimer** (`remove`) : l'élément et son contenu sortent de la page. Un élément seul dans son `<li>` (lien de menu,
  article d'une liste) emmène son `<li>` ; une image emmène son `<picture>`.
- **Déplacer** (`move`) : l'élément part avant, après, au début ou à la fin d'un autre, avec ses modifications. Un
  élément de l'en-tête ou du pied de page reste dans sa zone (il vaut pour tout le site).
- **Ajouter** (`insert`) : un bloc de la palette prend la balise et les classes d'un élément voisin du même genre (titre,
  paragraphe, bouton, liste), donc le dessin du site, attributs `data-astro-cid-*` compris. « Dupliquer » copie
  l'élément tel qu'il est publié. Le fragment est assaini : ni script, ni formulaire, ni gestionnaire d'événement, ni
  `id` (un `id` copié serait en double) ; les images de Studio sont optimisées dans `/_astro/` comme les autres.

Les positions se calculent sur les pages d'origine : retirer, déplacer ou ajouter ne change pas l'empreinte des autres
éléments (§ 3.6). Un bloc posé contre un élément disparu du code n'est pas appliqué et apparaît comme « introuvable »
dans l'éditeur. Un contenu retiré ou ajouté dans l'éditeur ne met pas à jour les données structurées de la page
(`Menu` d'une carte, `FAQPage`…) : à reporter dans le code quand elles comptent pour le référencement.

---

## 4. Déploiement

### 4.1 Chaîne de mise en ligne

```
build (astro build)  →  weapzy() : pages d'origine envoyées à Studio, modifications appliquées  →  envoi sur le serveur
```

- L'envoi peut être complet (`rsync --delete`) : le build contient déjà les modifications de Studio.
- **Chaque chemin de mise en ligne de production** (CI, script local de chaque développeur) charge les variables de
  Studio : un déploiement fait sans elles efface les modifications de Studio.
- Pas de `WEAPZY_CONNECT_KEY` ni de `WEAPZY_DEPLOY_KEY` dans une préproduction ou un build de contrôle.

**Build local, préproduction, production.** Chaque page d'un build qui envoie ses pages à Studio porte l'empreinte de
ce build (`<meta name="weapzy-build" content="…">`, `@weapzy/astro` 0.3.3 et plus). Studio garde ces pages **en
attente** et ne les prend pour référence (éditeur visuel, publication directe) qu'une fois l'empreinte servie par le
site en ligne, vérifiée à chaque passage du moniteur (5 minutes) et avant chaque publication directe. Ainsi :

- un développeur modifie le code et met en ligne, depuis son poste ou la CI : le build réapplique les modifications de
  Studio, et Studio reprend ses pages dès qu'elles sont en ligne ;
- un build local ou de préproduction qui envoie ses pages par erreur est ignoré : il n'est jamais servi en ligne ;
- un déploiement fait **sans** les variables de Studio est repéré (Hub › À traiter : « Site en ligne différent de
  Studio ») ; Studio cesse alors de publier sur le site, pour ne pas écraser ce déploiement, jusqu'au prochain
  déploiement fait avec les variables.

Exemple GitHub Actions :

```yaml
- name: Construire
  env:
    WEAPZY_API_URL: https://studio.chamans.io/api/delivery/v1
    WEAPZY_CONNECT_KEY: ${{ secrets.WEAPZY_CONNECT_KEY }}
  run: npm ci && npm run build
- name: Envoyer sur le serveur
  run: rsync -az --delete -e "ssh -i ~/.ssh/deploy_key" dist/ "$USER@$HOST:$PATH/"
```

Exemple de script local (le secret vit dans `.env.weapzy`, ignoré par git) :

```sh
set -a; . ./.env.weapzy; set +a   # WEAPZY_API_URL, WEAPZY_CONNECT_KEY
npm run build
rsync -az --delete dist/ site@serveur:/home/site/htdocs/exemple.fr/
```

### 4.2 Publication directe (Studio écrit sur le serveur)

Studio › fiche du site › **Déploiement** › **Serveur du site** :

1. Saisir l'hôte, le port SSH, l'utilisateur et le **dossier du site** sur le serveur, puis **Générer l'accès**. Studio
   crée sa propre clé SSH (la clé privée reste chiffrée dans Studio, jamais affichée).
2. Copier la ligne proposée dans `~/.ssh/authorized_keys` de l'utilisateur du site :

   ```
   command="rrsync -no-del -munge /home/site/htdocs/exemple.fr",restrict ssh-ed25519 AAAA… studio-exemple
   ```

   L'accès est limité à ce dossier, **sans shell ni suppression**. `rrsync` est fourni avec rsync (CloudPanel :
   `/usr/bin/rrsync`) ; sur un hébergement mutualisé qui ne l'a pas (o2switch), copier le script dans `~/bin/rrsync` et
   remplacer `rrsync` par `python3 /home/<utilisateur>/bin/rrsync`.
3. **Tester l'accès** : Studio liste les pages HTML du dossier.
4. **Reprendre les pages en ligne** (tant qu'aucune modification n'existe) si le site n'a pas encore été déployé avec la
   clé de connexion.

Ensuite, chaque modification part en ligne après la mise en ligne automatique (30 secondes de calme par défaut, réglable
dans l'onglet Déploiement) : Studio envoie **seulement les pages HTML dont le contenu change** ; les autres fichiers et les
autres pages ne sont jamais touchés.

### 4.3 Code source dans le dépôt des sites (build par Studio)

Le code de chaque site vit dans **un dossier du dépôt des sites** (`git@github.com:Weapzy/sites-clients.git`), nommé
comme le site dans Studio. Studio › fiche du site › **Déploiement** › **Code source** : choisir le dossier, le type de
site (Astro : `npm run build` → `dist` ; Next.js en export : → `out` ; HTML statique : aucune commande), et les
**variables de build** (celles du `.env` du site : identifiant Google Analytics, clé Turnstile…, chiffrées dans Studio,
jamais réaffichées). Le dépôt ne contient aucun secret.

À chaque mise en ligne (« Publier »), Studio fait exactement ce que ferait le développeur :

1. extrait le dossier du site (branche `main`) ;
2. installe et construit dans un **conteneur Docker jetable** (seul le dossier du site y est monté, ses variables de
   build, aucun secret de la plateforme ni clé de Studio ; réseau sortant vers Internet seulement) ;
3. applique au résultat ce que `weapzy()` appliquerait : pages d'origine gardées comme référence (marquées de
   l'empreinte du build), articles écrits dans Studio, modifications, images optimisées comme celles d'Astro ;
4. envoie le build sur le serveur du site : **fichiers d'abord, pages ensuite, jamais de suppression** (accès
   `rrsync -no-del`). Un fichier retiré du code reste donc sur le serveur ; le retirer à la main si besoin. Les
   fichiers que le déploiement du développeur n'envoie pas (un `.htaccess` sur un serveur nginx, des `*.map`…) se
   règlent dans **Jamais envoyés sur le serveur**.

Le résultat est celui d'un `astro build` suivi d'un déploiement du développeur. Un site relié à son
code ne passe plus par la publication directe : tout part par le build. Un **nouveau code** poussé sur `main` (dossier
du site modifié) s'affiche « Nouveau code à publier » et part au prochain « Publier ».

- Travailler dans le dépôt des sites, plus dans un dépôt propre au site : un déploiement fait ailleurs n'est pas connu
  de Studio (incident « Site en ligne différent de Studio ») et serait remplacé au « Publier » suivant.
- Ce que le serveur porte hors du dossier du site (configuration nginx, en-têtes, redirections du serveur) reste du
  ressort du script de déploiement du développeur.
- Builds réglés sur le serveur de Studio (`REPO_BUILD_RUNNER=docker`, réseau `wz-builds` à pare-feu sortant :
  `scripts/setup-build-network.sh`) ; un seul build à la fois, 2 processeurs et 3 Go de mémoire au plus.

### 4.4 Sauvegardes

Hub › **Sauvegardes** (super administrateur) : heure de la dernière sauvegarde, prochaine, état de chaque site,
historique.

- **Code** : le dépôt des sites lui-même (historique git). Les travaux pas encore poussés de chaque poste partent à 6 h
  et 18 h sur la branche `sauvegarde/poste-<nom>` (`outils/installer-sauvegarde-poste.sh`, une fois par poste).
- **Studio**, deux fois par jour (6 h et 18 h, heure de Paris) et à la demande : branche `sauvegarde/studio` du dépôt,
  un dossier par site — `studio/` (modifications, articles, menus, contenus, réglages, fiches des médias), `medias/`
  (fichiers envoyés dans Studio), `en-ligne/` (le site tel qu'il est servi, lu sur son serveur) ou, sans accès au
  serveur, `socle/` (ses pages d'origine). Un commit par passage.
- Jamais de données personnelles (demandes des formulaires) ni de secret dans les sauvegardes.
- Échec ou sauvegarde incomplète : incident « Sauvegarde des sites en échec » dans Hub › À traiter.

---

## 5. Liste de contrôle « prêt pour Studio »

- [ ] `npx --no-install weapzy-check dist --site https://www.exemple.fr` sans erreur (il contrôle la plupart des points
      ci-dessous) ; `--explain` montre ce que Studio lira, `--compare <ancien build>` que le rendu n'a pas changé
- [ ] `site` renseigné dans `astro.config.mjs` (ou `WEAPZY_SITE_URL` hors Astro), `weapzy()` en dernière intégration
- [ ] `<html lang>`, `<title>`, `meta description`, **canonique absolue** sur chaque page
- [ ] `og:title`, `og:description`, `og:image`, `og:site_name`, favicon
- [ ] `<header>`, `<nav aria-label>`, `<main>`, `<footer>` ; un seul `<h1>` par page
- [ ] Menus reconnus (`weapzy-check --explain`) ; s'ils sont nommés par `data-weapzy-menu`, chaque liste de liens l'est,
      d'un nom différent ; une entrée = un `<li>` ; entrées en icône avec texte accessible
- [ ] Articles avec données structurées `BlogPosting` (`headline`, `datePublished`, `image`) ; une page de rubrique par
      dossier d'articles ; modèle d'article et liste marqués (`data-weapzy-article`, `data-weapzy-articles`)
- [ ] Images avec `alt`, `width`, `height` ; pas de contenu important en image de fond ou injecté par JavaScript
- [ ] Classes stables ; `data-weapzy-ignore` sur le bandeau de cookies et les widgets
- [ ] Formulaires vers `/api/public/forms/<identifiant du site>` avec le champ piège `_gotcha`
- [ ] `WEAPZY_API_URL` et `WEAPZY_CONNECT_KEY` dans l'environnement de déploiement seulement
- [ ] Serveur du site déclaré dans Studio et accès testé (publication directe)

---

## 6. Dépannage

| Symptôme | Cause et solution |
|---|---|
| Build arrêté : « Connexion à Studio refusée (403) » | Domaine déjà rattaché à un site hors du périmètre de la clé, ou clé sans le droit « Connexion des sites ». |
| Build arrêté : « … (409) » | Le domaine appartient à un site construit dans Studio (non connecté). |
| « Adresse du site manquante » | Renseigner `site` dans la config Astro ou `WEAPZY_SITE_URL`. |
| « N modification(s) sans texte correspondant » | Le texte, la balise ou les classes d'origine ont changé dans le code : refaire la modification dans Studio. |
| Une page modifiée dans Studio n'est pas à jour en ligne | Cache du serveur ou du CDN (ex. `max-age=3600`) ; vérifier l'onglet Déploiement (dernière publication). |
| Menus mal découpés | Nommer les menus avec `data-weapzy-menu`. |
| « Site en ligne différent de Studio » (Hub › À traiter), publication bloquée | Le site a été déployé sans les variables de Studio, ou par un build que Studio n'a pas reçu. Redéployer par le script ou la CI qui charge `WEAPZY_API_URL`, `WEAPZY_API_KEY` et `WEAPZY_DEPLOY_KEY` : l'alerte se ferme et la publication reprend. |
| Pages envoyées par un build mais absentes de l'éditeur | Ce build n'est pas (encore) en ligne : Studio attend que le site serve son empreinte. Build local ou préproduction : normal, il est ignoré. |
| « Accès refusé par le serveur » au test | La ligne n'est pas dans `authorized_keys`, ou le chemin de `rrsync` est faux. |

---

## 7. Référence

- Variables : `WEAPZY_API_URL` (adresse de l'API de diffusion), `WEAPZY_CONNECT_KEY` (clé de connexion d'agence),
  `WEAPZY_SITE_URL` (adresse publique, hors Astro). Variante site par site : `WEAPZY_API_KEY` (lecture) +
  `WEAPZY_DEPLOY_KEY` (envoi des pages d'origine), créées dans l'onglet Connexion du site.
- Installation par un agent : `GET /install/<lien>` (instructions en Markdown), `POST /api/install/v1/claim`
  (`{ url, name? }` → site et clés, une fois), `POST /api/install/v1/report` (`{ step, status, message }`), avec
  `Authorization: Bearer wzi_…` ; documents publics `/install.md`, `/llms.txt`, `/docs/sites-connectes.md`, `/docs/api.md`.
- API : `POST /api/delivery/v1/connect` (clé `sites:connect`, corps `{ url, name?, files }` ; réponse : modifications et
  articles de Studio), `POST /base` (clé `base:write`), `GET /overrides` et `GET /articles` (clé `content:read`).
