1> ## Documentation Index
2> Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt
3> Use this file to discover all available pages before exploring further.
4
5# Dessiner dans l'interface avec un mod
6
7> 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.
8
9Un 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à.
10
11Cette carte montre où un mod peut dessiner dans une session de terminal :
12
13<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" />
14
15<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" />
16
17Dans un terminal plus étroit, le volet se trouve au-dessus de l'invite au lieu de côté de la transcription.
18
19Construisez 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.
20
21<Note>
22 Pour rechercher une prop ou une limite, consultez la [référence](/docs/fr/plugins/mods/reference#render-sites).
23</Note>
24
25<h2 id="build-a-pane-with-tabs">
26 Construire un volet avec des onglets
27</h2>
28
29Dans 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.
30
31Le 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 :
32
33<Frame>
34 <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" />
35
36 <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" />
37</Frame>
38
39Claude 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.
40
41<Steps>
42 <Step title="Créer le plugin">
43 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.
44
45 Enregistrez le manifeste sous `hello-tabs/.claude-plugin/plugin.json` :
46
47 ```json hello-tabs/.claude-plugin/plugin.json theme={null}
48 {
49 "name": "hello-tabs",
50 "version": "0.1.0",
51 "description": "Opens a pane with two tabs and a counter",
52 "author": { "name": "Your Name" }
53 }
54 ```
55
56 Nommez votre point d'entrée dans `hello-tabs/hooks/hooks.json` :
57
58 ```json hello-tabs/hooks/hooks.json theme={null}
59 {
60 "modules": ["./register.js"]
61 }
62 ```
63 </Step>
64
65 <Step title="Écrire le code">
66 Le code fait trois choses, une dans chaque hook :
67
68 * Ajoute la commande `/hello-tabs`
69 * Ouvre le volet quand vous exécutez cette commande
70 * Dessine le contenu du volet : la ligne d'onglets et le corps de l'onglet ouvert
71
72 Deux variables au niveau du module, `tab` et `count`, conservent l'état du volet.
73
74 Enregistrez ceci sous `hello-tabs/hooks/register.js` :
75
76 ```javascript hello-tabs/hooks/register.js theme={null}
77 // The pane's id, used to open the pane and to recognize it when drawing
78 const PANE = 'hello-tabs'
79
80 // What the pane shows: which tab is open, and the counter's value
81 let tab = 'one'
82 let count = 0
83
84 export function register(on) {
85 // Runs before your first prompt, and again after a reload
86 on('session.start', async ($, e, next) => {
87 await $.command.register({ name: 'hello-tabs', description: 'Open the hello-tabs pane' })
88 // Load the count an earlier session saved, if there is one
89 const saved = await $.store.get('count')
90 if (typeof saved === 'number') count = saved
91 return next(e)
92 })
93
94 // Runs when you type /hello-tabs
95 on('command.run', { command: 'hello-tabs' }, async ($) => {
96 // Open the pane, give it the keyboard, and let Esc close it
97 await $.ui.open({ id: PANE, title: 'Hello tabs', focus: true, closeOnEscape: true })
98 // Print nothing in the transcript
99 return {}
100 })
101
102 // Runs each time Claude Code draws a pane
103 on('ui.render', { component: 'Pane' }, async ($, e, next) => {
104 // Leave other mods' panes alone
105 if (e.requestId !== PANE) return next(e)
106 // Get the elements this app can draw
107 const { Box, Text, Button } = $.ui.resolve(e)
108 // Ask Claude Code to run this hook again
109 const redraw = () => $.ui.invalidate('ui.render')
110
111 // One tab: a button that switches to its tab when pressed
112 const tabButton = (name, label, hotkey) =>
113 Button({
114 key: 'tab-' + name,
115 label,
116 hotkey,
117 plain: true,
118 // Dim the tab that isn't open
119 dimColor: tab !== name,
120 onPress: () => {
121 tab = name
122 redraw()
123 },
124 })
125
126 // What goes under the tabs, depending on which one is open
127 const body =
128 tab === 'one'
129 ? [Text({ children: ['This is the first tab.'] })]
130 : [
131 Box({
132 flexDirection: 'row',
133 columnGap: 2,
134 children: [
135 Button({
136 key: 'more',
137 label: 'Add one',
138 hotkey: 'a',
139 onPress: async () => {
140 count += 1
141 redraw()
142 // Save the count so it's there after a restart
143 await $.store.set('count', count)
144 },
145 }),
146 Text({ children: ['Count: ' + count] }),
147 ],
148 }),
149 ]
150
151 // The whole pane: the row of tabs, a blank line, then the body
152 return Box({
153 flexDirection: 'column',
154 children: [
155 Box({
156 flexDirection: 'row',
157 columnGap: 3,
158 children: [tabButton('one', 'One', '1'), tabButton('two', 'Two', '2')],
159 }),
160 Text({ children: [' '] }),
161 ...body,
162 ],
163 })
164 })
165 }
166 ```
167
168 Chaque hook fait aussi quelque chose que le code ne rend pas évident :
169
170 * **[`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.
171 * **[`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.
172 * **`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.
173
174 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.
175 </Step>
176
177 <Step title="Ouvrir le volet">
178 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.
179 </Step>
180
181 <Step title="Vérifier que le compte a été sauvegardé">
182 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é.
183
184 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.
185 </Step>
186</Steps>
187
188<h2 id="pick-where-to-draw">
189 Choisir où dessiner
190</h2>
191
192Un 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.
193
194Deux 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 :
195
196<Tabs>
197 <Tab title="Volet">
198 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.
199
200 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.
201
202 Pour dessiner dans votre volet, filtrez sur `{ component: 'Pane' }` et vérifiez que `e.requestId` est votre `id`.
203 </Tab>
204
205 <Tab title="Bande au-dessus de l'invite">
206 La bande est une bande directement au-dessus de l'entrée d'invite. Elle est toujours là, et chaque mod la partage.
207
208 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.
209
210 Pour dessiner dans la bande, filtrez sur `{ component: 'AbovePrompt' }`.
211 </Tab>
212</Tabs>
213
214<h3 id="change-what-claude-code-already-draws">
215 Modifier ce que Claude Code dessine déjà
216</h3>
217
218Claude 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 :
219
220| Site | Ce que c'est |
221| :- | :- |
222| `UserMessage`, `AssistantMessage` | Un message dans la transcription |
223| `ToolUse`, `ToolResult`, `ToolGroup` | La ligne d'un appel d'outil, son résultat, et une exécution repliée d'appels |
224| `CommandOutput` | La ligne qu'une commande a imprimée |
225| `AskUserQuestion` | Le dialogue que Claude ouvre pour vous poser une question |
226| `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 |
227| `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 |
228
229À 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).
230
231<Tabs>
232 <Tab title="Modifier un détail">
233 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 :
234
235 ```javascript theme={null}
236 on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
237 // Keep Claude Code's spinner, and change the text after its word
238 return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
239 })
240 ```
241
242 Le spinner garde son animation et son mot, et votre texte suit le mot :
243
244 ```text theme={null}
245 Thinking · tool calls: 2…
246 ```
247 </Tab>
248
249 <Tab title="Remplacer le dessin">
250 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 :
251
252 ```javascript theme={null}
253 on('ui.render', { component: 'Spinner' }, async ($, e) => {
254 const { Text } = $.ui.resolve(e)
255 // No call to next, so this line is drawn in the spinner's place
256 return Text({ children: ['Claude has made ' + calls + ' tool calls'] })
257 })
258 ```
259
260 Pendant que Claude travaille, votre ligne s'affiche et le spinner de Claude Code ne s'affiche pas :
261
262 ```text theme={null}
263 Claude has made 2 tool calls
264 ```
265 </Tab>
266
267 <Tab title="Le laisser tranquille">
268 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 :
269
270 ```javascript theme={null}
271 on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
272 // Nothing to show yet, so pass the event on unchanged
273 if (calls === 0) return next(e)
274 return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
275 })
276 ```
277
278 Avant le premier appel d'outil, le spinner ressemble à la façon dont il le fait sans le mod :
279
280 ```text theme={null}
281 Thinking…
282 ```
283 </Tab>
284</Tabs>
285
286L'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.
287
288Le 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é.
289
290<h3 id="open-a-pane-at-the-right-time">
291 Ouvrir un volet au bon moment
292</h3>
293
294Un 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.
295
296Pour 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.
297
298```javascript theme={null}
299await $.ui.open({ id: 'hello-tabs', title: 'Hello tabs', focus: true })
300```
301
302Pour fermer le volet, appelez `$.ui.close` avec l'`id` avec lequel vous l'avez ouvert :
303
304```javascript theme={null}
305await $.ui.close({ id: 'hello-tabs' })
306```
307
308En plus de `id`, `$.ui.open` prend ces champs optionnels :
309
310| Champ | Ce qu'il fait |
311| :- | :- |
312| `title` | L'étiquette d'onglet du volet quand plus d'un volet est ouvert |
313| `focus` | Demande le [focus clavier](#know-which-keys-your-mod-can-receive) |
314| `closeOnEscape` | Fait que Esc ferme le volet. Passez `true` ou laissez le champ de côté, car Claude Code refuse `false`. |
315| `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 |
316| `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. |
317| `columns` | La largeur à demander quand le volet se trouve à côté de la transcription |
318
319Pour 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.
320
321<h4 id="when-a-pane-waits-for-a-wider-terminal">
322 Quand un volet attend un terminal plus large
323</h4>
324
325Un 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 :
326
327* **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
328* **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.
329
330Quand 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.
331
332<h2 id="build-a-tree-from-elements">
333 Construire un arbre à partir d'éléments
334</h2>
335
336Ce 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.
337
338Pour 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`.
339
340La plupart des dessins utilisent quatre éléments. Sélectionnez un onglet pour voir chacun et comment le terminal le dessine :
341
342<Tabs>
343 <Tab title="Texte">
344 `Text` dessine une chaîne, avec un style optionnel comme `bold` et `color` :
345
346 ```javascript theme={null}
347 Text({ children: ['This is the first tab.'] })
348 ```
349
350 ```text theme={null}
351 This is the first tab.
352 ```
353 </Tab>
354
355 <Tab title="Boîte">
356 `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 :
357
358 ```javascript theme={null}
359 Box({
360 flexDirection: 'row',
361 columnGap: 2,
362 children: [
363 Button({ key: 'more', label: 'Add one', onPress: addOne }),
364 Text({ children: ['Count: 0'] }),
365 ],
366 })
367 ```
368
369 ```text theme={null}
370 [ Add one ] Count: 0
371 ```
372 </Tab>
373
374 <Tab title="Bouton">
375 `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 :
376
377 ```javascript theme={null}
378 Button({ key: 'more', label: 'Add one', onPress: addOne })
379 Button({ key: 'tab-one', label: 'One', hotkey: '1', plain: true, onPress: showTabOne })
380 ```
381
382 ```text theme={null}
383 [ Add one ]
384 1: One
385 ```
386 </Tab>
387
388 <Tab title="Entrée">
389 `Input` est un champ de texte. Il exécute votre callback `onSubmit` avec le texte quand l'utilisateur appuie sur Entrée :
390
391 ```javascript theme={null}
392 Input({
393 key: 'new-note',
394 label: 'Note',
395 placeholder: 'Type a note and press Enter',
396 value: '',
397 submitLabel: 'add',
398 onSubmit: addNote,
399 })
400 ```
401
402 ```text theme={null}
403 Note: Type a note and press Enter ⏎ add
404 ```
405 </Tab>
406</Tabs>
407
408Ce tableau liste chaque élément :
409
410| Élément | Ce qu'il dessine | Où |
411| :- | :- | :- |
412| `Box` | Un conteneur flex. Prend des props de mise en page comme `flexDirection`, `columnGap`, `padding`, `borderStyle`, et `width`. | Partout |
413| `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 |
414| `Button` | Un contrôle qui appelle `onPress` | Partout |
415| `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 |
416| `Input`, `Select` | Un champ de texte et un sélecteur | Terminal, Desktop |
417| `Svg` | Un document SVG | Desktop |
418| `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 |
419| `Raster`, `Image` | Une [grille de cellules colorées](#draw-a-grid-of-colored-cells), et une image | Terminal |
420
421Si 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.
422
423Si 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.
424
425Dans 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.
426
427<h3 id="draw-a-grid-of-colored-cells">
428 Dessiner une grille de cellules colorées
429</h3>
430
431Pour 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.
432
433L'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 :
434
435```javascript theme={null}
436// The value that means "use the terminal's default color"
437const DEFAULT_COLOR = 0x01000000
438
439// Pack rows of [character, color] pairs into the one string a Raster takes
440// One cell is three numbers: the character's code point, its color, and its background
441function cellsOf(rows) {
442 const numbers = rows.flat().flatMap(([char, color]) => [char.codePointAt(0), color, DEFAULT_COLOR])
443 return new Uint8Array(Uint32Array.from(numbers).buffer).toBase64()
444}
445
446on('ui.render', { component: 'Pane' }, async ($, e, next) => {
447 // Draw only in the pane opened with the id 'heat'
448 if (e.requestId !== 'heat') return next(e)
449 const { Box, Text, Raster } = $.ui.resolve(e)
450 // Two rows of three cells, each a block character and its color
451 const rows = [
452 [['█', 0x2e7d32], ['█', 0xf9a825], ['█', 0xc62828]],
453 [['█', 0x2e7d32], ['█', 0x2e7d32], ['█', 0xf9a825]],
454 ]
455 if (e.surface !== 'terminal') {
456 return Text({ children: ['The heat map needs the terminal.'] })
457 }
458 return Box({
459 flexDirection: 'column',
460 children: [Raster({ key: 'grid', columns: 3, rows: 2, cells: cellsOf(rows) })],
461 })
462})
463```
464
465Dans le terminal, le volet affiche la grille :
466
467<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" />
468
469Le 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.
470
471Chaque 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.
472
473<h2 id="respond-to-presses-and-typing">
474 Répondre aux appuis et à la saisie
475</h2>
476
477Quand 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 :
478
479* **`Button`** : prend `onPress(e)`, où `e.surface` est l'application d'où vient l'appui
480* **`Input`** : prend `onSubmit(value)` et `onInput(value)`
481* **`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' }]`
482
483Un 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.
484
485<h3 id="know-which-keys-your-mod-can-receive">
486 Focus clavier et touches de raccourci
487</h3>
488
489Votre 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.
490
491<h4 id="how-a-pane-gets-keyboard-focus">
492 Comment un volet obtient le focus clavier
493</h4>
494
495Un volet obtient le focus clavier de l'une de trois façons :
496
497* Votre mod l'ouvre avec `focus: true` à partir d'une commande ou d'un appui
498* L'utilisateur appuie sur Ctrl+X puis Tab
499* L'utilisateur clique dessus
500
501Claude 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.
502
503<h4 id="what-each-key-does">
504 Ce que chaque touche fait
505</h4>
506
507Ce tableau liste ce qu'une touche fait pendant que votre volet ou bande a le focus clavier :
508
509| Touche | Ce qu'elle fait |
510| :- | :- |
511| Tab | Se déplace vers le contrôle suivant |
512| 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. |
513| Entrée | Appuie sur le `Button` ciblé, soumet le `Input` ciblé, ou choisit dans un `Select` |
514| La touche de raccourci d'un bouton | Appuie sur ce bouton. Pendant qu'un `Input` a le focus, chaque touche imprimable va au champ. |
515| Esc | Retourne le focus clavier à l'invite. Avec `closeOnEscape: true`, il ferme aussi le volet. |
516
517Un 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`.
518
519<h4 id="set-a-hotkey-and-the-first-focus">
520 Définir une touche de raccourci et le premier focus
521</h4>
522
523Deux props sur un contrôle décident comment le clavier l'atteint :
524
525* **`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'`
526* **`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`.
527
528Comment une touche de raccourci s'affiche dépend du bouton et de l'application :
529
530| Bouton | Dans le terminal | Dans l'application Desktop |
531| :- | :- | :- |
532| Avec crochets, la valeur par défaut | `[ Add one ]`, sans touche de raccourci affichée | L'étiquette avec une petite touche à côté |
533| Avec `plain: true` | `1: One` | L'étiquette avec une petite touche à côté |
534
535Dans 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.
536
537<h3 id="take-typed-input-and-draw-a-row-for-each-item">
538 Prendre l'entrée tapée et dessiner une ligne pour chaque élément
539</h3>
540
541De 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 :
542
543```text theme={null}
544╭──────────────────────────────────────────────────────────╮
545│ Note: Type a note and press Enter ⏎ add ✕ │
546│ x buy milk │
547│ x call bob │
548╰──────────────────────────────────────────────────────────╯
549```
550
551L'exemple utilise deux techniques :
552
553* **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
554* **Dessiner une liste** : mappez vos données à une ligne chacune, et donnez à chaque bouton de ligne sa propre `key`
555
556Ce hook dessine le contenu du volet :
557
558```javascript theme={null}
559// The list the pane draws
560let notes = []
561
562on('ui.render', { component: 'Pane' }, async ($, e, next) => {
563 // Draw only in the pane opened with the id 'notes'
564 if (e.requestId !== 'notes') return next(e)
565 const { Box, Text, Button, Input } = $.ui.resolve(e)
566 const redraw = () => $.ui.invalidate('ui.render')
567
568 return Box({
569 flexDirection: 'column',
570 children: [
571 Input({
572 key: 'new-note',
573 label: 'Note',
574 placeholder: 'Type a note and press Enter',
575 // Draw the field empty each time, which clears it after a submit
576 value: '',
577 submitLabel: 'add',
578 autoFocus: true,
579 // Runs when you press Enter in the field
580 onSubmit: async (value) => {
581 // Ignore an empty line
582 if (!value.trim()) return
583 notes = [...notes, value.trim()]
584 redraw()
585 await $.store.set('notes', notes)
586 },
587 }),
588 // One row for each note: a delete button, then the note's text
589 ...notes.map((note, i) =>
590 Box({
591 flexDirection: 'row',
592 columnGap: 1,
593 children: [
594 Button({
595 // A key of its own, so each row's button can be told apart
596 key: 'delete-' + i,
597 label: 'x',
598 plain: true,
599 onPress: async () => {
600 notes = notes.filter((_, j) => j !== i)
601 redraw()
602 await $.store.set('notes', notes)
603 },
604 }),
605 Text({ children: [note] }),
606 ],
607 }),
608 ),
609 ],
610 })
611})
612```
613
614Pour essayer le volet :
615
616* **Ajouter une note** : tapez une ligne et appuyez sur Entrée. La ligne apparaît comme une nouvelle ligne, et le champ se vide.
617* **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.
618
619Chaque changement suit le même cycle de rendu que `hello-tabs` : le callback change `notes`, appelle `redraw`, et enregistre la liste dans `$.store`.
620
621Le 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 `''`.
622
623L'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`.
624
625Trois props composent la ligne du champ, `Note: Type a note and press Enter ⏎ add` :
626
627| Prop | Dans l'exemple | Ce que c'est |
628| :- | :- | :- |
629| `label` | `Note` | Le texte avant le champ. Le terminal dessine `: ` après. |
630| `placeholder` | `Type a note and press Enter` | Texte atténué qui s'affiche pendant que le champ est vide |
631| `submitLabel` | `add` | Le mot après `⏎` qui dit ce que fait Entrée |
632
633Soumettre 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).
634
635<h2 id="redraw-when-something-changes">
636 Redessiner un site
637</h2>
638
639Un 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.
640
641<h3 id="when-claude-code-redraws-without-being-asked">
642 Quand Claude Code redessine sans être demandé
643</h3>
644
645Claude 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.
646
647<h3 id="redraw-when-your-data-changes">
648 Redessiner quand vos données changent
649</h3>
650
651Pour 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 :
652
653```javascript theme={null}
654let count = 0
655
656on('ui.render', { component: 'Pane' }, async ($, e, next) => {
657 if (e.requestId !== 'counter') return next(e)
658 const { Box, Text, Button } = $.ui.resolve(e)
659 return Box({
660 flexDirection: 'row',
661 columnGap: 2,
662 children: [
663 Button({
664 key: 'more',
665 label: 'Add one',
666 onPress: () => {
667 count += 1
668 // The data changed, so ask Claude Code to draw the pane again
669 $.ui.invalidate('ui.render')
670 },
671 }),
672 Text({ children: ['Count: ' + count] }),
673 ],
674 })
675})
676```
677
678Chaque 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`.
679
680Une 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.
681
682<h3 id="redraw-on-a-timer">
683 Redessiner sur une minuterie
684</h3>
685
686Pour 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 :
687
688```javascript theme={null}
689on('session.start', async ($, e, next) => {
690 // Every 1000 milliseconds, ask Claude Code to draw your sites again
691 $.clock.every(1000, () => $.ui.invalidate('ui.render'))
692 return next(e)
693})
694```
695
696Claude 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.
697
698<h3 id="how-often-a-site-can-redraw">
699 À quelle fréquence un site peut redessiner
700</h3>
701
702Claude 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.
703
704Les 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.
705
706<h2 id="keep-state">
707 Conserver l'état
708</h2>
709
710Un 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 :
711
712| La garder dans | Elle dure jusqu'à | L'utiliser pour |
713| :- | :- | :- |
714| 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` |
715| `$.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 |
716| `$.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 |
717
718`$.store.get(key)` se résout en la valeur ou `undefined`, et `$.store.set(key, value)` prend n'importe quelle valeur JSON.
719
720<h3 id="keep-a-value-in-state">
721 Garder une valeur dans `$.state`
722</h3>
723
724`$.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.
725
726Pour 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`.
727
728<h4 id="declare-the-values">
729 Déclarer les valeurs
730</h4>
731
732Dé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` :
733
734```typescript hello-tabs/types/index.d.ts theme={null}
735declare module 'claude-code' {
736 interface PluginState {
737 'hello-tabs': {
738 tab: 'one' | 'two'
739 count: number
740 }
741 }
742}
743```
744
745<h4 id="point-the-manifest-at-the-declaration">
746 Pointer le manifeste vers la déclaration
747</h4>
748
749Pour laisser `claude plugin validate` vérifier votre code par rapport à ce fichier, ajoutez un champ `types` au manifeste avec son chemin :
750
751```json hello-tabs/.claude-plugin/plugin.json theme={null}
752{
753 "name": "hello-tabs",
754 "version": "0.1.0",
755 "description": "Opens a pane with two tabs and a counter",
756 "author": { "name": "Your Name" },
757 "types": "./types/index.d.ts"
758}
759```
760
761<h4 id="define-read-and-write-a-value">
762 Définir, lire et écrire une valeur
763</h4>
764
765Dans 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 :
766
767```javascript theme={null}
768import { atom, read, update } from 'claude-code'
769
770// At the top of the module: name the value and give its default
771const count = atom({ plugin: 'hello-tabs', key: 'count' }, 0)
772
773// In the ui.render hook: read the value to draw it
774const n = await read($, count)
775
776// In a Button: write a new value from the old one
777onPress: () => update($, count, (value) => value + 1)
778```
779
780Parce que le hook `ui.render` a lu `count`, Claude Code exécute le hook à nouveau chaque fois que le bouton l'écrit.
781
782Trois règles s'appliquent au code :
783
784* **Écrivez `plugin` et `key` comme des chaînes littérales** : `claude plugin validate` les lit de votre source
785* **Déclarez chaque valeur dans le fichier de types** : sinon la validation échoue avec `hello-tabs.count is not declared`
786* **É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
787
788<h4 id="change-hello-tabs-to-use-state">
789 Modifier `hello-tabs` pour utiliser `$.state`
790</h4>
791
792Pour déplacer `count` dans `hello-tabs` dans `$.state`, modifiez chaque ligne qui l'utilise :
793
794* **En haut du module** : ajoutez la ligne `import`, et remplacez `let count = 0` par la ligne `atom`
795* **Dans le hook `ui.render`** : ajoutez la ligne `read` avant `tabButton`, et dessinez `'Count: ' + n` dans le `Text`
796* **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
797* **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)
798
799Gardez `redraw` pour les boutons d'onglets, car `tab` est toujours une variable.
800
801<h3 id="load-a-saved-value-again-after-clear">
802 Charger une valeur sauvegardée à nouveau après `/clear`
803</h3>
804
805Si 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é.
806
807Ce 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 :
808
809```javascript theme={null}
810// Copy the saved count from $.store into $.state, or 0 if nothing is saved
811async function loadCount($) {
812 const saved = Number((await $.store.get('count')) ?? 0)
813 await update($, count, () => saved)
814}
815
816// Runs before your first prompt, and again after a reload
817on('session.start', async ($, e, next) => {
818 await loadCount($)
819 return next(e)
820})
821
822// Runs again after /clear, /resume, and /branch, which reports fork
823on('classic.SessionStart', { source: ['clear', 'resume', 'fork'] }, async ($, e, next) => {
824 await loadCount($)
825 return next(e)
826})
827```
828
829Avec 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é.
830
831`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.
832
833Pour vérifier le rechargement sans une session, [testez le dessin après `/clear`](/docs/fr/plugins/mods/test#test-a-drawing-after-clear).
834
835<h3 id="save-from-more-than-one-session">
836 Enregistrer à partir de plus d'une session
837</h3>
838
839Chaque 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.
840
841Deux choix rendent cela moins probable :
842
843* **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
844* **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`.
845
846Ce bouton ajoute un à ce que le magasin contient maintenant, puis met à jour le dessin :
847
848```javascript theme={null}
849onPress: async () => {
850 // Read what the store holds now, which another session may have changed
851 const saved = Number((await $.store.get('count')) ?? 0)
852 // Save the new count, then show it
853 await $.store.set('count', saved + 1)
854 await update($, count, () => saved + 1)
855}
856```
857
858Si 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.
859
860<h2 id="next-steps">
861 Prochaines étapes
862</h2>
863
864* [Réagir aux événements](/docs/fr/plugins/mods/events) : alimentez votre dessin à partir d'appels d'outils et de tours
865* [Utiliser l'API des mods](/docs/fr/plugins/mods/api) : alimentez votre dessin à partir de minuteries et d'appels de modèle
866* [Tester un dessin](/docs/fr/plugins/mods/test#test-a-drawing) : appuyez sur vos boutons à partir d'un test, sur plus d'une surface
867* [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