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# Dibujar en la interfaz con un mod
6
7> Dibuje paneles, una banda sobre el símbolo del sistema, botones y campos de texto desde un mod de Claude Code, maneje pulsaciones e entrada, y mantenga el estado entre redibujos y sesiones.
8
9Un mod puede dibujar su propia interfaz en Claude Code y cambiar partes de la interfaz que Claude Code ya dibuja. Cada lugar donde un mod puede dibujar se llama [sitio de renderizado](/docs/es/plugins/mods/reference#render-sites), como un panel, la banda sobre el símbolo del sistema o el spinner. Claude Code genera el evento [`ui.render`](/docs/es/plugins/mods/reference#interface) cada vez que está a punto de dibujar un sitio de renderizado, y su hook para ese evento devuelve lo que se debe dibujar allí.
10
11Este mapa muestra dónde un mod puede dibujar en una sesión 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="Mapa de una sesión de terminal de Claude Code. Un mod puede agregar un panel como barra lateral a la derecha, una notificación en la esquina superior derecha de la transcripción, una línea de registro en la transcripción, una banda sobre el símbolo del sistema y una línea de estado bajo el símbolo del sistema. Un mod puede redibujar mensajes, filas de llamadas de herramientas y el spinner. El símbolo del sistema es propio 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="Mapa de una sesión de terminal de Claude Code. Un mod puede agregar un panel como barra lateral a la derecha, una notificación en la esquina superior derecha de la transcripción, una línea de registro en la transcripción, una banda sobre el símbolo del sistema y una línea de estado bajo el símbolo del sistema. Un mod puede redibujar mensajes, filas de llamadas de herramientas y el spinner. El símbolo del sistema es propio de Claude Code." width="600" height="336" data-path="images/mods-screen-map-dark.svg" />
16
17En una terminal más estrecha, el panel se sitúa sobre el símbolo del sistema en lugar de junto a la transcripción.
18
19Construya su [primer mod](/docs/es/plugins/mods/create) antes de comenzar aquí. Comience con el ejemplo trabajado, que construye un panel con dos pestañas y un contador, luego lea la sección para cada parte que desee cambiar.
20
21<Note>
22 Para buscar una propiedad o límite, consulte la [referencia](/docs/es/plugins/mods/reference#render-sites).
23</Note>
24
25<h2 id="build-a-pane-with-tabs">
26 Construir un panel con pestañas
27</h2>
28
29En esta sección construye un mod que agrega un comando `/hello-tabs` y el comando abre un panel. Un panel es una barra lateral junto a la transcripción en una terminal de pantalla completa ancha, o una región enmarcada sobre el símbolo del sistema de otra manera. Este panel muestra dos pestañas, y la segunda pestaña tiene un botón que suma uno a un contador. El recuento sigue ahí después de reiniciar Claude Code.
30
31El mod terminado se ve así. La grabación abre el panel, cambia a la segunda pestaña, presiona el botón varias veces y vuelve a la primera pestaña:
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="El comando /hello-tabs se escribe en el símbolo del sistema de Claude Code y se abre un panel enmarcado sobre él, con '1: One' y '2: Two' en la parte superior y el texto 'This is the first tab.' La segunda pestaña muestra un botón 'Add one' junto a 'Count: 1', y el recuento sube a 3. El panel luego vuelve a la primera pestaña." 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="El comando /hello-tabs se escribe en el símbolo del sistema de Claude Code y se abre un panel enmarcado sobre él, con '1: One' y '2: Two' en la parte superior y el texto 'This is the first tab.' La segunda pestaña muestra un botón 'Add one' junto a 'Count: 1', y el recuento sube a 3. El panel luego vuelve a la primera pestaña." data-path="images/mods-hello-tabs-dark.mp4" />
37</Frame>
38
39Claude Code no tiene un elemento de pestañas integrado, por lo que las pestañas son dos botones en una fila. El mod realiza un seguimiento de cuál está activo y dibuja el contenido de esa pestaña bajo la fila.
40
41<Steps>
42 <Step title="Crear el plugin">
43 Un mod es un plugin con un manifiesto, un `hooks.json` que apunta a su código y el archivo de código. [Crear un mod](/docs/es/plugins/mods/create#write-a-mod-yourself) explica cada uno. Cree un directorio llamado `hello-tabs` con directorios `.claude-plugin` y `hooks` dentro, luego guarde los dos primeros archivos.
44
45 Guarde el manifiesto como `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 Nombre su punto de entrada en `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="Escribir el código">
66 El código realiza tres trabajos, uno en cada hook:
67
68 * Agrega el comando `/hello-tabs`
69 * Abre el panel cuando ejecuta ese comando
70 * Dibuja el contenido del panel: la fila de pestañas y el cuerpo de la pestaña abierta
71
72 Dos variables a nivel de módulo, `tab` y `count`, mantienen el estado del panel.
73
74 Guarde esto como `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 Cada hook también hace algo que el código no deja claro:
169
170 * **[`session.start`](/docs/es/plugins/mods/reference#session)** también lee el recuento guardado de [`$.store`](#keep-state), un almacén de clave-valor que persiste entre sesiones.
171 * **[`command.run`](/docs/es/plugins/mods/api#add-a-command)** solo le dice a Claude Code que el panel existe. Abrir un panel no dibuja nada por sí solo: Claude Code luego genera `ui.render` para preguntar qué va en él.
172 * **`ui.render`** devuelve el árbol de elementos, un `Box` que contiene otros cuadros, texto y botones, y lo construye nuevamente desde `tab` y `count` cada vez que se ejecuta.
173
174 Presionar un botón ejecuta su callback `onPress`, que cambia una variable y llama a `redraw`. Claude Code luego ejecuta el hook `ui.render` nuevamente, y el hook construye un nuevo árbol a partir de los nuevos valores. Cada dibujo interactivo utiliza ese ciclo de renderizado: un callback cambia el estado y el hook se renderiza nuevamente desde el nuevo estado.
175 </Step>
176
177 <Step title="Abrir el panel">
178 En su shell, inicie Claude Code con `claude --plugin-dir ./hello-tabs`. En el símbolo del sistema de Claude Code, ejecute `/hello-tabs`. Se abre un panel con `1: One` y `2: Two` en la parte superior. Presione `2`, luego presione `a`, el atajo de teclado para **Add one**, varias veces. El recuento sube.
179 </Step>
180
181 <Step title="Verificar que el recuento fue guardado">
182 Presione Esc para cerrar el panel, luego salga de la sesión. En su shell, inicie Claude Code nuevamente con el mismo comando `claude --plugin-dir ./hello-tabs` y en el símbolo del sistema de Claude Code ejecute `/hello-tabs`. El recuento está donde lo dejó.
183
184 Para borrar el recuento, haga que el mod llame a `$.store.delete('count')`. [Mantener estado](#keep-state) cubre cuánto tiempo dura cada tipo de valor.
185 </Step>
186</Steps>
187
188<h2 id="pick-where-to-draw">
189 Elegir dónde dibujar
190</h2>
191
192Un hook `ui.render` se ejecuta para cada sitio de renderizado a menos que lo reduzca al que desea dibujar. Para elegir el sitio de renderizado, pase un filtro, llamado [matcher](/docs/es/plugins/mods/events#filter-which-events-a-hook-handles), como segundo argumento a `on`. `{ component: 'Pane' }` ejecuta el hook solo para paneles. En el hook, `e.component` nombra el sitio, `e.surface` dice qué aplicación está dibujando, y `e.props` contiene los datos propios del sitio. Para un panel, `e.requestId` es el `id` con el que lo abrió.
193
194Dos sitios están vacíos hasta que un mod los llena, el panel y la banda. Seleccione una pestaña para ver qué es cada uno y cómo dibujar en él:
195
196<Tabs>
197 <Tab title="Panel">
198 Un panel es una barra lateral junto a la transcripción en una terminal de pantalla completa ancha, o una región enmarcada sobre el símbolo del sistema de otra manera. Con varios paneles abiertos, cada uno obtiene una pestaña que muestra su título.
199
200 Un panel aparece cuando su mod llama a `$.ui.open` con un `id` que elige, como en `$.ui.open({ id: 'hello-tabs' })`. [Abrir un panel en el momento adecuado](#open-a-pane-at-the-right-time) cubre los otros campos y cuándo un panel espera una terminal más ancha.
201
202 Para dibujar en su panel, filtre en `{ component: 'Pane' }` y verifique que `e.requestId` sea su `id`.
203 </Tab>
204
205 <Tab title="Banda sobre el símbolo del sistema">
206 La banda es una franja directamente sobre la entrada del símbolo del sistema. Siempre está ahí, y cada mod la comparte.
207
208 Su hook devuelve un árbol para mostrar algo en la banda, o `next(e)` para no mostrar nada. Un árbol reemplaza lo que los mods [después del suyo](/docs/es/plugins/mods/events#the-order-mods-run-in) dibujan allí. Para mantener el suyo, ponga el resultado de `await next(e)` entre los hijos de un [`Box`](#build-a-tree-from-elements) en su árbol.
209
210 Para dibujar en la banda, filtre en `{ component: 'AbovePrompt' }`.
211 </Tab>
212</Tabs>
213
214<h3 id="change-what-claude-code-already-draws">
215 Cambiar lo que Claude Code ya dibuja
216</h3>
217
218Claude Code dibuja la mayoría de su interfaz a sí mismo: mensajes, filas de llamadas de herramientas, el spinner y más. Cada una de esas partes es un sitio de renderizado también, por lo que un mod puede cambiar el estilo o reemplazarlo. Para cambiar uno, filtre su hook `ui.render` en su nombre de esta tabla:
219
220| Sitio | Qué es |
221| :- | :- |
222| `UserMessage`, `AssistantMessage` | Un mensaje en la transcripción |
223| `ToolUse`, `ToolResult`, `ToolGroup` | La fila de una llamada de herramienta, su resultado y una ejecución plegada de llamadas |
224| `CommandOutput` | La fila que un comando imprimió |
225| `AskUserQuestion` | El diálogo que Claude abre para hacerle una pregunta |
226| `Spinner`, `ToolProgress`, `TurnDuration` | Líneas de estado para un turno: la línea que se anima mientras Claude trabaja, la línea de progreso en vivo de una herramienta en ejecución y la línea que cierra un turno |
227| `InfoNotice`, `SessionMode`, `PromptHint` | Líneas de estado bajo el logo, las etiquetas de modo en el pie de página y la línea de sugerencia bajo el símbolo del sistema |
228
229En un sitio que Claude Code ya dibuja, su hook tiene tres opciones: cambiar un detalle, reemplazar el dibujo o dejarlo solo. Seleccione una pestaña para ver cada una aplicada al spinner. Los ejemplos leen una variable `calls` que otro hook cuenta, como en el [mod de tutorial](/docs/es/plugins/mods/create#write-a-mod-yourself).
230
231<Tabs>
232 <Tab title="Cambiar un detalle">
233 Para mantener el dibujo de Claude Code y cambiar una parte de él, pase a `next` una copia del evento con `props` cambiados. Este hook cambia el texto después de la palabra del 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 El spinner mantiene su animación y su palabra, y su texto sigue a la palabra:
243
244 ```text theme={null}
245 Thinking · tool calls: 2…
246 ```
247 </Tab>
248
249 <Tab title="Reemplazar el dibujo">
250 Para dibujar algo propio en el lugar del sitio, devuelva un árbol y no llame a `next`. Este hook dibuja una línea de texto donde estaría el spinner:
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 Mientras Claude trabaja, su línea se muestra y el spinner de Claude Code no:
261
262 ```text theme={null}
263 Claude has made 2 tool calls
264 ```
265 </Tab>
266
267 <Tab title="Dejarlo solo">
268 Para dejar el sitio como Claude Code lo dibuja, devuelva `next(e)`. Un hook a menudo hace eso para algunos eventos y no para otros. Este hook deja el spinner solo hasta que hay una llamada para contar:
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 Antes de la primera llamada de herramienta, el spinner se ve de la manera que lo hace sin el mod:
279
280 ```text theme={null}
281 Thinking…
282 ```
283 </Tab>
284</Tabs>
285
286El símbolo del sistema de permiso no es un sitio de renderizado, por lo que un mod no puede cambiar lo que muestra. El diálogo de pregunta, `AskUserQuestion`, es uno, por lo que un mod puede cambiar eso.
287
288La terminal y la aplicación de escritorio no generan todos los mismos sitios. `Pane`, `AbovePrompt`, `Spinner` y los sitios de transcripción funcionan en ambos. Algunas otras líneas de estado se generan solo en la terminal. La [tabla de sitios de renderizado](/docs/es/plugins/mods/reference#render-sites) enumera dónde se genera cada uno.
289
290<h3 id="open-a-pane-at-the-right-time">
291 Abrir un panel en el momento adecuado
292</h3>
293
294Un panel aparece solo cuando su mod lo abre. Cómo y cuándo lo abre decide si toma el enfoque del teclado, cuánto espacio solicita y si aparece en absoluto en una terminal estrecha.
295
296Para abrir un panel, llame a [`$.ui.open`](/docs/es/plugins/mods/reference#mods-api-methods) con un `id` que elija. El `id` es el nombre del panel: su hook `ui.render` lo verifica y lo pasa nuevamente para cerrar el panel.
297
298```javascript theme={null}
299await $.ui.open({ id: 'hello-tabs', title: 'Hello tabs', focus: true })
300```
301
302Para cerrar el panel, llame a `$.ui.close` con el `id` con el que lo abrió:
303
304```javascript theme={null}
305await $.ui.close({ id: 'hello-tabs' })
306```
307
308Además de `id`, `$.ui.open` toma estos campos opcionales:
309
310| Campo | Qué hace |
311| :- | :- |
312| `title` | La etiqueta de pestaña del panel cuando hay más de un panel abierto |
313| `focus` | Solicita [enfoque del teclado](#know-which-keys-your-mod-can-receive) |
314| `closeOnEscape` | Hace que Esc cierre el panel. Pase `true` o deje el campo fuera, porque Claude Code rechaza `false`. |
315| `holdToasts` | Mantiene las notificaciones, los pequeños avisos de [`$.ui.toast`](/docs/es/plugins/mods/api#show-something-without-starting-a-turn), hasta que se cierre el panel |
316| `rows` | La altura a solicitar cuando el panel se sitúa sobre el símbolo del sistema. El valor predeterminado es un tercio del espacio. |
317| `columns` | El ancho a solicitar cuando el panel se sitúa junto a la transcripción |
318
319Para permitir que un comando abra el panel mientras Claude está trabajando, agregue `immediate: true` cuando [registre el comando](/docs/es/plugins/mods/api#add-a-command). Sin él, un comando escrito durante un turno espera a que el turno termine.
320
321<h4 id="when-a-pane-waits-for-a-wider-terminal">
322 Cuando un panel espera una terminal más ancha
323</h4>
324
325Un panel que su mod abre sin ser solicitado no aparece en una terminal estrecha, por lo que no puede ocupar una pantalla pequeña. Si aparece depende de lo que lo abrió:
326
327* **Abierto por algo que hizo el usuario**, como un comando que ejecutó o un botón que presionó, el panel aparece en cualquier ancho
328* **Abierto por su mod actuando por sí solo**, como desde un temporizador o un hook [`turn.start`](/docs/es/plugins/mods/events#follow-a-turn), el panel aparece solo en una terminal de al menos 144 columnas de ancho. Después de que el usuario haya abierto ese panel una vez por sí mismo, 110 columnas es suficiente.
329
330Cuando aparece el panel, `$.ui.open` se resuelve en `{ isPlaced: true }`. Cuando el panel está esperando, `isPlaced` es `false` y `reason` es una cadena que dice por qué. Un panel en espera aparece cuando el usuario lo abre o amplía la terminal. Para decir que algo está disponible sin abrir un panel, llame a `$.ui.toast('Your message')`, que muestra un pequeño aviso que desaparece después de unos segundos.
331
332<h2 id="build-a-tree-from-elements">
333 Construir un árbol a partir de elementos
334</h2>
335
336Lo que devuelve un hook `ui.render` es un árbol de elementos: una descripción de qué dibujar, hecha de cuadros, texto y controles anidados entre sí. Describe el dibujo y Claude Code lo renderiza en la terminal o en la aplicación de escritorio.
337
338Para obtener los elementos, llame a `$.ui.resolve(e)` en su hook, como en `const { Box, Text, Button } = $.ui.resolve(e)`. Cada elemento es una función. Pasa sus propiedades y pone los elementos y cadenas que van dentro en `children`.
339
340La mayoría de los dibujos utilizan cuatro elementos. Seleccione una pestaña para ver cada uno y cómo la terminal lo dibuja:
341
342<Tabs>
343 <Tab title="Texto">
344 `Text` dibuja una cadena, con estilo opcional como `bold` y `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="Cuadro">
356 `Box` organiza lo que hay dentro, en una fila o una columna. Este pone un botón y una línea de texto uno al lado del otro, dos columnas separadas:
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="Botón">
375 `Button` es un control que el usuario puede presionar. Ejecuta su callback `onPress`. Con `plain: true` no tiene corchetes y muestra su atajo de teclado:
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="Entrada">
389 `Input` es un campo de texto. Ejecuta su callback `onSubmit` con el texto cuando el usuario presiona Enter:
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
408Esta tabla enumera cada elemento:
409
410| Elemento | Qué dibuja | Dónde |
411| :- | :- | :- |
412| `Box` | Un contenedor flex. Toma propiedades de diseño como `flexDirection`, `columnGap`, `padding`, `borderStyle` y `width`. | En todas partes |
413| `Text` | Texto con estilo. Toma `color`, `bold`, `dimColor`, `italic` y `wrap`. Un `color` es una clave de tema o un color como `'red'`. Un `wrap` es `'wrap'`, `'truncate'`, `'truncate-start'`, `'truncate-middle'` o `'truncate-end'`. | En todas partes |
414| `Button` | Un control que llama a `onPress` | En todas partes |
415| `Link`, `Code`, `Markdown` | Un enlace con `href` y una `label` opcional, un bloque de código y texto formateado de la manera que lo son las respuestas de Claude. `Markdown` toma su contenido en una propiedad `text`, no en `children`, y necesita una `key` cuando pasa `onLinkPress`. | En todas partes |
416| `Input`, `Select` | Un campo de texto y un selector | Terminal, Escritorio |
417| `Svg` | Un documento SVG | Escritorio |
418| `Client` | Una región dibujada por un segundo archivo suyo, para animación e entrada de puntero. Ese archivo no obtiene API de mods. Solo llega a sus hooks publicando datos, que llegan como un evento `ui.message`. | Terminal, Escritorio |
419| `Raster`, `Image` | Una [cuadrícula de celdas coloreadas](#draw-a-grid-of-colored-cells) y una imagen | Terminal |
420
421Si su módulo es un archivo `.tsx` o `.jsx`, puede escribir el árbol como JSX. Desestructure los elementos de `$.ui.resolve(e)` primero, porque un módulo de hooks no tiene globales de elementos.
422
423Si un árbol utiliza un elemento que la aplicación no tiene, una propiedad que un elemento no toma o un hijo donde no va ninguno, Claude Code dibuja su propia versión del sitio.
424
425En una sesión iniciada con `--plugin-dir`, una línea de transcripción lo dice, como `ui.render (Pane) refused: Text prop "bogusProp" is not allowed; the engine drew its own`. El [registro de depuración](/docs/es/plugins/mods/troubleshoot#read-the-debug-log) lo registra como `ui.render (Pane): a hook returned a tree that does not validate` con la misma razón. Nada más aparece en la sesión, por lo que cuando un dibujo no aparece, verifique esa línea o el registro.
426
427<h3 id="draw-a-grid-of-colored-cells">
428 Dibujar una cuadrícula de celdas coloreadas
429</h3>
430
431Para un mapa de calor, un gráfico de chispa o un tablero de juego en la terminal, dibuje un `Raster` y no un `Box` para cada celda. Un `Raster` toma una `key`, su tamaño en `columns` y `rows`, y `cells`, que empaqueta cada celda en una cadena. Cada celda son tres números: el punto de código del carácter, su color y su color de fondo. Un color es un número hexadecimal con dos dígitos cada uno para rojo, verde y azul, como `0xc62828` para un rojo, o `0x01000000` para el predeterminado de la terminal.
432
433La aplicación de escritorio no tiene `Raster`, por lo que verifique `e.surface` y dibuje texto allí. Este cuerpo de panel dibuja un mapa de calor de tres por dos:
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
465En la terminal, el panel muestra la cuadrícula:
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 panel en la terminal que contiene una pequeña cuadrícula de bloques coloreados, dos filas de tres. La fila superior es verde, ámbar y roja. La fila inferior es verde, verde y ámbar." width="360" height="132" data-path="images/mods-heat-map.svg" />
468
469La matriz `rows` es la parte que cambiaría, y `cellsOf` la convierte en la cadena empaquetada. El hook dibuja solo en un panel cuyo `id` es `heat`, por lo que abra uno con `$.ui.open({ id: 'heat' })` desde un comando, como el ejemplo [`hello-tabs`](#build-a-pane-with-tabs) abre su panel.
470
471Cada carácter tiene que tener un ancho de una celda. Para animar un `Raster` que ya está en pantalla, llame a `$.ui.blit` con el `id` del panel como `requestId`, la `key` del `Raster`, el mismo tamaño y celdas nuevas. Para este ejemplo, eso es `$.ui.blit({ requestId: 'heat', key: 'grid', columns: 3, rows: 2, cells: cellsOf(newRows) })`. Repinta ese elemento sin ejecutar su hook `ui.render` nuevamente.
472
473<h2 id="respond-to-presses-and-typing">
474 Responder a pulsaciones y escritura
475</h2>
476
477Cuando el usuario presiona un botón, escribe en un campo o elige de una lista que su mod dibujó, Claude Code llama a la función que le dio a ese control, y se ejecuta en su módulo. Cada control toma sus propios callbacks:
478
479* **`Button`**: toma `onPress(e)`, donde `e.surface` es la aplicación de la que proviene la pulsación
480* **`Input`**: toma `onSubmit(value)` y `onInput(value)`
481* **`Select`**: toma `onSelect(value)` con sus opciones en `options`, una lista de al menos una opción con valores únicos, como `[{ value: 'sm', label: 'Small' }, { value: 'lg', label: 'Large' }]`
482
483Una prueba presiona o escribe en un control por su `key`, así que dé a cada control uno. Cada uso de un control también genera [`ui.press`, `ui.input` o `ui.select`](/docs/es/plugins/mods/reference#interface) con la `key` en `e.element`, y otro mod puede enganchar esos eventos. Su hook se ejecuta antes de su callback, por lo que ve lo que el usuario escribe en su `Input` y puede cambiarlo o responder en lugar de su callback. La API de mods no tiene método que presione el botón de otro mod.
484
485<h3 id="know-which-keys-your-mod-can-receive">
486 Enfoque del teclado y atajos de teclado
487</h3>
488
489Su mod nunca lee el teclado a sí mismo. El usuario presiona una tecla, Claude Code decide cuál de sus controles es para, y se ejecuta el callback de ese control. Aparte de un [atajo de teclado de dígito en la banda](/docs/es/plugins/mods/reference#elements), eso sucede solo mientras su panel o banda tiene enfoque del teclado. El resto del tiempo, las teclas van al símbolo del sistema.
490
491<h4 id="how-a-pane-gets-keyboard-focus">
492 Cómo un panel obtiene enfoque del teclado
493</h4>
494
495Un panel obtiene enfoque del teclado de una de tres maneras:
496
497* Su mod lo abre con `focus: true` desde un comando o una pulsación
498* El usuario presiona Ctrl+X y luego Tab
499* El usuario hace clic en él
500
501Claude Code otorga `focus: true` solo mientras el símbolo del sistema está vacío y nada más tiene enfoque del teclado. Un panel que se abre mientras el usuario está escribiendo no toma sus pulsaciones de teclas.
502
503<h4 id="what-each-key-does">
504 Qué hace cada tecla
505</h4>
506
507Esta tabla enumera qué hace una tecla mientras su panel o banda tiene enfoque del teclado:
508
509| Tecla | Qué hace |
510| :- | :- |
511| Tab | Se mueve al siguiente control |
512| Arriba y Abajo | Se mueven entre controles mientras su dibujo cabe. Cuando el panel o banda tiene más filas de las que puede mostrar, los desplazan. |
513| Enter | Presiona el `Button` enfocado, envía el `Input` enfocado o elige en un `Select` |
514| Atajo de teclado de un botón | Presiona ese botón. Mientras un `Input` tiene el enfoque, cada tecla imprimible va al campo. |
515| Esc | Devuelve el enfoque del teclado al símbolo del sistema. Con `closeOnEscape: true`, también cierra el panel. |
516
517Un mod no puede vincular Tab o las teclas de flecha a nada más, por lo que un juego se dirige con `w`, `a`, `s` y `d`.
518
519<h4 id="set-a-hotkey-and-the-first-focus">
520 Establecer un atajo de teclado y el primer enfoque
521</h4>
522
523Dos propiedades en un control deciden cómo el teclado lo alcanza:
524
525* **`hotkey`**: para permitir que el usuario presione un `Button` con una tecla, dé un `hotkey` de un dígito o una letra minúscula, como en `hotkey: 'a'`
526* **`autoFocus`**: para elegir qué control tiene el enfoque cuando se abre el panel, agregue `autoFocus: true` a él. Deje la propiedad fuera de los otros, porque Claude Code rechaza `autoFocus: false`.
527
528Cómo se muestra un atajo de teclado depende del botón y la aplicación:
529
530| Botón | En la terminal | En la aplicación de escritorio |
531| :- | :- | :- |
532| Con corchetes, el predeterminado | `[ Add one ]`, sin atajo de teclado mostrado | La etiqueta con una pequeña tecla al lado |
533| Con `plain: true` | `1: One` | La etiqueta con una pequeña tecla al lado |
534
535En la terminal, nombre la tecla en la etiqueta de un botón entre corchetes, o use `plain: true`, para que el usuario pueda ver qué presionar. La [referencia de elementos](/docs/es/plugins/mods/reference#elements) tiene las otras reglas de `Button`: `action`, atajos de teclado de dígitos en la banda y dos botones en un atajo de teclado.
536
537<h3 id="take-typed-input-and-draw-a-row-for-each-item">
538 Tomar entrada escrita y dibujar una fila para cada elemento
539</h3>
540
541Muchos paneles son un campo de texto con una lista debajo. El ejemplo en esta sección es un panel de notas: escribe una nota y presiona Enter para agregarla, y cada nota tiene un botón `x` que la elimina. Con dos notas agregadas, la terminal dibuja el panel de esta manera:
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
551El ejemplo utiliza dos técnicas:
552
553* **Tomar entrada escrita**: un `Input` llama a `onSubmit(value)` con el texto del campo cuando el usuario presiona Enter, y `onInput(value)` en cada cambio
554* **Dibujar una lista**: asigne sus datos a una fila cada uno, y dé a cada botón de fila su propia `key`
555
556Este hook dibuja el contenido del panel:
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
614Para probar el panel:
615
616* **Agregar una nota**: escriba una línea y presione Enter. La línea aparece como una nueva fila y el campo se vacía.
617* **Eliminar una nota**: presione Tab hasta que el botón `x` de la nota tenga el enfoque, luego presione Enter. La `x` es la etiqueta del botón y no un atajo de teclado, por lo que escribir la letra no lo presiona.
618
619Cada cambio sigue el mismo ciclo de renderizado que `hello-tabs`: el callback cambia `notes`, llama a `redraw` y guarda la lista en `$.store`.
620
621El campo se vacía después de cada envío debido a su propiedad `value`. `value` es el texto que el campo contiene cuando se dibuja, y la escritura del usuario lo reemplaza hasta que su hook dibuja el campo nuevamente. El ejemplo siempre dibuja el campo con `''`.
622
623El ejemplo guarda las notas y no las carga. Para traerlas de vuelta en la siguiente sesión, léalas en un hook `session.start`, de la manera que `hello-tabs` lee `count`.
624
625Tres propiedades componen la línea del campo, `Note: Type a note and press Enter ⏎ add`:
626
627| Propiedad | En el ejemplo | Qué es |
628| :- | :- | :- |
629| `label` | `Note` | El texto antes del campo. La terminal dibuja `: ` después. |
630| `placeholder` | `Type a note and press Enter` | Texto atenuado que se muestra mientras el campo está vacío |
631| `submitLabel` | `add` | La palabra después de `⏎` que dice qué hace Enter |
632
633Enviar un `Input` no inicia un turno a menos que su callback llame a [`$.prompt.submit`](/docs/es/plugins/mods/api#start-a-turn-from-a-background-job).
634
635<h2 id="redraw-when-something-changes">
636 Redibujar un sitio
637</h2>
638
639Un dibujo es una instantánea: muestra lo que devolvió su hook `ui.render` la última vez que se ejecutó. Para mostrar algo nuevo, el hook tiene que ejecutarse de nuevo. Claude Code lo ejecuta de nuevo para algunos cambios, y su mod solicita el resto.
640
641<h3 id="when-claude-code-redraws-without-being-asked">
642 Cuándo Claude Code redibuja sin ser solicitado
643</h3>
644
645Claude Code ejecuta su hook `ui.render` de nuevo cuando cambian los props del sitio o cambia el ancho de la terminal. No ejecuta el hook en un temporizador y no puede saber cuándo cambia una variable en su módulo.
646
647<h3 id="redraw-when-your-data-changes">
648 Redibujar cuando sus datos cambian
649</h3>
650
651Para que sus sitios se redibjen después de que sus propios datos cambien, llame a `$.ui.invalidate('ui.render')`. Este panel cuenta pulsaciones. La devolución de llamada del botón cambia `count` y luego solicita un redibujado:
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
678Cada pulsación aumenta el número en el panel. El ejemplo [`hello-tabs`](#build-a-pane-with-tabs) envuelve la misma llamada en su función `redraw`.
679
680Un valor que mantiene en [`$.state`](#keep-a-value-in-\$-state) no necesita la llamada, porque escribir el valor redibuja los sitios que lo leen.
681
682<h3 id="redraw-on-a-timer">
683 Redibujar en un temporizador
684</h3>
685
686Para mantener un reloj, una cuenta atrás o un valor de fuera de la sesión actual, redibuje según un cronograma. Inicie un temporizador en el hook `session.start` del módulo. Si el módulo ya tiene uno, como lo hace `hello-tabs`, agregue la línea [`$.clock.every`](/docs/es/plugins/mods/api#run-work-in-the-background) a él:
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 ahora ejecuta su hook `ui.render` una vez por segundo. El temporizador se detiene cuando el módulo se recarga, y la nueva copia del módulo inicia el suyo propio.
697
698<h3 id="how-often-a-site-can-redraw">
699 Con qué frecuencia se puede redibujar un sitio
700</h3>
701
702Claude Code limita la frecuencia con la que redibuja un sitio, por lo que su mod puede llamar a `$.ui.invalidate` con la frecuencia que cambien sus datos. El panel visible y la banda tienen un límite más alto que otros sitios, y la [tabla de límites](/docs/es/plugins/mods/reference#limits) tiene los números.
703
704Las llamadas que llegan más rápido que el límite se combinan en un redibujado. Ese redibujado ejecuta su hook una vez, y el hook lee sus datos tal como están en ese momento, por lo que se muestra el valor más reciente y los valores intermedios no se muestran. Una animación no puede ejecutarse más rápido que el límite.
705
706<h2 id="keep-state">
707 Mantener estado
708</h2>
709
710Un mod tiene tres lugares para mantener un valor, y difieren en cuánto tiempo dura el valor: hasta que el módulo se recarga, hasta que termina la sesión o de una sesión a la siguiente. Elija según cuánto tiempo tenga que durar el valor:
711
712| Mantenerlo en | Dura hasta | Úselo para |
713| :- | :- | :- |
714| Una variable a nivel de módulo | El módulo se recarga, lo que sucede cada vez que guarda un archivo durante el desarrollo | Valores que puede perder, como `tab` en `hello-tabs` |
715| `$.state` | La sesión termina, o el usuario ejecuta `/clear`, `/resume` o `/branch` | Valores de los que depende un dibujo que deben sobrevivir a una recarga |
716| `$.store` | Su mod lo elimina, o ninguna sesión lee o escribe el almacén durante [`cleanupPeriodDays`](/docs/es/settings-reference#cleanupperioddays). El almacén es un almacén de clave-valor, guardado como un archivo JSON de su propio plugin bajo `~/.claude/plugins/store/`. | Configuración, historial, cualquier cosa que el usuario espera encontrar la próxima vez |
717
718`$.store.get(key)` se resuelve en el valor o `undefined`, y `$.store.set(key, value)` toma cualquier valor JSON.
719
720<h3 id="keep-a-value-in-state">
721 Mantener un valor en `$.state`
722</h3>
723
724`$.state` mantiene valores durante la duración de una sesión, y se redibuja por usted. Es estado reactivo: un hook `ui.render` que lee un valor se suscribe a él, por lo que Claude Code redibuja ese sitio cada vez que escribe el valor, y no llama a `$.ui.invalidate`. Un valor en `$.state` también sobrevive a una recarga del módulo, lo que una variable no.
725
726Para configurarlo, declare sus valores, apunte su manifiesto a la declaración, luego defina y use cada valor. Los ejemplos mueven el `count` de `hello-tabs` a `$.state`.
727
728<h4 id="declare-the-values">
729 Declarar los valores
730</h4>
731
732Declare los valores en un archivo de tipos. La clave externa es el nombre de su plugin, y cada entrada bajo ella es un valor y su tipo. Guarde esto como `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 Apuntar el manifiesto a la declaración
747</h4>
748
749Para permitir que `claude plugin validate` verifique su código contra ese archivo, agregue un campo `types` al manifiesto con su ruta:
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 Definir, leer y escribir un valor
763</h4>
764
765En su módulo, defina cada valor con un predeterminado, léalo mientras dibuja y escríbalo desde un callback. `atom` nombra un valor y su predeterminado, `read` lo devuelve y `update` lo escribe. Los tres ayudantes llaman a `$.state.get` y `$.state.set` por usted:
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
780Porque el hook `ui.render` leyó `count`, Claude Code ejecuta el hook nuevamente cada vez que el botón lo escribe.
781
782Tres reglas se aplican al código:
783
784* **Escriba `plugin` y `key` como cadenas literales**: `claude plugin validate` las lee de su fuente
785* **Declare cada valor en el archivo de tipos**: de lo contrario, la validación falla con `hello-tabs.count is not declared`
786* **Escriba desde un callback u otro hook de evento**: un hook `ui.render` puede leer estado y no puede escribirlo, así que escriba desde `onPress`, `onSubmit` u otro hook de evento
787
788<h4 id="change-hello-tabs-to-use-state">
789 Cambiar `hello-tabs` para usar `$.state`
790</h4>
791
792Para mover `count` en `hello-tabs` a `$.state`, cambie cada línea que lo use:
793
794* **En la parte superior del módulo**: agregue la línea `import` y reemplace `let count = 0` con la línea `atom`
795* **En el hook `ui.render`**: agregue la línea `read` antes de `tabButton` y dibuje `'Count: ' + n` en el `Text`
796* **En el botón Add one**: reemplace `onPress` con el de [Guardar desde más de una sesión](#save-from-more-than-one-session), que guarda el recuento además de escribirlo
797* **En el hook `session.start`**: reemplace las dos líneas que leen `saved` con la llamada `loadCount` de [Cargar un valor guardado nuevamente después de `/clear`](#load-a-saved-value-again-after-clear)
798
799Mantenga `redraw` para los botones de pestaña, porque `tab` sigue siendo una variable.
800
801<h3 id="load-a-saved-value-again-after-clear">
802 Cargar un valor guardado nuevamente después de `/clear`
803</h3>
804
805Si su mod copia un valor guardado de `$.store` a `$.state` en `session.start`, tiene que copiarlo nuevamente después de `/clear`, `/resume` o `/branch`. Esos comandos devuelven cada valor de `$.state` a su predeterminado, y `session.start` no se dispara nuevamente. [`classic.SessionStart`](/docs/es/plugins/mods/events#hook-the-settings-hook-events) se dispara después de cada uno de ellos, con `e.source` establecido en `clear`, `resume` o `fork`, así que copie el valor nuevamente en un hook en él. De lo contrario, su dibujo muestra el predeterminado, y un callback que guarda el valor de `$.state` escribe el predeterminado sobre lo que almacenó.
806
807Este código carga `count` de ambos hooks. Se basa en la versión de `$.state` de `hello-tabs`, donde `count` es un átomo y `update` se importa. Ponga `loadCount` arriba de `register` y agregue la llamada `loadCount` al hook `session.start` que ya tiene. `classic.SessionStart` también se dispara al inicio y después de la compactación, que no reinicia `$.state`, por lo que el filtro en `source` mantiene el hook a los tres reiniciados:
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
829Con ambos hooks en su lugar, el panel muestra el recuento guardado después de `/clear` y no `0`, y la siguiente pulsación de **Add one** suma al recuento guardado.
830
831`loadCount` escribe el valor almacenado sobre el que está en `$.state`, y `session.start` se dispara nuevamente cada vez que el módulo se recarga. Para mantener el almacén actualizado, guarde en cada cambio, como lo hace el botón **Add one**.
832
833Para verificar la recarga sin una sesión, [pruebe el dibujo después de `/clear`](/docs/es/plugins/mods/test#test-a-drawing-after-clear).
834
835<h3 id="save-from-more-than-one-session">
836 Guardar desde más de una sesión
837</h3>
838
839Cada sesión en su máquina que ejecuta su mod comparte un `$.store`. Un `get` seguido de un `set` no es atómico. Cuando dos sesiones leen un valor, lo cambian y lo escriben de vuelta, compiten, y la segunda escritura reemplaza la primera.
840
841Dos opciones hacen que sea menos probable:
842
843* **Dé a cada elemento su propia clave**: un `set` cambia solo su propia clave, por lo que las sesiones que escriben claves diferentes no se sobrescriben entre sí
844* **Lea nuevamente justo antes de escribir**: para un valor que varias sesiones cambian, `get` la clave en el callback y construya el nuevo valor a partir de eso, no de una copia que cargó en `session.start`. La escritura de otra sesión se pierde si llega entre su `get` y su `set`.
845
846Este botón suma uno a lo que el almacén contiene ahora, luego actualiza el dibujo:
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 una segunda sesión ha presionado su propio botón tres veces desde que esta sesión comenzó, esta pulsación muestra y guarda un recuento que incluye esos tres.
859
860<h2 id="next-steps">
861 Próximos pasos
862</h2>
863
864* [Reaccionar a eventos](/docs/es/plugins/mods/events): alimente su dibujo desde llamadas de herramientas y turnos
865* [Usar la API de mods](/docs/es/plugins/mods/api): alimente su dibujo desde temporizadores y llamadas de modelo
866* [Probar un dibujo](/docs/es/plugins/mods/test#test-a-drawing): presione sus botones desde una prueba, en más de una superficie
867* [Sitios de renderizado](/docs/es/plugins/mods/reference#render-sites) y [elementos](/docs/es/plugins/mods/reference#elements): propiedades de cada sitio y propiedades de cada elemento