### **4. Extension d'Édition**

L'extension est la brique qui porte la valeur commerciale : elle transforme un pointage à la souris en un sélecteur qui survivra aux refontes du site. Tout le reste du produit existe pour distribuer et mesurer ce qu'elle produit.

#### **4.1. Architecture WXT**

Trois points d'entrée, aux responsabilités strictement séparées :

| Point d'entrée | Rôle | Détient le jeton |
| --- | --- | --- |
| Service worker | Appels à l'API Circuy, authentification, stockage du jeton | Oui |
| Content script | Inspection du DOM, overlay d'édition, prévisualisation | Non |
| Popup | Connexion, choix du projet, activation de l'édition | Non |

**Le jeton d'authentification ne quitte jamais le service worker.** Le content script s'exécute dans un onglet dont la page peut être hostile ; il ne détient aucun secret et ne fait aucun appel réseau vers l'API. Il demande, le service worker exécute et répond. Cette séparation est la principale mesure de sécurité de l'extension.

**Injection à la demande.** Le content script n'est pas déclaré sur toutes les URL. Il est injecté programmatiquement lorsque l'utilisateur active l'édition depuis la popup, en s'appuyant sur la permission `activeTab`. Une extension présente en permanence sur toutes les pages de l'utilisateur imposerait un coût sur chaque navigation, élargirait la surface d'attaque et compliquerait la validation par le magasin d'extensions, pour un gain nul : l'édition est une action délibérée.

**Messagerie.** Les échanges entre points d'entrée passent par les messages d'exécution du navigateur, avec un type discriminant par message et une validation du contenu à la réception. Un message venant d'un content script est traité comme une entrée non fiable, y compris par notre propre service worker.

#### **4.2. Overlay d'édition**

L'interface d'édition est injectée dans une racine fantôme ouverte, dont l'hôte porte `all: initial` afin d'annuler tout héritage de styles de la page. Elle occupe un panneau fixé à droite, large de 380 pixels, avec un bouton de repli.

**Non-interférence avec le site hôte.** Le panneau est en position fixe et n'entre jamais dans le flux du document : la zone d'affichage du site n'est pas réduite et son comportement adaptatif n'est pas déclenché. En dehors du panneau, la page conserve son comportement normal.

**Mode inspection.** À l'activation du pointage, le panneau se réduit pour dégager la vue et le content script pose un écouteur de survol sur le document, qui trace un contour autour de l'élément visé. Le clic capture l'élément, restaure le panneau et interrompt l'écoute.

**Un seul mode de positionnement en V1.** Le cahier fonctionnel prévoit trois dispositions : panneau, dock inférieur et fenêtre modale. Seul le panneau est livré. Les deux autres triplent la surface de mise en page à écrire et à tester pour un besoin qu'aucun usage n'a encore établi, et leur ajout ultérieur ne remet en cause aucune structure.

#### **4.3. Inspecteur visuel et génération des sélecteurs**

C'est l'algorithme dont dépend la promesse de résilience. Au clic sur un élément, l'extension produit la liste ordonnée de candidats définie en section 2.2.

**Ordre de production, du plus stable au plus fragile :**

1. **`testid`** — Recherche d'un attribut de test (`data-testid`, `data-test`, `data-cy`, `data-qa`) sur l'élément, puis sur son plus proche ancêtre interactif. Ces attributs existent pour être ciblés : ce sont les plus fiables du document.
2. **`id`** — Retenu seulement si l'identifiant passe le contrôle de stabilité décrit plus bas.
3. **`aria`** — Rôle, explicite ou déduit de la balise, associé au nom accessible calculé selon les règles usuelles : `aria-label`, puis `aria-labelledby`, puis le contenu textuel, puis `alt` ou `title`.
4. **`text`** — Contenu textuel normalisé et tronqué, restreint à un type de balise donné.
5. **`css`** — Chemin structurel court, construit depuis le plus proche ancêtre porteur d'un identifiant stable et limité à quatre niveaux. Filet de sécurité, jamais premier choix.

