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 |
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
319`focus`, `closeOnEscape` y `holdToasts` son opcionales y aceptan solo `true`. Para omitir uno, déjelo fuera. Pasar `false` lanza un error como `ui.open: focus is true or left out`. Para establecer uno de ellos condicionalmente, agregue el campo solo cuando la condición se cumple. Esta llamada solicita enfoque del teclado solo cuando `items` no está vacío:
320
321```javascript theme={null}
322const pane = { id: 'hello-tabs', title: 'Hello tabs' }
323await $.ui.open(items.length > 0 ? { ...pane, focus: true } : pane)
324```
325
326Para 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.
327
328<h4 id="when-a-pane-waits-for-a-wider-terminal">
329 Cuando un panel espera una terminal más ancha
330</h4>
331
332Un 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ó:
333
334* **Abierto por algo que hizo el usuario**, como un comando que ejecutó o un botón que presionó, el panel aparece en cualquier ancho
335* **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.
336
337Cuando 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.
338
339<h2 id="build-a-tree-from-elements">
340 Construir un árbol a partir de elementos
341</h2>
342
343Lo 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.
344
345Para 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`.
346
347La mayoría de los dibujos utilizan cuatro elementos. Seleccione una pestaña para ver cada uno y cómo la terminal lo dibuja:
348
349<Tabs>
350 <Tab title="Texto">
351 `Text` dibuja una cadena, con estilo opcional como `bold` y `color`:
352
353 ```javascript theme={null}
354 Text({ children: ['This is the first tab.'] })
355 ```
356
357 ```text theme={null}
358 This is the first tab.
359 ```
360 </Tab>
361
362 <Tab title="Cuadro">
363 `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:
364
365 ```javascript theme={null}
366 Box({
367 flexDirection: 'row',
368 columnGap: 2,
369 children: [
370 Button({ key: 'more', label: 'Add one', onPress: addOne }),
371 Text({ children: ['Count: 0'] }),
372 ],
373 })
374 ```
375
376 ```text theme={null}
377 [ Add one ] Count: 0
378 ```
379 </Tab>
380
381 <Tab title="Botón">
382 `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:
383
384 ```javascript theme={null}
385 Button({ key: 'more', label: 'Add one', onPress: addOne })
386 Button({ key: 'tab-one', label: 'One', hotkey: '1', plain: true, onPress: showTabOne })
387 ```
388
389 ```text theme={null}
390 [ Add one ]
391 1: One
392 ```
393 </Tab>
394
395 <Tab title="Entrada">
396 `Input` es un campo de texto. Ejecuta su callback `onSubmit` con el texto cuando el usuario presiona Enter:
397
398 ```javascript theme={null}
399 Input({
400 key: 'new-note',
401 label: 'Note',
402 placeholder: 'Type a note and press Enter',
403 value: '',
404 submitLabel: 'add',
405 onSubmit: addNote,
406 })
407 ```
408
409 ```text theme={null}
410 Note: Type a note and press Enter ⏎ add
411 ```
412 </Tab>
413</Tabs>
414
415Esta tabla enumera cada elemento:
416
417| Elemento | Qué dibuja | Dónde |
418| :- | :- | :- |
419| `Box` | Un contenedor flex. Toma propiedades de diseño como `flexDirection`, `columnGap`, `padding`, `borderStyle` y `width`. | En todas partes |
420| `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 |
421| `Button` | Un control que llama a `onPress` | En todas partes |
422| `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 |
423| `Input`, `Select` | Un campo de texto y un selector | Terminal, Escritorio |
424| `Svg` | Un documento SVG | Escritorio |
425| `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 |
426| `Raster`, `Image` | Una [cuadrícula de celdas coloreadas](#draw-a-grid-of-colored-cells) y una imagen | Terminal |
427
428Si 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.
429
430Si 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.
431
432En 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.
433
434<h3 id="draw-a-grid-of-colored-cells">
435 Dibujar una cuadrícula de celdas coloreadas
436</h3>
437
438Para 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.
439
440La 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:
441
442```javascript theme={null}
443// The value that means "use the terminal's default color"
444const DEFAULT_COLOR = 0x01000000
445
446// Pack rows of [character, color] pairs into the one string a Raster takes
447// One cell is three numbers: the character's code point, its color, and its background
448function cellsOf(rows) {
449 const numbers = rows.flat().flatMap(([char, color]) => [char.codePointAt(0), color, DEFAULT_COLOR])
450 return new Uint8Array(Uint32Array.from(numbers).buffer).toBase64()
451}
452
453on('ui.render', { component: 'Pane' }, async ($, e, next) => {
454 // Draw only in the pane opened with the id 'heat'
455 if (e.requestId !== 'heat') return next(e)
456 const { Box, Text, Raster } = $.ui.resolve(e)
457 // Two rows of three cells, each a block character and its color
458 const rows = [
459 [['█', 0x2e7d32], ['█', 0xf9a825], ['█', 0xc62828]],
460 [['█', 0x2e7d32], ['█', 0x2e7d32], ['█', 0xf9a825]],
461 ]
462 if (e.surface !== 'terminal') {
463 return Text({ children: ['The heat map needs the terminal.'] })
464 }
465 return Box({
466 flexDirection: 'column',
467 children: [Raster({ key: 'grid', columns: 3, rows: 2, cells: cellsOf(rows) })],
468 })
469})
470```
471
472En la terminal, el panel muestra la cuadrícula:
473
474<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" />
475
476La 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.
477
478Cada 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.
479
480<h2 id="respond-to-presses-and-typing">
481 Responder a pulsaciones y escritura
482</h2>
483
484Cuando 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:
485
486* **`Button`**: toma `onPress(e)`, donde `e.surface` es la aplicación de la que proviene la pulsación
487* **`Input`**: toma `onSubmit(value)` y `onInput(value)`
488* **`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' }]`
489
490Una 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.
491
492<h3 id="know-which-keys-your-mod-can-receive">
493 Enfoque del teclado y atajos de teclado
494</h3>
495
496Su 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.
497
498<h4 id="how-a-pane-gets-keyboard-focus">
499 Cómo un panel obtiene enfoque del teclado
500</h4>
501
502Un panel obtiene enfoque del teclado de una de tres maneras:
503
504* Su mod lo abre con `focus: true` desde un comando o una pulsación
505* El usuario presiona Ctrl+X y luego Tab
506* El usuario hace clic en él
507
508Claude 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.
509
510<h4 id="what-each-key-does">
511 Qué hace cada tecla
512</h4>
513
514Esta tabla enumera qué hace una tecla mientras su panel o banda tiene enfoque del teclado:
515
516| Tecla | Qué hace |
517| :- | :- |
518| Tab | Se mueve al siguiente control |
519| 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. |
520| Enter | Presiona el `Button` enfocado, envía el `Input` enfocado o elige en un `Select` |
521| Atajo de teclado de un botón | Presiona ese botón. Mientras un `Input` tiene el enfoque, cada tecla imprimible va al campo. |
522| Esc | Devuelve el enfoque del teclado al símbolo del sistema. Con `closeOnEscape: true`, también cierra el panel. |
523
524Un 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`.
525
526<h4 id="set-a-hotkey-and-the-first-focus">
527 Establecer un atajo de teclado y el primer enfoque
528</h4>
529
530Dos propiedades en un control deciden cómo el teclado lo alcanza:
531
532* **`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'`
533* **`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`.
534
535Cómo se muestra un atajo de teclado depende del botón y la aplicación:
536
537| Botón | En la terminal | En la aplicación de escritorio |
538| :- | :- | :- |
539| Con corchetes, el predeterminado | `[ Add one ]`, sin atajo de teclado mostrado | La etiqueta con una pequeña tecla al lado |
540| Con `plain: true` | `1: One` | La etiqueta con una pequeña tecla al lado |
541
542En 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.
543
544<h3 id="take-typed-input-and-draw-a-row-for-each-item">
545 Tomar entrada escrita y dibujar una fila para cada elemento
546</h3>
547
548Muchos 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:
549
550```text theme={null}
551╭──────────────────────────────────────────────────────────╮
552│ Note: Type a note and press Enter ⏎ add ✕ │
553│ x buy milk │
554│ x call bob │
555╰──────────────────────────────────────────────────────────╯
556```
557
558El ejemplo utiliza dos técnicas:
559
560* **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
561* **Dibujar una lista**: asigne sus datos a una fila cada uno, y dé a cada botón de fila su propia `key`
562
563Este hook dibuja el contenido del panel:
564
565```javascript theme={null}
566// The list the pane draws
567let notes = []
568
569on('ui.render', { component: 'Pane' }, async ($, e, next) => {
570 // Draw only in the pane opened with the id 'notes'
571 if (e.requestId !== 'notes') return next(e)
572 const { Box, Text, Button, Input } = $.ui.resolve(e)
573 const redraw = () => $.ui.invalidate('ui.render')
574
575 return Box({
576 flexDirection: 'column',
577 children: [
578 Input({
579 key: 'new-note',
580 label: 'Note',
581 placeholder: 'Type a note and press Enter',
582 // Draw the field empty each time, which clears it after a submit
583 value: '',
584 submitLabel: 'add',
585 autoFocus: true,
586 // Runs when you press Enter in the field
587 onSubmit: async (value) => {
588 // Ignore an empty line
589 if (!value.trim()) return
590 notes = [...notes, value.trim()]
591 redraw()
592 await $.store.set('notes', notes)
593 },
594 }),
595 // One row for each note: a delete button, then the note's text
596 ...notes.map((note, i) =>
597 Box({
598 flexDirection: 'row',
599 columnGap: 1,
600 children: [
601 Button({
602 // A key of its own, so each row's button can be told apart
603 key: 'delete-' + i,
604 label: 'x',
605 plain: true,
606 onPress: async () => {
607 notes = notes.filter((_, j) => j !== i)
608 redraw()
609 await $.store.set('notes', notes)
610 },
611 }),
612 Text({ children: [note] }),
613 ],
614 }),
615 ),
616 ],
617 })
618})
619```
620
621Para probar el panel:
622
623* **Agregar una nota**: escriba una línea y presione Enter. La línea aparece como una nueva fila y el campo se vacía.
624* **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.
625
626Cada cambio sigue el mismo ciclo de renderizado que `hello-tabs`: el callback cambia `notes`, llama a `redraw` y guarda la lista en `$.store`.
627
628El 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 `''`.
629
630El 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`.
631
632Tres propiedades componen la línea del campo, `Note: Type a note and press Enter ⏎ add`:
633
634| Propiedad | En el ejemplo | Qué es |
635| :- | :- | :- |
636| `label` | `Note` | El texto antes del campo. La terminal dibuja `: ` después. |
637| `placeholder` | `Type a note and press Enter` | Texto atenuado que se muestra mientras el campo está vacío |
638| `submitLabel` | `add` | La palabra después de `⏎` que dice qué hace Enter |
639
640Enviar 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).
641
642<h2 id="redraw-when-something-changes">
643 Redibujar un sitio
644</h2>
645
646Un 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.
647
648<h3 id="when-claude-code-redraws-without-being-asked">
649 Cuándo Claude Code redibuja sin ser solicitado
650</h3>
651
652Claude 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.
653
654<h3 id="redraw-when-your-data-changes">
655 Redibujar cuando sus datos cambian
656</h3>
657
658Para 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:
659
660```javascript theme={null}
661let count = 0
662
663on('ui.render', { component: 'Pane' }, async ($, e, next) => {
664 if (e.requestId !== 'counter') return next(e)
665 const { Box, Text, Button } = $.ui.resolve(e)
666 return Box({
667 flexDirection: 'row',
668 columnGap: 2,
669 children: [
670 Button({
671 key: 'more',
672 label: 'Add one',
673 onPress: () => {
674 count += 1
675 // The data changed, so ask Claude Code to draw the pane again
676 $.ui.invalidate('ui.render')
677 },
678 }),
679 Text({ children: ['Count: ' + count] }),
680 ],
681 })
682})
683```
684
685Cada 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`.
686
687Un valor que mantiene en [`$.state`](#keep-a-value-in-\$-state) no necesita la llamada, porque escribir el valor redibuja los sitios que lo leen.
688
689<h3 id="redraw-on-a-timer">
690 Redibujar en un temporizador
691</h3>
692
693Para 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:
694
695```javascript theme={null}
696on('session.start', async ($, e, next) => {
697 // Every 1000 milliseconds, ask Claude Code to draw your sites again
698 $.clock.every(1000, () => $.ui.invalidate('ui.render'))
699 return next(e)
700})
701```
702
703Claude 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.
704
705<h3 id="how-often-a-site-can-redraw">
706 Con qué frecuencia se puede redibujar un sitio
707</h3>
708
709Claude 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.
710
711Las 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.
712
713<h2 id="keep-state">
714 Mantener estado
715</h2>
716
717Un 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:
718
719| Mantenerlo en | Dura hasta | Úselo para |
720| :- | :- | :- |
721| 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` |
722| `$.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 |
723| `$.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 |
724
725`$.store.get(key)` se resuelve en el valor o `undefined`, y `$.store.set(key, value)` toma cualquier valor JSON.
726
727<h3 id="keep-a-value-in-state">
728 Mantener un valor en `$.state`
729</h3>
730
731`$.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.
732
733Para 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`.
734
735<h4 id="declare-the-values">
736 Declarar los valores
737</h4>
738
739Declare 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`:
740
741```typescript hello-tabs/types/index.d.ts theme={null}
742declare module 'claude-code' {
743 interface PluginState {
744 'hello-tabs': {
745 tab: 'one' | 'two'
746 count: number
747 }
748 }
749}
750```
751
752<h4 id="point-the-manifest-at-the-declaration">
753 Apuntar el manifiesto a la declaración
754</h4>
755
756Para permitir que `claude plugin validate` verifique su código contra ese archivo, agregue un campo `types` al manifiesto con su ruta:
757
758```json hello-tabs/.claude-plugin/plugin.json theme={null}
759{
760 "name": "hello-tabs",
761 "version": "0.1.0",
762 "description": "Opens a pane with two tabs and a counter",
763 "author": { "name": "Your Name" },
764 "types": "./types/index.d.ts"
765}
766```
767
768<h4 id="define-read-and-write-a-value">
769 Definir, leer y escribir un valor
770</h4>
771
772En 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:
773
774```javascript theme={null}
775import { atom, read, update } from 'claude-code'
776
777// At the top of the module: name the value and give its default
778const count = atom({ plugin: 'hello-tabs', key: 'count' }, 0)
779
780// In the ui.render hook: read the value to draw it
781const n = await read($, count)
782
783// In a Button: write a new value from the old one
784onPress: () => update($, count, (value) => value + 1)
785```
786
787Porque el hook `ui.render` leyó `count`, Claude Code ejecuta el hook nuevamente cada vez que el botón lo escribe.
788
789Tres reglas se aplican al código:
790
791* **Escriba `plugin` y `key` como cadenas literales**: `claude plugin validate` las lee de su fuente
792* **Declare cada valor en el archivo de tipos**: de lo contrario, la validación falla con `hello-tabs.count is not declared`
793* **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
794
795<h4 id="change-hello-tabs-to-use-state">
796 Cambiar `hello-tabs` para usar `$.state`
797</h4>
798
799Para mover `count` en `hello-tabs` a `$.state`, cambie cada línea que lo use:
800
801* **En la parte superior del módulo**: agregue la línea `import` y reemplace `let count = 0` con la línea `atom`
802* **En el hook `ui.render`**: agregue la línea `read` antes de `tabButton` y dibuje `'Count: ' + n` en el `Text`
803* **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
804* **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)
805
806Mantenga `redraw` para los botones de pestaña, porque `tab` sigue siendo una variable.
807
808<h3 id="load-a-saved-value-again-after-clear">
809 Cargar un valor guardado nuevamente después de `/clear`
810</h3>
811
812Si 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ó.
813
814Este 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:
815
816```javascript theme={null}
817// Copy the saved count from $.store into $.state, or 0 if nothing is saved
818async function loadCount($) {
819 const saved = Number((await $.store.get('count')) ?? 0)
820 await update($, count, () => saved)
821}
822
823// Runs before your first prompt, and again after a reload
824on('session.start', async ($, e, next) => {
825 await loadCount($)
826 return next(e)
827})
828
829// Runs again after /clear, /resume, and /branch, which reports fork
830on('classic.SessionStart', { source: ['clear', 'resume', 'fork'] }, async ($, e, next) => {
831 await loadCount($)
832 return next(e)
833})
834```
835
836Con 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.
837
838`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**.
839
840Para verificar la recarga sin una sesión, [pruebe el dibujo después de `/clear`](/docs/es/plugins/mods/test#test-a-drawing-after-clear).
841
842<h3 id="save-from-more-than-one-session">
843 Guardar desde más de una sesión
844</h3>
845
846Cada 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.
847
848Dos opciones hacen que sea menos probable:
849
850* **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í
851* **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`.
852
853Este botón suma uno a lo que el almacén contiene ahora, luego actualiza el dibujo:
854
855```javascript theme={null}
856onPress: async () => {
857 // Read what the store holds now, which another session may have changed
858 const saved = Number((await $.store.get('count')) ?? 0)
859 // Save the new count, then show it
860 await $.store.set('count', saved + 1)
861 await update($, count, () => saved + 1)
862}
863```
864
865Si 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.
866
867<h2 id="next-steps">
868 Próximos pasos
869</h2>
870
871* [Reaccionar a eventos](/docs/es/plugins/mods/events): alimente su dibujo desde llamadas de herramientas y turnos
872* [Usar la API de mods](/docs/es/plugins/mods/api): alimente su dibujo desde temporizadores y llamadas de modelo
873* [Probar un dibujo](/docs/es/plugins/mods/test#test-a-drawing): presione sus botones desde una prueba, en más de una superficie
874* [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