> ## Documentation Index
> Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Dessiner dans l'interface avec un mod

> Dessinez des volets, une bande au-dessus de l'invite, des boutons et des champs de texte à partir d'un mod Claude Code, gérez les appuis et les entrées, et conservez l'état entre les redessinages et les sessions.

Un mod peut dessiner sa propre interface dans Claude Code et modifier des parties de l'interface que Claude Code dessine déjà. Chaque endroit où un mod peut dessiner s'appelle un [site de rendu](/docs/fr/plugins/mods/reference#render-sites), comme un volet, la bande au-dessus de l'invite, ou le spinner. Claude Code déclenche l'événement [`ui.render`](/docs/fr/plugins/mods/reference#interface) chaque fois qu'il s'apprête à dessiner un site de rendu, et votre hook pour cet événement retourne ce qu'il faut dessiner là.

Cette carte montre où un mod peut dessiner dans une session de terminal :

<img src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-screen-map.svg?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=5fda26b6609c62b68c6f9e528c1590ea" className="dark:hidden" alt="Carte d'une session de terminal Claude Code. Un mod peut ajouter un volet comme barre latérale à droite, un toast en haut à droite de la transcription, une ligne de journal dans la transcription, une bande au-dessus de l'invite, et une ligne d'état sous l'invite. Un mod peut redessiner les messages, les lignes d'appels d'outils, et le spinner. L'invite est celle de Claude Code." width="600" height="336" data-path="images/mods-screen-map.svg" />

<img src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-screen-map-dark.svg?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=5b4161581a1bd2c0450b0c8b57bc1225" className="hidden dark:block" alt="Carte d'une session de terminal Claude Code. Un mod peut ajouter un volet comme barre latérale à droite, un toast en haut à droite de la transcription, une ligne de journal dans la transcription, une bande au-dessus de l'invite, et une ligne d'état sous l'invite. Un mod peut redessiner les messages, les lignes d'appels d'outils, et le spinner. L'invite est celle de Claude Code." width="600" height="336" data-path="images/mods-screen-map-dark.svg" />

Dans un terminal plus étroit, le volet se trouve au-dessus de l'invite au lieu de côté de la transcription.

Construisez votre [premier mod](/docs/fr/plugins/mods/create) avant de commencer ici. Commencez par l'exemple travaillé, qui construit un volet avec deux onglets et un compteur, puis lisez la section pour chaque élément que vous voulez modifier.

<Note>
  Pour rechercher une prop ou une limite, consultez la [référence](/docs/fr/plugins/mods/reference#render-sites).
</Note>

<h2 id="build-a-pane-with-tabs">
  Construire un volet avec des onglets
</h2>

Dans cette section, vous construisez un mod qui ajoute une commande `/hello-tabs`, et la commande ouvre un volet. Un volet est une barre latérale à côté de la transcription dans un terminal plein écran large, ou une région encadrée au-dessus de l'invite sinon. Ce volet affiche deux onglets, et le deuxième onglet a un bouton qui ajoute un au compteur. Le compte est toujours là après que vous redémarriez Claude Code.

Le mod fini ressemble à ceci. L'enregistrement ouvre le volet, bascule vers le deuxième onglet, appuie sur le bouton quelques fois, et revient au premier onglet :

<Frame>
  <video autoPlay muted loop playsInline controls className="w-full dark:hidden" src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-hello-tabs-light.mp4?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=49d520094d87b5b44bfe50fa49677f06" aria-label="La commande /hello-tabs est tapée à l'invite Claude Code et un volet encadré s'ouvre au-dessus, avec « 1 : One » et « 2 : Two » en haut et le texte « This is the first tab. » Le deuxième onglet affiche un bouton « Add one » à côté de « Count: 1 », et le compte monte à 3. Le volet revient ensuite au premier onglet." data-path="images/mods-hello-tabs-light.mp4" />

  <video autoPlay muted loop playsInline controls className="w-full hidden dark:block" src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-hello-tabs-dark.mp4?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=ff7a14d713d6e5d3b0000efa8522ea4b" aria-label="La commande /hello-tabs est tapée à l'invite Claude Code et un volet encadré s'ouvre au-dessus, avec « 1 : One » et « 2 : Two » en haut et le texte « This is the first tab. » Le deuxième onglet affiche un bouton « Add one » à côté de « Count: 1 », et le compte monte à 3. Le volet revient ensuite au premier onglet." data-path="images/mods-hello-tabs-dark.mp4" />
</Frame>

Claude Code n'a pas d'élément d'onglets intégré, donc les onglets sont deux boutons dans une ligne. Le mod garde la trace de celui qui est actif et dessine le contenu de cet onglet sous la ligne.

<Steps>
  <Step title="Créer le plugin">
    Un mod est un plugin avec un manifeste, un `hooks.json` qui pointe vers votre code, et le fichier de code. [Créer un mod](/docs/fr/plugins/mods/create#write-a-mod-yourself) explique chacun. Créez un répertoire nommé `hello-tabs` avec des répertoires `.claude-plugin` et `hooks` à l'intérieur, puis enregistrez les deux premiers fichiers.

    Enregistrez le manifeste sous `hello-tabs/.claude-plugin/plugin.json` :

    ```json hello-tabs/.claude-plugin/plugin.json theme={null}
    {
      "name": "hello-tabs",
      "version": "0.1.0",
      "description": "Opens a pane with two tabs and a counter",
      "author": { "name": "Your Name" }
    }
    ```

    Nommez votre point d'entrée dans `hello-tabs/hooks/hooks.json` :

    ```json hello-tabs/hooks/hooks.json theme={null}
    {
      "modules": ["./register.js"]
    }
    ```
  </Step>

  <Step title="Écrire le code">
    Le code fait trois choses, une dans chaque hook :

    * Ajoute la commande `/hello-tabs`
    * Ouvre le volet quand vous exécutez cette commande
    * Dessine le contenu du volet : la ligne d'onglets et le corps de l'onglet ouvert

    Deux variables au niveau du module, `tab` et `count`, conservent l'état du volet.

    Enregistrez ceci sous `hello-tabs/hooks/register.js` :

    ```javascript hello-tabs/hooks/register.js theme={null}
    // The pane's id, used to open the pane and to recognize it when drawing
    const PANE = 'hello-tabs'

    // What the pane shows: which tab is open, and the counter's value
    let tab = 'one'
    let count = 0

    export function register(on) {
      // Runs before your first prompt, and again after a reload
      on('session.start', async ($, e, next) => {
        await $.command.register({ name: 'hello-tabs', description: 'Open the hello-tabs pane' })
        // Load the count an earlier session saved, if there is one
        const saved = await $.store.get('count')
        if (typeof saved === 'number') count = saved
        return next(e)
      })

      // Runs when you type /hello-tabs
      on('command.run', { command: 'hello-tabs' }, async ($) => {
        // Open the pane, give it the keyboard, and let Esc close it
        await $.ui.open({ id: PANE, title: 'Hello tabs', focus: true, closeOnEscape: true })
        // Print nothing in the transcript
        return {}
      })

      // Runs each time Claude Code draws a pane
      on('ui.render', { component: 'Pane' }, async ($, e, next) => {
        // Leave other mods' panes alone
        if (e.requestId !== PANE) return next(e)
        // Get the elements this app can draw
        const { Box, Text, Button } = $.ui.resolve(e)
        // Ask Claude Code to run this hook again
        const redraw = () => $.ui.invalidate('ui.render')

        // One tab: a button that switches to its tab when pressed
        const tabButton = (name, label, hotkey) =>
          Button({
            key: 'tab-' + name,
            label,
            hotkey,
            plain: true,
            // Dim the tab that isn't open
            dimColor: tab !== name,
            onPress: () => {
              tab = name
              redraw()
            },
          })

        // What goes under the tabs, depending on which one is open
        const body =
          tab === 'one'
            ? [Text({ children: ['This is the first tab.'] })]
            : [
                Box({
                  flexDirection: 'row',
                  columnGap: 2,
                  children: [
                    Button({
                      key: 'more',
                      label: 'Add one',
                      hotkey: 'a',
                      onPress: async () => {
                        count += 1
                        redraw()
                        // Save the count so it's there after a restart
                        await $.store.set('count', count)
                      },
                    }),
                    Text({ children: ['Count: ' + count] }),
                  ],
                }),
              ]

        // The whole pane: the row of tabs, a blank line, then the body
        return Box({
          flexDirection: 'column',
          children: [
            Box({
              flexDirection: 'row',
              columnGap: 3,
              children: [tabButton('one', 'One', '1'), tabButton('two', 'Two', '2')],
            }),
            Text({ children: [' '] }),
            ...body,
          ],
        })
      })
    }
    ```

    Chaque hook fait aussi quelque chose que le code ne rend pas évident :

    * **[`session.start`](/docs/fr/plugins/mods/reference#session)** lit aussi le compte sauvegardé depuis [`$.store`](#keep-state), un magasin clé-valeur qui persiste entre les sessions.
    * **[`command.run`](/docs/fr/plugins/mods/api#add-a-command)** dit seulement à Claude Code que le volet existe. Ouvrir un volet ne dessine rien par lui-même : Claude Code déclenche ensuite `ui.render` pour demander ce qu'il faut y mettre.
    * **`ui.render`** retourne l'arbre d'éléments, une `Box` qui contient d'autres boîtes, du texte et des boutons, et le construit à nouveau à partir de `tab` et `count` chaque fois qu'il s'exécute.

    Appuyer sur un bouton exécute son callback `onPress`, qui change une variable et appelle `redraw`. Claude Code exécute ensuite le hook `ui.render` à nouveau, et le hook construit un nouvel arbre à partir des nouvelles valeurs. Chaque dessin interactif utilise ce cycle de rendu : un callback change l'état, et le hook dessine à nouveau à partir du nouvel état.
  </Step>

  <Step title="Ouvrir le volet">
    Dans votre shell, démarrez Claude Code avec `claude --plugin-dir ./hello-tabs`. À l'invite Claude Code, exécutez `/hello-tabs`. Un volet s'ouvre avec `1: One` et `2: Two` en haut. Appuyez sur `2`, puis appuyez sur `a`, la touche de raccourci pour **Add one**, quelques fois. Le compte augmente.
  </Step>

  <Step title="Vérifier que le compte a été sauvegardé">
    Appuyez sur Esc pour fermer le volet, puis quittez la session. Dans votre shell, démarrez Claude Code à nouveau avec la même commande `claude --plugin-dir ./hello-tabs`, et à l'invite Claude Code exécutez `/hello-tabs`. Le compte est où vous l'avez laissé.

    Pour effacer le compte, faites appeler au mod `$.store.delete('count')`. [Conserver l'état](#keep-state) couvre combien de temps chaque type de valeur dure.
  </Step>
</Steps>

<h2 id="pick-where-to-draw">
  Choisir où dessiner
</h2>

Un hook `ui.render` s'exécute pour chaque site de rendu sauf si vous le réduisez à celui que vous voulez dessiner. Pour choisir le site de rendu, passez un filtre, appelé un [matcher](/docs/fr/plugins/mods/events#filter-which-events-a-hook-handles), comme deuxième argument à `on`. `{ component: 'Pane' }` exécute le hook seulement pour les volets. Dans le hook, `e.component` nomme le site, `e.surface` dit quelle application dessine, et `e.props` contient les données propres du site. Pour un volet, `e.requestId` est l'`id` avec lequel vous l'avez ouvert.

Deux sites sont vides jusqu'à ce qu'un mod les remplisse, le volet et la bande. Sélectionnez un onglet pour voir ce que chacun est et comment dessiner dedans :

<Tabs>
  <Tab title="Volet">
    Un volet est une barre latérale à côté de la transcription dans un terminal plein écran large, ou une région encadrée au-dessus de l'invite sinon. Avec plusieurs volets ouverts, chacun obtient un onglet qui affiche son titre.

    Un volet apparaît quand votre mod appelle `$.ui.open` avec un `id` que vous choisissez, comme dans `$.ui.open({ id: 'hello-tabs' })`. [Ouvrir un volet au bon moment](#open-a-pane-at-the-right-time) couvre les autres champs et quand un volet attend un terminal plus large.

    Pour dessiner dans votre volet, filtrez sur `{ component: 'Pane' }` et vérifiez que `e.requestId` est votre `id`.
  </Tab>

  <Tab title="Bande au-dessus de l'invite">
    La bande est une bande directement au-dessus de l'entrée d'invite. Elle est toujours là, et chaque mod la partage.

    Votre hook retourne un arbre pour afficher quelque chose dans la bande, ou `next(e)` pour ne rien afficher. Un arbre remplace ce que les mods [après le vôtre](/docs/fr/plugins/mods/events#the-order-mods-run-in) dessinent là. Pour garder le leur, mettez le résultat de `await next(e)` parmi les enfants d'une [`Box`](#build-a-tree-from-elements) dans votre arbre.

    Pour dessiner dans la bande, filtrez sur `{ component: 'AbovePrompt' }`.
  </Tab>
</Tabs>

<h3 id="change-what-claude-code-already-draws">
  Modifier ce que Claude Code dessine déjà
</h3>

Claude Code dessine la plupart de son interface lui-même : les messages, les lignes d'appels d'outils, le spinner, et plus. Chacune de ces parties est aussi un site de rendu, donc un mod peut le restyler ou le remplacer. Pour en modifier un, filtrez votre hook `ui.render` sur son nom de ce tableau :

| Site | Ce que c'est |
| :- | :- |
| `UserMessage`, `AssistantMessage` | Un message dans la transcription |
| `ToolUse`, `ToolResult`, `ToolGroup` | La ligne d'un appel d'outil, son résultat, et une exécution repliée d'appels |
| `CommandOutput` | La ligne qu'une commande a imprimée |
| `AskUserQuestion` | Le dialogue que Claude ouvre pour vous poser une question |
| `Spinner`, `ToolProgress`, `TurnDuration` | Lignes d'état pour un tour : la ligne qui s'anime pendant que Claude travaille, la ligne de progression en direct d'un outil en cours d'exécution, et la ligne qui ferme un tour |
| `InfoNotice`, `SessionMode`, `PromptHint` | Lignes d'état sous le logo, les étiquettes de mode dans le pied de page, et la ligne d'indice sous l'invite |

À un site que Claude Code dessine déjà, votre hook a trois choix : modifier un détail, remplacer le dessin, ou le laisser tranquille. Sélectionnez un onglet pour voir chacun appliqué au spinner. Les exemples lisent une variable `calls` qu'un autre hook compte, comme dans le [mod tutoriel](/docs/fr/plugins/mods/create#write-a-mod-yourself).

<Tabs>
  <Tab title="Modifier un détail">
    Pour garder le dessin de Claude Code et modifier une partie de celui-ci, passez à `next` une copie de l'événement avec des `props` modifiées. Ce hook change le texte après le mot du spinner :

    ```javascript theme={null}
    on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
      // Keep Claude Code's spinner, and change the text after its word
      return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
    })
    ```

    Le spinner garde son animation et son mot, et votre texte suit le mot :

    ```text theme={null}
    Thinking · tool calls: 2…
    ```
  </Tab>

  <Tab title="Remplacer le dessin">
    Pour dessiner quelque chose de votre propre à la place du site, retournez un arbre et n'appelez pas `next`. Ce hook dessine une ligne de texte où le spinner serait :

    ```javascript theme={null}
    on('ui.render', { component: 'Spinner' }, async ($, e) => {
      const { Text } = $.ui.resolve(e)
      // No call to next, so this line is drawn in the spinner's place
      return Text({ children: ['Claude has made ' + calls + ' tool calls'] })
    })
    ```

    Pendant que Claude travaille, votre ligne s'affiche et le spinner de Claude Code ne s'affiche pas :

    ```text theme={null}
    Claude has made 2 tool calls
    ```
  </Tab>

  <Tab title="Le laisser tranquille">
    Pour laisser le site tel que Claude Code le dessine, retournez `next(e)`. Un hook fait souvent cela pour certains événements et pas pour d'autres. Ce hook laisse le spinner tranquille jusqu'à ce qu'il y ait un appel à compter :

    ```javascript theme={null}
    on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
      // Nothing to show yet, so pass the event on unchanged
      if (calls === 0) return next(e)
      return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
    })
    ```

    Avant le premier appel d'outil, le spinner ressemble à la façon dont il le fait sans le mod :

    ```text theme={null}
    Thinking…
    ```
  </Tab>
</Tabs>

L'invite de permission n'est pas un site de rendu, donc un mod ne peut pas modifier ce qu'il affiche. Le dialogue de question, `AskUserQuestion`, en est un, donc un mod peut modifier cela.

Le terminal et l'application Desktop ne déclenchent pas tous les mêmes sites. `Pane`, `AbovePrompt`, `Spinner`, et les sites de transcription fonctionnent dans les deux. Quelques autres lignes d'état sont déclenchées seulement dans le terminal. Le [tableau des sites de rendu](/docs/fr/plugins/mods/reference#render-sites) liste où chacun est déclenché.

<h3 id="open-a-pane-at-the-right-time">
  Ouvrir un volet au bon moment
</h3>

Un volet n'apparaît que quand votre mod l'ouvre. Comment et quand vous l'ouvrez décide s'il prend le focus clavier, combien d'espace il demande, et s'il s'affiche du tout dans un terminal étroit.

Pour ouvrir un volet, appelez [`$.ui.open`](/docs/fr/plugins/mods/reference#mods-api-methods) avec un `id` que vous choisissez. L'`id` est le nom du volet : votre hook `ui.render` le vérifie, et vous le passez à nouveau pour fermer le volet.

```javascript theme={null}
await $.ui.open({ id: 'hello-tabs', title: 'Hello tabs', focus: true })
```

Pour fermer le volet, appelez `$.ui.close` avec l'`id` avec lequel vous l'avez ouvert :

```javascript theme={null}
await $.ui.close({ id: 'hello-tabs' })
```

En plus de `id`, `$.ui.open` prend ces champs optionnels :

| Champ | Ce qu'il fait |
| :- | :- |
| `title` | L'étiquette d'onglet du volet quand plus d'un volet est ouvert |
| `focus` | Demande le [focus clavier](#know-which-keys-your-mod-can-receive) |
| `closeOnEscape` | Fait que Esc ferme le volet. Passez `true` ou laissez le champ de côté, car Claude Code refuse `false`. |
| `holdToasts` | Retient les toasts, les petits avis de [`$.ui.toast`](/docs/fr/plugins/mods/api#show-something-without-starting-a-turn), jusqu'à ce que le volet se ferme |
| `rows` | La hauteur à demander quand le volet se trouve au-dessus de l'invite. La valeur par défaut est un tiers de l'espace. |
| `columns` | La largeur à demander quand le volet se trouve à côté de la transcription |

Pour laisser une commande ouvrir le volet pendant que Claude travaille, ajoutez `immediate: true` quand vous [enregistrez la commande](/docs/fr/plugins/mods/api#add-a-command). Sans cela, une commande tapée pendant un tour attend la fin du tour.

<h4 id="when-a-pane-waits-for-a-wider-terminal">
  Quand un volet attend un terminal plus large
</h4>

Un volet que votre mod ouvre sans être demandé n'apparaît pas dans un terminal étroit, donc il ne peut pas prendre le contrôle d'un petit écran. S'il apparaît dépend de ce qui l'a ouvert :

* **Ouvert par quelque chose que l'utilisateur a fait**, comme une commande qu'il a exécutée ou un bouton qu'il a appuyé, le volet apparaît à n'importe quelle largeur
* **Ouvert par votre mod agissant par lui-même**, comme à partir d'une minuterie ou d'un hook [`turn.start`](/docs/fr/plugins/mods/events#follow-a-turn), le volet n'apparaît que dans un terminal d'au moins 144 colonnes de large. Après que l'utilisateur ait ouvert ce volet une fois lui-même, 110 colonnes suffisent.

Quand le volet apparaît, `$.ui.open` se résout en `{ isPlaced: true }`. Quand le volet attend, `isPlaced` est `false` et `reason` est une chaîne qui dit pourquoi. Un volet en attente apparaît quand l'utilisateur l'ouvre ou élargit le terminal. Pour dire que quelque chose est disponible sans ouvrir un volet, appelez `$.ui.toast('Your message')`, qui affiche un petit avis qui disparaît après quelques secondes.

<h2 id="build-a-tree-from-elements">
  Construire un arbre à partir d'éléments
</h2>

Ce qu'un hook `ui.render` retourne est un arbre d'éléments : une description de ce qu'il faut dessiner, faite de boîtes, de texte et de contrôles imbriqués les uns dans les autres. Vous décrivez le dessin, et Claude Code le rend dans le terminal ou l'application Desktop.

Pour obtenir les éléments, appelez `$.ui.resolve(e)` dans votre hook, comme dans `const { Box, Text, Button } = $.ui.resolve(e)`. Chaque élément est une fonction. Vous lui passez des props, et vous mettez les éléments et les chaînes qui vont à l'intérieur dans `children`.

La plupart des dessins utilisent quatre éléments. Sélectionnez un onglet pour voir chacun et comment le terminal le dessine :

<Tabs>
  <Tab title="Texte">
    `Text` dessine une chaîne, avec un style optionnel comme `bold` et `color` :

    ```javascript theme={null}
    Text({ children: ['This is the first tab.'] })
    ```

    ```text theme={null}
    This is the first tab.
    ```
  </Tab>

  <Tab title="Boîte">
    `Box` arrange ce qui est à l'intérieur, dans une ligne ou une colonne. Celle-ci met un bouton et une ligne de texte côte à côte, deux colonnes à part :

    ```javascript theme={null}
    Box({
      flexDirection: 'row',
      columnGap: 2,
      children: [
        Button({ key: 'more', label: 'Add one', onPress: addOne }),
        Text({ children: ['Count: 0'] }),
      ],
    })
    ```

    ```text theme={null}
    [ Add one ]  Count: 0
    ```
  </Tab>

  <Tab title="Bouton">
    `Button` est un contrôle que l'utilisateur peut appuyer. Il exécute votre callback `onPress`. Avec `plain: true` il n'a pas de crochets et affiche sa touche de raccourci :

    ```javascript theme={null}
    Button({ key: 'more', label: 'Add one', onPress: addOne })
    Button({ key: 'tab-one', label: 'One', hotkey: '1', plain: true, onPress: showTabOne })
    ```

    ```text theme={null}
    [ Add one ]
    1: One
    ```
  </Tab>

  <Tab title="Entrée">
    `Input` est un champ de texte. Il exécute votre callback `onSubmit` avec le texte quand l'utilisateur appuie sur Entrée :

    ```javascript theme={null}
    Input({
      key: 'new-note',
      label: 'Note',
      placeholder: 'Type a note and press Enter',
      value: '',
      submitLabel: 'add',
      onSubmit: addNote,
    })
    ```

    ```text theme={null}
    Note: Type a note and press Enter ⏎ add
    ```
  </Tab>
</Tabs>

Ce tableau liste chaque élément :

| Élément | Ce qu'il dessine | Où |
| :- | :- | :- |
| `Box` | Un conteneur flex. Prend des props de mise en page comme `flexDirection`, `columnGap`, `padding`, `borderStyle`, et `width`. | Partout |
| `Text` | Texte stylisé. Prend `color`, `bold`, `dimColor`, `italic`, et `wrap`. Une `color` est une clé de thème ou une couleur comme `'red'`. Un `wrap` est `'wrap'`, `'truncate'`, `'truncate-start'`, `'truncate-middle'`, ou `'truncate-end'`. | Partout |
| `Button` | Un contrôle qui appelle `onPress` | Partout |
| `Link`, `Code`, `Markdown` | Un lien avec `href` et une `label` optionnelle, un bloc de code, et du texte formaté comme les réponses de Claude. `Markdown` prend son contenu dans une prop `text`, pas dans `children`, et a besoin d'une `key` quand vous passez `onLinkPress`. | Partout |
| `Input`, `Select` | Un champ de texte et un sélecteur | Terminal, Desktop |
| `Svg` | Un document SVG | Desktop |
| `Client` | Une région dessinée par un deuxième fichier du vôtre, pour l'animation et l'entrée au pointeur. Ce fichier n'obtient pas l'API des mods. Il atteint vos hooks seulement en postant des données, qui arrivent comme un événement `ui.message`. | Terminal, Desktop |
| `Raster`, `Image` | Une [grille de cellules colorées](#draw-a-grid-of-colored-cells), et une image | Terminal |

Si votre module est un fichier `.tsx` ou `.jsx`, vous pouvez écrire l'arbre en JSX. Déstructurez les éléments de `$.ui.resolve(e)` d'abord, car un module de hooks n'a pas de globals d'éléments.

Si un arbre utilise un élément que l'application n'a pas, une prop qu'un élément ne prend pas, ou un enfant où aucun ne va, Claude Code dessine sa propre version du site.

Dans une session démarrée avec `--plugin-dir`, une ligne de transcription le dit, comme `ui.render (Pane) refused: Text prop "bogusProp" is not allowed; the engine drew its own`. Le [journal de débogage](/docs/fr/plugins/mods/troubleshoot#read-the-debug-log) l'enregistre comme `ui.render (Pane): a hook returned a tree that does not validate` avec la même raison. Rien d'autre n'apparaît dans la session, donc quand un dessin ne s'affiche pas, vérifiez cette ligne ou le journal.

<h3 id="draw-a-grid-of-colored-cells">
  Dessiner une grille de cellules colorées
</h3>

Pour une carte thermique, une sparkline, ou un plateau de jeu dans le terminal, dessinez un `Raster` et non une `Box` pour chaque cellule. Un `Raster` prend une `key`, sa taille en `columns` et `rows`, et `cells`, qui empaquette chaque cellule dans une chaîne. Chaque cellule est trois nombres : le point de code du caractère, sa couleur et sa couleur de fond. Une couleur est un nombre hexadécimal avec deux chiffres chacun pour le rouge, le vert et le bleu, comme `0xc62828` pour un rouge, ou `0x01000000` pour la valeur par défaut du terminal.

L'application Desktop n'a pas de `Raster`, donc vérifiez `e.surface` et dessinez du texte là. Ce corps de volet dessine une carte thermique de trois par deux :

```javascript theme={null}
// The value that means "use the terminal's default color"
const DEFAULT_COLOR = 0x01000000

// Pack rows of [character, color] pairs into the one string a Raster takes
// One cell is three numbers: the character's code point, its color, and its background
function cellsOf(rows) {
  const numbers = rows.flat().flatMap(([char, color]) => [char.codePointAt(0), color, DEFAULT_COLOR])
  return new Uint8Array(Uint32Array.from(numbers).buffer).toBase64()
}

on('ui.render', { component: 'Pane' }, async ($, e, next) => {
  // Draw only in the pane opened with the id 'heat'
  if (e.requestId !== 'heat') return next(e)
  const { Box, Text, Raster } = $.ui.resolve(e)
  // Two rows of three cells, each a block character and its color
  const rows = [
    [['█', 0x2e7d32], ['█', 0xf9a825], ['█', 0xc62828]],
    [['█', 0x2e7d32], ['█', 0x2e7d32], ['█', 0xf9a825]],
  ]
  if (e.surface !== 'terminal') {
    return Text({ children: ['The heat map needs the terminal.'] })
  }
  return Box({
    flexDirection: 'column',
    children: [Raster({ key: 'grid', columns: 3, rows: 2, cells: cellsOf(rows) })],
  })
})
```

Dans le terminal, le volet affiche la grille :

<img src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-heat-map.svg?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=b91bcce3bad74bc851149133d4acc5d5" alt="Un volet dans le terminal qui contient une petite grille de blocs colorés, deux lignes de trois. La ligne du haut est verte, ambre et rouge. La ligne du bas est verte, verte et ambre." width="360" height="132" data-path="images/mods-heat-map.svg" />

Le tableau `rows` est la partie que vous changeriez, et `cellsOf` la transforme en chaîne empaquetée. Le hook dessine seulement dans un volet dont l'`id` est `heat`, donc ouvrez-en un avec `$.ui.open({ id: 'heat' })` à partir d'une commande, comme l'exemple [`hello-tabs`](#build-a-pane-with-tabs) ouvre son volet.

Chaque caractère doit être large d'une cellule. Pour animer un `Raster` qui est déjà à l'écran, appelez `$.ui.blit` avec l'`id` du volet comme `requestId`, la `key` du `Raster`, la même taille, et de nouvelles cellules. Pour cet exemple, c'est `$.ui.blit({ requestId: 'heat', key: 'grid', columns: 3, rows: 2, cells: cellsOf(newRows) })`. Il repeint cet élément sans exécuter votre hook `ui.render` à nouveau.

<h2 id="respond-to-presses-and-typing">
  Répondre aux appuis et à la saisie
</h2>

Quand l'utilisateur appuie sur un bouton, tape dans un champ, ou choisit dans une liste que votre mod a dessinée, Claude Code appelle la fonction que vous avez donnée à ce contrôle, et elle s'exécute dans votre module. Chaque contrôle prend ses propres callbacks :

* **`Button`** : prend `onPress(e)`, où `e.surface` est l'application d'où vient l'appui
* **`Input`** : prend `onSubmit(value)` et `onInput(value)`
* **`Select`** : prend `onSelect(value)` avec ses choix dans `options`, une liste d'au moins un choix avec des valeurs uniques, comme `[{ value: 'sm', label: 'Small' }, { value: 'lg', label: 'Large' }]`

Un test appuie ou tape dans un contrôle par sa `key`, donc donnez-en un à chaque contrôle. Chaque utilisation d'un contrôle déclenche aussi [`ui.press`, `ui.input`, ou `ui.select`](/docs/fr/plugins/mods/reference#interface) avec la `key` dans `e.element`, et un autre mod peut accrocher ces événements. Son hook s'exécute avant votre callback, donc il voit ce que l'utilisateur tape dans votre `Input` et peut le modifier ou répondre à la place de votre callback. L'API des mods n'a pas de méthode qui appuie sur le bouton d'un autre mod.

<h3 id="know-which-keys-your-mod-can-receive">
  Focus clavier et touches de raccourci
</h3>

Votre mod ne lit jamais le clavier lui-même. L'utilisateur appuie sur une touche, Claude Code décide lequel de vos contrôles c'est, et le callback de ce contrôle s'exécute. À part une [touche de raccourci numérique sur la bande](/docs/fr/plugins/mods/reference#elements), cela ne se produit que pendant que votre volet ou bande a le focus clavier. Le reste du temps, les touches vont à l'invite.

<h4 id="how-a-pane-gets-keyboard-focus">
  Comment un volet obtient le focus clavier
</h4>

Un volet obtient le focus clavier de l'une de trois façons :

* Votre mod l'ouvre avec `focus: true` à partir d'une commande ou d'un appui
* L'utilisateur appuie sur Ctrl+X puis Tab
* L'utilisateur clique dessus

Claude Code accorde `focus: true` seulement pendant que l'invite est vide et rien d'autre n'a le focus clavier. Un volet qui s'ouvre pendant que l'utilisateur tape ne prend pas ses frappes.

<h4 id="what-each-key-does">
  Ce que chaque touche fait
</h4>

Ce tableau liste ce qu'une touche fait pendant que votre volet ou bande a le focus clavier :

| Touche | Ce qu'elle fait |
| :- | :- |
| Tab | Se déplace vers le contrôle suivant |
| Haut et Bas | Se déplacent entre les contrôles pendant que votre dessin s'adapte. Quand le volet ou la bande a plus de lignes qu'il ne peut en afficher, ils le font défiler. |
| Entrée | Appuie sur le `Button` ciblé, soumet le `Input` ciblé, ou choisit dans un `Select` |
| La touche de raccourci d'un bouton | Appuie sur ce bouton. Pendant qu'un `Input` a le focus, chaque touche imprimable va au champ. |
| Esc | Retourne le focus clavier à l'invite. Avec `closeOnEscape: true`, il ferme aussi le volet. |

Un mod ne peut pas lier Tab ou les touches fléchées à autre chose, donc un jeu se dirige avec `w`, `a`, `s`, et `d`.

<h4 id="set-a-hotkey-and-the-first-focus">
  Définir une touche de raccourci et le premier focus
</h4>

Deux props sur un contrôle décident comment le clavier l'atteint :

* **`hotkey`** : pour laisser l'utilisateur appuyer sur un `Button` avec une touche, donnez-lui une `hotkey` d'un chiffre ou une lettre minuscule, comme dans `hotkey: 'a'`
* **`autoFocus`** : pour choisir quel contrôle a le focus quand le volet s'ouvre, ajoutez `autoFocus: true` à celui-ci. Laissez la prop de côté sur les autres, car Claude Code refuse `autoFocus: false`.

Comment une touche de raccourci s'affiche dépend du bouton et de l'application :

| Bouton | Dans le terminal | Dans l'application Desktop |
| :- | :- | :- |
| Avec crochets, la valeur par défaut | `[ Add one ]`, sans touche de raccourci affichée | L'étiquette avec une petite touche à côté |
| Avec `plain: true` | `1: One` | L'étiquette avec une petite touche à côté |

Dans le terminal, nommez la touche dans l'étiquette d'un bouton entre crochets, ou utilisez `plain: true`, pour que l'utilisateur puisse voir ce qu'il faut appuyer. La [référence des éléments](/docs/fr/plugins/mods/reference#elements) a les autres règles de `Button` : `action`, les touches de raccourci numériques sur la bande, et deux boutons sur une touche de raccourci.

<h3 id="take-typed-input-and-draw-a-row-for-each-item">
  Prendre l'entrée tapée et dessiner une ligne pour chaque élément
</h3>

De nombreux volets sont un champ de texte avec une liste en dessous. L'exemple de cette section est un volet de notes : vous tapez une note et appuyez sur Entrée pour l'ajouter, et chaque note a un bouton `x` qui la supprime. Avec deux notes ajoutées, le terminal dessine le volet de cette façon :

```text theme={null}
╭──────────────────────────────────────────────────────────╮
│ Note: Type a note and press Enter ⏎ add                ✕ │
│ x buy milk │
│ x call bob │
╰──────────────────────────────────────────────────────────╯
```

L'exemple utilise deux techniques :

* **Prendre l'entrée tapée** : un `Input` appelle `onSubmit(value)` avec le texte du champ quand l'utilisateur appuie sur Entrée, et `onInput(value)` à chaque changement
* **Dessiner une liste** : mappez vos données à une ligne chacune, et donnez à chaque bouton de ligne sa propre `key`

Ce hook dessine le contenu du volet :

```javascript theme={null}
// The list the pane draws
let notes = []

on('ui.render', { component: 'Pane' }, async ($, e, next) => {
  // Draw only in the pane opened with the id 'notes'
  if (e.requestId !== 'notes') return next(e)
  const { Box, Text, Button, Input } = $.ui.resolve(e)
  const redraw = () => $.ui.invalidate('ui.render')

  return Box({
    flexDirection: 'column',
    children: [
      Input({
        key: 'new-note',
        label: 'Note',
        placeholder: 'Type a note and press Enter',
        // Draw the field empty each time, which clears it after a submit
        value: '',
        submitLabel: 'add',
        autoFocus: true,
        // Runs when you press Enter in the field
        onSubmit: async (value) => {
          // Ignore an empty line
          if (!value.trim()) return
          notes = [...notes, value.trim()]
          redraw()
          await $.store.set('notes', notes)
        },
      }),
      // One row for each note: a delete button, then the note's text
      ...notes.map((note, i) =>
        Box({
          flexDirection: 'row',
          columnGap: 1,
          children: [
            Button({
              // A key of its own, so each row's button can be told apart
              key: 'delete-' + i,
              label: 'x',
              plain: true,
              onPress: async () => {
                notes = notes.filter((_, j) => j !== i)
                redraw()
                await $.store.set('notes', notes)
              },
            }),
            Text({ children: [note] }),
          ],
        }),
      ),
    ],
  })
})
```

Pour essayer le volet :

* **Ajouter une note** : tapez une ligne et appuyez sur Entrée. La ligne apparaît comme une nouvelle ligne, et le champ se vide.
* **Supprimer une note** : appuyez sur Tab jusqu'à ce que le bouton `x` de la note ait le focus, puis appuyez sur Entrée. Le `x` est l'étiquette du bouton et non une touche de raccourci, donc taper la lettre ne l'appuie pas.

Chaque changement suit le même cycle de rendu que `hello-tabs` : le callback change `notes`, appelle `redraw`, et enregistre la liste dans `$.store`.

Le champ se vide après chaque soumission à cause de sa prop `value`. `value` est le texte que le champ contient quand il est dessiné, et la saisie de l'utilisateur le remplace jusqu'à ce que votre hook dessine le champ à nouveau. L'exemple dessine toujours le champ avec `''`.

L'exemple enregistre les notes et ne les charge pas. Pour les ramener dans la session suivante, lisez-les dans un hook `session.start`, de la même façon que `hello-tabs` lit `count`.

Trois props composent la ligne du champ, `Note: Type a note and press Enter ⏎ add` :

| Prop | Dans l'exemple | Ce que c'est |
| :- | :- | :- |
| `label` | `Note` | Le texte avant le champ. Le terminal dessine `: ` après. |
| `placeholder` | `Type a note and press Enter` | Texte atténué qui s'affiche pendant que le champ est vide |
| `submitLabel` | `add` | Le mot après `⏎` qui dit ce que fait Entrée |

Soumettre un `Input` ne démarre pas un tour sauf si votre callback appelle [`$.prompt.submit`](/docs/fr/plugins/mods/api#start-a-turn-from-a-background-job).

<h2 id="redraw-when-something-changes">
  Redessiner un site
</h2>

Un dessin est un instantané : il affiche ce que votre hook `ui.render` a retourné la dernière fois que le hook s'est exécuté. Pour afficher quelque chose de nouveau, le hook doit s'exécuter à nouveau. Claude Code l'exécute à nouveau pour certains changements, et votre mod demande le reste.

<h3 id="when-claude-code-redraws-without-being-asked">
  Quand Claude Code redessine sans être demandé
</h3>

Claude Code exécute votre hook `ui.render` à nouveau quand les props du site changent ou la largeur du terminal change. Il n'exécute pas le hook sur une minuterie, et il ne peut pas dire quand une variable dans votre module change.

<h3 id="redraw-when-your-data-changes">
  Redessiner quand vos données changent
</h3>

Pour avoir vos sites dessinés à nouveau après que vos propres données changent, appelez `$.ui.invalidate('ui.render')`. Ce volet compte les appuis. Le callback du bouton change `count`, puis demande un redessin :

```javascript theme={null}
let count = 0

on('ui.render', { component: 'Pane' }, async ($, e, next) => {
  if (e.requestId !== 'counter') return next(e)
  const { Box, Text, Button } = $.ui.resolve(e)
  return Box({
    flexDirection: 'row',
    columnGap: 2,
    children: [
      Button({
        key: 'more',
        label: 'Add one',
        onPress: () => {
          count += 1
          // The data changed, so ask Claude Code to draw the pane again
          $.ui.invalidate('ui.render')
        },
      }),
      Text({ children: ['Count: ' + count] }),
    ],
  })
})
```

Chaque appui augmente le nombre dans le volet. L'exemple [`hello-tabs`](#build-a-pane-with-tabs) enveloppe le même appel dans sa fonction `redraw`.

Une valeur que vous gardez dans [`$.state`](#keep-a-value-in-\$-state) n'a pas besoin de l'appel, car écrire la valeur redessine les sites qui la lisent.

<h3 id="redraw-on-a-timer">
  Redessiner sur une minuterie
</h3>

Pour garder une horloge, un compte à rebours, ou une valeur de l'extérieur de la session actuelle, redessinez selon un calendrier. Démarrez une minuterie dans le hook `session.start` du module. Si le module en a déjà une, comme `hello-tabs` le fait, ajoutez la ligne [`$.clock.every`](/docs/fr/plugins/mods/api#run-work-in-the-background) à celle-ci :

```javascript theme={null}
on('session.start', async ($, e, next) => {
  // Every 1000 milliseconds, ask Claude Code to draw your sites again
  $.clock.every(1000, () => $.ui.invalidate('ui.render'))
  return next(e)
})
```

Claude Code exécute maintenant votre hook `ui.render` une fois par seconde. La minuterie s'arrête quand le module se recharge, et la nouvelle copie du module démarre la sienne.

<h3 id="how-often-a-site-can-redraw">
  À quelle fréquence un site peut redessiner
</h3>

Claude Code limite la fréquence à laquelle il redessine un site, donc votre mod peut appeler `$.ui.invalidate` aussi souvent que ses données changent. Le volet visible et la bande ont une limite plus élevée que les autres sites, et le [tableau des limites](/docs/fr/plugins/mods/reference#limits) contient les chiffres.

Les appels qui viennent plus vite que la limite sont combinés en un redessin. Ce redessin exécute votre hook une fois, et le hook lit vos données telles qu'elles sont à ce moment, donc la valeur la plus récente s'affiche et les valeurs entre les deux ne s'affichent pas. Une animation ne peut pas s'exécuter plus vite que la limite.

<h2 id="keep-state">
  Conserver l'état
</h2>

Un mod a trois endroits pour garder une valeur, et ils diffèrent dans la durée pendant laquelle la valeur dure : jusqu'à ce que le module se recharge, jusqu'à ce que la session se termine, ou d'une session à l'autre. Choisissez selon la durée pendant laquelle la valeur doit durer :

| La garder dans | Elle dure jusqu'à | L'utiliser pour |
| :- | :- | :- |
| Une variable au niveau du module | Le module se recharge, ce qui se produit chaque fois que vous enregistrez un fichier pendant le développement | Les valeurs que vous pouvez perdre, comme `tab` dans `hello-tabs` |
| `$.state` | La session se termine, ou l'utilisateur exécute `/clear`, `/resume`, ou `/branch` | Les valeurs sur lesquelles un dessin dépend qui devraient survivre à un rechargement |
| `$.store` | Votre mod la supprime, ou aucune session ne lit ou n'écrit le magasin pendant [`cleanupPeriodDays`](/docs/fr/settings-reference#cleanupperioddays). Le magasin est un magasin clé-valeur, enregistré en tant que fichier JSON de votre propre plugin sous `~/.claude/plugins/store/`. | Les paramètres, l'historique, tout ce que l'utilisateur s'attend à trouver la prochaine fois |

`$.store.get(key)` se résout en la valeur ou `undefined`, et `$.store.set(key, value)` prend n'importe quelle valeur JSON.

<h3 id="keep-a-value-in-state">
  Garder une valeur dans `$.state`
</h3>

`$.state` contient des valeurs pour la durée d'une session, et il redessine pour vous. C'est un état réactif : un hook `ui.render` qui lit une valeur s'y abonne, donc Claude Code redessine ce site chaque fois que vous écrivez la valeur, et vous n'appelez pas `$.ui.invalidate`. Une valeur dans `$.state` survit aussi à un rechargement du module, ce qu'une variable ne fait pas.

Pour le configurer, déclarez vos valeurs, pointez votre manifeste vers la déclaration, puis définissez et utilisez chaque valeur. Les exemples déplacent le `count` de `hello-tabs` dans `$.state`.

<h4 id="declare-the-values">
  Déclarer les valeurs
</h4>

Déclarez les valeurs dans un fichier de types. La clé externe est le nom de votre plugin, et chaque entrée en dessous est une valeur et son type. Enregistrez ceci sous `hello-tabs/types/index.d.ts` :

```typescript hello-tabs/types/index.d.ts theme={null}
declare module 'claude-code' {
  interface PluginState {
    'hello-tabs': {
      tab: 'one' | 'two'
      count: number
    }
  }
}
```

<h4 id="point-the-manifest-at-the-declaration">
  Pointer le manifeste vers la déclaration
</h4>

Pour laisser `claude plugin validate` vérifier votre code par rapport à ce fichier, ajoutez un champ `types` au manifeste avec son chemin :

```json hello-tabs/.claude-plugin/plugin.json theme={null}
{
  "name": "hello-tabs",
  "version": "0.1.0",
  "description": "Opens a pane with two tabs and a counter",
  "author": { "name": "Your Name" },
  "types": "./types/index.d.ts"
}
```

<h4 id="define-read-and-write-a-value">
  Définir, lire et écrire une valeur
</h4>

Dans votre module, définissez chaque valeur avec une valeur par défaut, lisez-la pendant le dessin, et écrivez-la à partir d'un callback. `atom` nomme une valeur et sa valeur par défaut, `read` la retourne, et `update` l'écrit. Les trois aides appellent `$.state.get` et `$.state.set` pour vous :

```javascript theme={null}
import { atom, read, update } from 'claude-code'

// At the top of the module: name the value and give its default
const count = atom({ plugin: 'hello-tabs', key: 'count' }, 0)

// In the ui.render hook: read the value to draw it
const n = await read($, count)

// In a Button: write a new value from the old one
onPress: () => update($, count, (value) => value + 1)
```

Parce que le hook `ui.render` a lu `count`, Claude Code exécute le hook à nouveau chaque fois que le bouton l'écrit.

Trois règles s'appliquent au code :

* **Écrivez `plugin` et `key` comme des chaînes littérales** : `claude plugin validate` les lit de votre source
* **Déclarez chaque valeur dans le fichier de types** : sinon la validation échoue avec `hello-tabs.count is not declared`
* **Écrivez à partir d'un callback ou du hook d'un autre événement** : un hook `ui.render` peut lire l'état et ne peut pas l'écrire, donc écrivez à partir de `onPress`, `onSubmit`, ou un hook pour un autre événement

<h4 id="change-hello-tabs-to-use-state">
  Modifier `hello-tabs` pour utiliser `$.state`
</h4>

Pour déplacer `count` dans `hello-tabs` dans `$.state`, modifiez chaque ligne qui l'utilise :

* **En haut du module** : ajoutez la ligne `import`, et remplacez `let count = 0` par la ligne `atom`
* **Dans le hook `ui.render`** : ajoutez la ligne `read` avant `tabButton`, et dessinez `'Count: ' + n` dans le `Text`
* **Dans le bouton Add one** : remplacez `onPress` par celui dans [Enregistrer à partir de plus d'une session](#save-from-more-than-one-session), qui enregistre le compte ainsi que l'écrit
* **Dans le hook `session.start`** : remplacez les deux lignes qui lisent `saved` par l'appel `loadCount` de [Charger une valeur sauvegardée à nouveau après `/clear`](#load-a-saved-value-again-after-clear)

Gardez `redraw` pour les boutons d'onglets, car `tab` est toujours une variable.

<h3 id="load-a-saved-value-again-after-clear">
  Charger une valeur sauvegardée à nouveau après `/clear`
</h3>

Si votre mod copie une valeur sauvegardée de `$.store` dans `$.state` à `session.start`, il doit la copier à nouveau après `/clear`, `/resume`, ou `/branch`. Ces commandes remettent chaque valeur `$.state` à sa valeur par défaut, et `session.start` ne se déclenche pas à nouveau. [`classic.SessionStart`](/docs/fr/plugins/mods/events#hook-the-settings-hook-events) se déclenche après chacune d'elles, avec `e.source` défini sur `clear`, `resume`, ou `fork`, donc copiez la valeur à nouveau dans un hook dessus. Sinon votre dessin affiche la valeur par défaut, et un callback qui enregistre la valeur `$.state` écrit la valeur par défaut sur ce que vous avez stocké.

Ce code charge `count` à partir des deux hooks. Il s'appuie sur la version `$.state` de `hello-tabs`, où `count` est un atome et `update` est importé. Mettez `loadCount` au-dessus de `register`, et ajoutez l'appel `loadCount` au hook `session.start` que vous avez déjà. `classic.SessionStart` se déclenche aussi au démarrage et après compaction, ce qui ne réinitialise pas `$.state`, donc le filtre sur `source` garde le hook aux trois réinitialisations :

```javascript theme={null}
// Copy the saved count from $.store into $.state, or 0 if nothing is saved
async function loadCount($) {
  const saved = Number((await $.store.get('count')) ?? 0)
  await update($, count, () => saved)
}

// Runs before your first prompt, and again after a reload
on('session.start', async ($, e, next) => {
  await loadCount($)
  return next(e)
})

// Runs again after /clear, /resume, and /branch, which reports fork
on('classic.SessionStart', { source: ['clear', 'resume', 'fork'] }, async ($, e, next) => {
  await loadCount($)
  return next(e)
})
```

Avec les deux hooks en place, le volet affiche le compte sauvegardé après `/clear` et non `0`, et le prochain appui sur **Add one** ajoute au compte sauvegardé.

`loadCount` écrit la valeur stockée sur celle dans `$.state`, et `session.start` se déclenche à nouveau chaque fois que le module se recharge. Pour garder le magasin de prendre du retard, enregistrez à chaque changement, comme le bouton **Add one** le fait.

Pour vérifier le rechargement sans une session, [testez le dessin après `/clear`](/docs/fr/plugins/mods/test#test-a-drawing-after-clear).

<h3 id="save-from-more-than-one-session">
  Enregistrer à partir de plus d'une session
</h3>

Chaque session sur votre machine qui exécute votre mod partage un `$.store`. Un `get` suivi d'un `set` n'est pas atomique. Quand deux sessions lisent chacune une valeur, la modifient et l'écrivent, elles font la course, et la deuxième écriture remplace la première.

Deux choix rendent cela moins probable :

* **Donnez à chaque élément sa propre clé** : un `set` change seulement sa propre clé, donc les sessions qui écrivent des clés différentes ne s'écrasent pas mutuellement
* **Lisez à nouveau juste avant d'écrire** : pour une valeur que plusieurs sessions changent, `get` la clé dans le callback et construisez la nouvelle valeur à partir de cela, pas à partir d'une copie que vous avez chargée à `session.start`. L'écriture d'une autre session est toujours perdue si elle atterrit entre votre `get` et votre `set`.

Ce bouton ajoute un à ce que le magasin contient maintenant, puis met à jour le dessin :

```javascript theme={null}
onPress: async () => {
  // Read what the store holds now, which another session may have changed
  const saved = Number((await $.store.get('count')) ?? 0)
  // Save the new count, then show it
  await $.store.set('count', saved + 1)
  await update($, count, () => saved + 1)
}
```

Si une deuxième session a appuyé sur son propre bouton trois fois depuis le démarrage de cette session, cet appui affiche et enregistre un compte qui inclut ces trois.

<h2 id="next-steps">
  Prochaines étapes
</h2>

* [Réagir aux événements](/docs/fr/plugins/mods/events) : alimentez votre dessin à partir d'appels d'outils et de tours
* [Utiliser l'API des mods](/docs/fr/plugins/mods/api) : alimentez votre dessin à partir de minuteries et d'appels de modèle
* [Tester un dessin](/docs/fr/plugins/mods/test#test-a-drawing) : appuyez sur vos boutons à partir d'un test, sur plus d'une surface
* [Sites de rendu](/docs/fr/plugins/mods/reference#render-sites) et [éléments](/docs/fr/plugins/mods/reference#elements) : les props de chaque site et les props de chaque élément