**Contrôle de stabilité des identifiants.** Les frameworks modernes engendrent des identifiants renouvelés à chaque rendu ou à chaque compilation, qui donnent l'illusion d'un ciblage solide puis cassent à la première mise en production. Un identifiant est écarté s'il correspond à l'un de ces motifs : préfixe d'identifiant généré par React (`:r…`), numérotation de composant de bibliothèque (`ember`, `mui-`, `headlessui-`, `radix-` suivis de chiffres), identifiant universel unique, ou toute suite d'au moins six caractères manifestement aléatoires.

**Les classes CSS ne sont jamais utilisées comme critère.** Entre les classes utilitaires, qui décrivent l'apparence et non l'identité, et les classes hachées engendrées par les outils de style dans les composants, une classe n'apporte aucune information de ciblage durable. C'est pourquoi elles n'apparaissent pas parmi les types de candidats de la section 2.2.

**Vérification avant enregistrement.** Chaque candidat produit est immédiatement éprouvé sur la page courante et n'est conservé que s'il désigne exactement un élément, qui doit être celui que l'administrateur a désigné. Un candidat qui échoue à ce contrôle n'est jamais enregistré : la liste stockée ne contient que des repères vérifiés au moment de la capture.

**Retour de robustesse.** L'extension affiche le nombre et la nature des repères retenus. Un élément portant un attribut de test et un nom accessible est signalé comme solide ; un élément dont seul le chemin structurel a pu être retenu est signalé comme fragile, avec la recommandation explicite d'ajouter un attribut de test dans le code du site. Ce retour coûte peu et détourne l'administrateur de la construction de parcours condamnés d'avance.

#### **4.4. Édition des étapes**

Le formulaire couvre exactement les champs du contrat (section 2.1) : titre, corps, placement souhaité, mise en évidence et condition de passage. Le type d'étape détermine les champs affichés, une étape centrée n'ayant pas de cible ni de placement.

**Contenu riche.** Le corps accepte un sous-ensemble HTML volontairement étroit : emphase, gras, liens, listes et sauts de ligne. L'assainissement est appliqué à la saisie dans l'extension et de nouveau à la publication côté serveur (section 8.5). Ce contenu finira injecté chez des utilisateurs finaux : il n'est jamais considéré comme sûr, même écrit par un administrateur authentifié.

**Branchements.** Pour une étape de type question, chaque option choisit son étape suivante dans une liste des étapes existantes. L'extension refuse d'enregistrer une option pointant vers une étape inexistante et signale les étapes devenues inatteignables, sans les supprimer.

#### **4.5. Prévisualisation fidèle**

La prévisualisation instancie le moteur de rendu de `packages/sdk`, le même que celui qui s'exécutera en production (ADR-02), avec le brouillon en cours d'édition. La télémétrie et la persistance sont désactivées dans ce mode.

Cette fidélité par construction est le bénéfice architectural du choix d'un moteur possédé : il n'existe pas de second chemin de rendu à maintenir en cohérence avec le premier, donc pas de classe de bogue où la prévisualisation diverge de la production.

**Écarts résiduels, connus et assumés :** la prévisualisation s'exécute sur le brouillon et non sur une version publiée, et le ciblage par audience n'y est pas appliqué puisque l'administrateur veut justement voir toutes les étapes. Ces deux écarts sont indiqués dans l'interface.

#### **4.6. Publication**

1. **Validation locale** du brouillon contre le schéma partagé de `contracts`. À la différence du SDK, l'extension utilise bien le validateur et non seulement les types (section 1.3) : son poids n'a aucune importance, et les erreurs doivent être signalées à l'administrateur avant l'envoi, avec le champ fautif.
2. **Envoi authentifié** par le service worker vers l'API.
3. **Réponse** contenant le numéro de la version créée, affiché en confirmation.

**Conflits d'édition.** Un brouillon envoyé transporte la date de dernière modification connue de l'extension. Si elle ne correspond plus à celle enregistrée, le serveur refuse l'écriture et signale le conflit ; l'extension propose alors de recharger le brouillon distant ou d'écraser délibérément. Il n'y a pas de fusion automatique : deux administrateurs qui éditent le même parcours simultanément est un cas rare, dont la résolution silencieuse ferait plus de dégâts qu'un refus explicite.
