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

# Desenhar na interface com um mod

> Desenhe painéis, uma faixa acima do prompt, botões e campos de texto a partir de um mod Claude Code, manipule pressionamentos e entrada, e mantenha o estado entre redesenhos e sessões.

Um mod pode desenhar sua própria interface no Claude Code e alterar partes da interface que o Claude Code já desenha. Cada lugar onde um mod pode desenhar é chamado de [site de renderização](/docs/pt/plugins/mods/reference#render-sites), como um painel, a faixa acima do prompt ou o spinner. O Claude Code dispara o evento [`ui.render`](/docs/pt/plugins/mods/reference#interface) cada vez que está prestes a desenhar um site de renderização, e seu hook para esse evento retorna o que desenhar lá.

Este mapa mostra onde um mod pode desenhar em uma sessão de terminal:

<img src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-screen-map.svg?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=5fda26b6609c62b68c6f9e528c1590ea" className="dark:hidden" alt="Mapa de uma sessão de terminal Claude Code. Um mod pode adicionar um painel como uma barra lateral à direita, um toast no canto superior direito da transcrição, uma linha de log na transcrição, uma faixa acima do prompt e uma linha de status sob o prompt. Um mod pode redesenhar mensagens, linhas de chamada de ferramenta e o spinner. O prompt é do próprio Claude Code." width="600" height="336" data-path="images/mods-screen-map.svg" />

<img src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-screen-map-dark.svg?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=5b4161581a1bd2c0450b0c8b57bc1225" className="hidden dark:block" alt="Mapa de uma sessão de terminal Claude Code. Um mod pode adicionar um painel como uma barra lateral à direita, um toast no canto superior direito da transcrição, uma linha de log na transcrição, uma faixa acima do prompt e uma linha de status sob o prompt. Um mod pode redesenhar mensagens, linhas de chamada de ferramenta e o spinner. O prompt é do próprio Claude Code." width="600" height="336" data-path="images/mods-screen-map-dark.svg" />

Em um terminal mais estreito, o painel fica acima do prompt em vez de ao lado da transcrição.

Construa seu [primeiro mod](/docs/pt/plugins/mods/create) antes de começar aqui. Comece com o exemplo trabalhado, que constrói um painel com duas abas e um contador, depois leia a seção para cada parte que você deseja alterar.

<Note>
  Para procurar uma propriedade ou limite, consulte a [referência](/docs/pt/plugins/mods/reference#render-sites).
</Note>

<h2 id="build-a-pane-with-tabs">
  Construir um painel com abas
</h2>

Nesta seção você constrói um mod que adiciona um comando `/hello-tabs` e o comando abre um painel. Um painel é uma barra lateral ao lado da transcrição em um terminal fullscreen amplo, ou uma região enquadrada acima do prompt caso contrário. Este painel mostra duas abas, e a segunda aba tem um botão que adiciona um ao contador. A contagem ainda está lá depois que você reinicia o Claude Code.

O mod finalizado se parece com isto. A gravação abre o painel, muda para a segunda aba, pressiona o botão algumas vezes e retorna à primeira aba:

<Frame>
  <video autoPlay muted loop playsInline controls className="w-full dark:hidden" src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-hello-tabs-light.mp4?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=49d520094d87b5b44bfe50fa49677f06" aria-label="O comando /hello-tabs é digitado no prompt Claude Code e um painel enquadrado abre acima dele, com '1: One' e '2: Two' na parte superior e o texto 'This is the first tab.' A segunda aba mostra um botão 'Add one' ao lado de 'Count: 1', e a contagem sobe para 3. O painel então retorna à primeira aba." data-path="images/mods-hello-tabs-light.mp4" />

  <video autoPlay muted loop playsInline controls className="w-full hidden dark:block" src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-hello-tabs-dark.mp4?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=ff7a14d713d6e5d3b0000efa8522ea4b" aria-label="O comando /hello-tabs é digitado no prompt Claude Code e um painel enquadrado abre acima dele, com '1: One' e '2: Two' na parte superior e o texto 'This is the first tab.' A segunda aba mostra um botão 'Add one' ao lado de 'Count: 1', e a contagem sobe para 3. O painel então retorna à primeira aba." data-path="images/mods-hello-tabs-dark.mp4" />
</Frame>

O Claude Code não tem um elemento de abas integrado, então as abas são dois botões em uma linha. O mod acompanha qual está ativo e desenha o conteúdo dessa aba sob a linha.

<Steps>
  <Step title="Criar o plugin">
    Um mod é um plugin com um manifesto, um `hooks.json` que aponta para seu código, e o arquivo de código. [Criar um mod](/docs/pt/plugins/mods/create#write-a-mod-yourself) explica cada um. Crie um diretório chamado `hello-tabs` com diretórios `.claude-plugin` e `hooks` dentro dele, depois salve os dois primeiros arquivos.

    Salve o manifesto como `hello-tabs/.claude-plugin/plugin.json`:

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

    Nomeie seu ponto de entrada em `hello-tabs/hooks/hooks.json`:

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

  <Step title="Escrever o código">
    O código faz três trabalhos, um em cada hook:

    * Adiciona o comando `/hello-tabs`
    * Abre o painel quando você executa esse comando
    * Desenha o conteúdo do painel: a linha de abas e o corpo da aba aberta

    Duas variáveis no nível do módulo, `tab` e `count`, mantêm o estado do painel.

    Salve isto como `hello-tabs/hooks/register.js`:

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

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

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

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

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

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

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

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

    Cada hook também faz algo que o código não deixa claro:

    * **[`session.start`](/docs/pt/plugins/mods/reference#session)** também lê a contagem salva de [`$.store`](#keep-state), um armazenamento de chave-valor que persiste entre sessões.
    * **[`command.run`](/docs/pt/plugins/mods/api#add-a-command)** apenas diz ao Claude Code que o painel existe. Abrir um painel não desenha nada por si só: o Claude Code então dispara `ui.render` para perguntar o que colocar nele.
    * **`ui.render`** retorna a árvore de elementos, uma `Box` que contém outras caixas, texto e botões, e a constrói novamente a partir de `tab` e `count` cada vez que é executada.

    Pressionar um botão executa seu callback `onPress`, que altera uma variável e chama `redraw`. O Claude Code então executa o hook `ui.render` novamente, e o hook constrói uma nova árvore a partir dos novos valores. Cada desenho interativo usa esse ciclo de renderização: um callback altera o estado e o hook renderiza novamente a partir do novo estado.
  </Step>

  <Step title="Abrir o painel">
    Em seu shell, inicie o Claude Code com `claude --plugin-dir ./hello-tabs`. No prompt Claude Code, execute `/hello-tabs`. Um painel abre com `1: One` e `2: Two` na parte superior. Pressione `2`, depois pressione `a`, o atalho de teclado para **Add one**, algumas vezes. A contagem sobe.
  </Step>

  <Step title="Verificar se a contagem foi salva">
    Pressione Esc para fechar o painel, depois saia da sessão. Em seu shell, inicie o Claude Code novamente com o mesmo comando `claude --plugin-dir ./hello-tabs` e no prompt Claude Code execute `/hello-tabs`. A contagem está onde você a deixou.

    Para limpar a contagem, faça o mod chamar `$.store.delete('count')`. [Manter estado](#keep-state) cobre quanto tempo cada tipo de valor dura.
  </Step>
</Steps>

<h2 id="pick-where-to-draw">
  Escolher onde desenhar
</h2>

Um hook `ui.render` é executado para cada site de renderização a menos que você o restrinja ao que deseja desenhar. Para escolher o site de renderização, passe um filtro, chamado de [matcher](/docs/pt/plugins/mods/events#filter-which-events-a-hook-handles), como o segundo argumento para `on`. `{ component: 'Pane' }` executa o hook apenas para painéis. No hook, `e.component` nomeia o site, `e.surface` diz qual app está desenhando, e `e.props` contém os dados do próprio site. Para um painel, `e.requestId` é o `id` que você abriu com.

Dois sites estão vazios até um mod preenchê-los, o painel e a faixa. Selecione uma aba para ver o que cada um é e como desenhar nele:

<Tabs>
  <Tab title="Pane">
    Um painel é uma barra lateral ao lado da transcrição em um terminal fullscreen amplo, ou uma região enquadrada acima do prompt caso contrário. Com vários painéis abertos, cada um recebe uma aba que mostra seu título.

    Um painel aparece quando seu mod chama `$.ui.open` com um `id` que você escolhe, como em `$.ui.open({ id: 'hello-tabs' })`. [Abrir um painel no momento certo](#open-a-pane-at-the-right-time) cobre os outros campos e quando um painel espera por um terminal mais amplo.

    Para desenhar em seu painel, filtre em `{ component: 'Pane' }` e verifique se `e.requestId` é seu `id`.
  </Tab>

  <Tab title="Band above the prompt">
    A faixa é uma tira diretamente acima da entrada do prompt. Ela está sempre lá, e cada mod a compartilha.

    Seu hook retorna uma árvore para mostrar algo na faixa, ou `next(e)` para não mostrar nada. Uma árvore substitui o que os mods [depois do seu](/docs/pt/plugins/mods/events#the-order-mods-run-in) desenham lá. Para manter o deles, coloque o resultado de `await next(e)` entre os filhos de uma [`Box`](#build-a-tree-from-elements) em sua árvore.

    Para desenhar na faixa, filtre em `{ component: 'AbovePrompt' }`.
  </Tab>
</Tabs>

<h3 id="change-what-claude-code-already-draws">
  Alterar o que o Claude Code já desenha
</h3>

O Claude Code desenha a maior parte de sua interface por si só: mensagens, linhas de chamada de ferramenta, o spinner e muito mais. Cada uma dessas partes é um site de renderização também, então um mod pode restylar ou substituí-la. Para alterar uma, filtre seu hook `ui.render` em seu nome desta tabela:

| Site | O que é |
| :- | :- |
| `UserMessage`, `AssistantMessage` | Uma mensagem na transcrição |
| `ToolUse`, `ToolResult`, `ToolGroup` | A linha de uma chamada de ferramenta, seu resultado e uma execução dobrada de chamadas |
| `CommandOutput` | A linha que um comando imprimiu |
| `AskUserQuestion` | O diálogo que o Claude abre para fazer uma pergunta a você |
| `Spinner`, `ToolProgress`, `TurnDuration` | Linhas de status para uma volta: a linha que anima enquanto o Claude trabalha, a linha de progresso ao vivo de uma ferramenta em execução e a linha que fecha uma volta |
| `InfoNotice`, `SessionMode`, `PromptHint` | Linhas de status sob o logo, os rótulos de modo no rodapé e a linha de dica sob o prompt |

Em um site que o Claude Code já desenha, seu hook tem três escolhas: alterar um detalhe, substituir o desenho ou deixá-lo em paz. Selecione uma aba para ver cada um aplicado ao spinner. Os exemplos leem uma variável `calls` que outro hook conta, como no [mod do tutorial](/docs/pt/plugins/mods/create#write-a-mod-yourself).

<Tabs>
  <Tab title="Change a detail">
    Para manter o desenho do Claude Code e alterar uma parte dele, passe para `next` uma cópia do evento com `props` alteradas. Este hook altera o texto após a palavra do spinner:

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

    O spinner mantém sua animação e sua palavra, e seu texto segue a palavra:

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

  <Tab title="Replace the drawing">
    Para desenhar algo do seu próprio no lugar do site, retorne uma árvore e não chame `next`. Este hook desenha uma linha de texto onde o spinner seria:

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

    Enquanto o Claude trabalha, sua linha mostra e o spinner do Claude Code não:

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

  <Tab title="Leave it alone">
    Para deixar o site como o Claude Code o desenha, retorne `next(e)`. Um hook frequentemente faz isso para alguns eventos e não para outros. Este hook deixa o spinner em paz até haver uma chamada para contar:

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

    Antes da primeira chamada de ferramenta, o spinner se parece com a forma como é sem o mod:

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

O prompt de permissão não é um site de renderização, então um mod não pode alterar o que mostra. O diálogo de pergunta, `AskUserQuestion`, é um, então um mod pode alterar isso.

O terminal e o app Desktop não disparam todos os mesmos sites. `Pane`, `AbovePrompt`, `Spinner` e os sites de transcrição funcionam em ambos. Algumas outras linhas de status são disparadas apenas no terminal. A [tabela de sites de renderização](/docs/pt/plugins/mods/reference#render-sites) lista onde cada um é disparado.

<h3 id="open-a-pane-at-the-right-time">
  Abrir um painel no momento certo
</h3>

Um painel aparece apenas quando seu mod o abre. Como e quando você o abre decide se ele toma o foco do teclado, quanto espaço ele pede e se aparece em um terminal estreito.

Para abrir um painel, chame [`$.ui.open`](/docs/pt/plugins/mods/reference#mods-api-methods) com um `id` que você escolhe. O `id` é o nome do painel: seu hook `ui.render` verifica, e você o passa novamente para fechar o painel.

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

Para fechar o painel, chame `$.ui.close` com o `id` que você abriu com:

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

Além de `id`, `$.ui.open` leva estes campos opcionais:

| Campo | O que faz |
| :- | :- |
| `title` | O rótulo da aba do painel quando mais de um painel está aberto |
| `focus` | Solicita [foco do teclado](#know-which-keys-your-mod-can-receive) |
| `closeOnEscape` | Faz Esc fechar o painel. Passe `true` ou deixe o campo de fora, porque o Claude Code recusa `false`. |
| `holdToasts` | Mantém toasts, os pequenos avisos de [`$.ui.toast`](/docs/pt/plugins/mods/api#show-something-without-starting-a-turn), até o painel fechar |
| `rows` | A altura para pedir quando o painel fica acima do prompt. O padrão é um terço do espaço. |
| `columns` | A largura para pedir quando o painel fica ao lado da transcrição |

Para deixar um comando abrir o painel enquanto o Claude está trabalhando, adicione `immediate: true` quando você [registra o comando](/docs/pt/plugins/mods/api#add-a-command). Sem isso, um comando digitado durante uma volta espera a volta terminar.

<h4 id="when-a-pane-waits-for-a-wider-terminal">
  Quando um painel espera por um terminal mais amplo
</h4>

Um painel que seu mod abre sem ser solicitado não aparece em um terminal estreito, então não pode assumir uma tela pequena. Se aparece depende do que o abriu:

* **Aberto por algo que o usuário fez**, como um comando que executou ou um botão que pressionou, o painel aparece em qualquer largura
* **Aberto por seu mod agindo por si só**, como de um timer ou um hook [`turn.start`](/docs/pt/plugins/mods/events#follow-a-turn), o painel aparece apenas em um terminal com pelo menos 144 colunas de largura. Depois que o usuário abriu esse painel uma vez por si só, 110 colunas é suficiente.

Quando o painel aparece, `$.ui.open` resolve para `{ isPlaced: true }`. Quando o painel está esperando, `isPlaced` é `false` e `reason` é uma string que diz por quê. Um painel esperando aparece quando o usuário o abre ou amplia o terminal. Para dizer que algo está disponível sem abrir um painel, chame `$.ui.toast('Your message')`, que mostra um pequeno aviso que desaparece após alguns segundos.

<h2 id="build-a-tree-from-elements">
  Construir uma árvore a partir de elementos
</h2>

O que um hook `ui.render` retorna é uma árvore de elementos: uma descrição do que desenhar, feita de caixas, texto e controles aninhados um dentro do outro. Você descreve o desenho, e o Claude Code o renderiza no terminal ou no app Desktop.

Para obter os elementos, chame `$.ui.resolve(e)` em seu hook, como em `const { Box, Text, Button } = $.ui.resolve(e)`. Cada elemento é uma função. Você passa propriedades para ela, e coloca os elementos e strings que vão dentro dela em `children`.

A maioria dos desenhos usa quatro elementos. Selecione uma aba para ver cada um e como o terminal o desenha:

<Tabs>
  <Tab title="Text">
    `Text` desenha uma string, com estilo opcional como `bold` e `color`:

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

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

  <Tab title="Box">
    `Box` organiza o que está dentro dela, em uma linha ou uma coluna. Esta coloca um botão e uma linha de texto lado a lado, duas colunas separadas:

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

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

  <Tab title="Button">
    `Button` é um controle que o usuário pode pressionar. Ele executa seu callback `onPress`. Com `plain: true` não tem colchetes e mostra seu atalho de teclado:

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

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

  <Tab title="Input">
    `Input` é um campo de texto. Ele executa seu callback `onSubmit` com o texto quando o usuário pressiona Enter:

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

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

Esta tabela lista cada elemento:

| Elemento | O que desenha | Onde |
| :- | :- | :- |
| `Box` | Um contêiner flex. Leva propriedades de layout como `flexDirection`, `columnGap`, `padding`, `borderStyle` e `width`. | Em todos os lugares |
| `Text` | Texto estilizado. Leva `color`, `bold`, `dimColor`, `italic` e `wrap`. Uma `color` é uma chave de tema ou uma cor como `'red'`. Um `wrap` é `'wrap'`, `'truncate'`, `'truncate-start'`, `'truncate-middle'` ou `'truncate-end'`. | Em todos os lugares |
| `Button` | Um controle que chama `onPress` | Em todos os lugares |
| `Link`, `Code`, `Markdown` | Um link com `href` e um `label` opcional, um bloco de código e texto formatado da forma que as respostas do Claude são. `Markdown` leva seu conteúdo em uma propriedade `text`, não em `children`, e precisa de uma `key` quando você passa `onLinkPress`. | Em todos os lugares |
| `Input`, `Select` | Um campo de texto e um seletor | Terminal, Desktop |
| `Svg` | Um documento SVG | Desktop |
| `Client` | Uma região desenhada por um segundo arquivo seu, para animação e entrada de ponteiro. Esse arquivo não recebe API de mods. Ele alcança seus hooks apenas postando dados, que chegam como um evento `ui.message`. | Terminal, Desktop |
| `Raster`, `Image` | Uma [grade de células coloridas](#draw-a-grid-of-colored-cells) e uma imagem | Terminal |

Se seu módulo é um arquivo `.tsx` ou `.jsx`, você pode escrever a árvore como JSX. Desestruture os elementos de `$.ui.resolve(e)` primeiro, porque um módulo de hooks não tem globais de elementos.

Se uma árvore usa um elemento que o app não tem, uma propriedade que um elemento não leva, ou um filho onde nenhum vai, o Claude Code desenha sua própria versão do site.

Em uma sessão iniciada com `--plugin-dir`, uma linha de transcrição diz assim, como `ui.render (Pane) refused: Text prop "bogusProp" is not allowed; the engine drew its own`. O [log de depuração](/docs/pt/plugins/mods/troubleshoot#read-the-debug-log) registra como `ui.render (Pane): a hook returned a tree that does not validate` com a mesma razão. Nada mais aparece na sessão, então quando um desenho não aparece, verifique essa linha ou o log.

<h3 id="draw-a-grid-of-colored-cells">
  Desenhar uma grade de células coloridas
</h3>

Para um mapa de calor, um sparkline ou um tabuleiro de jogo no terminal, desenhe um `Raster` e não uma `Box` para cada célula. Um `Raster` leva uma `key`, seu tamanho em `columns` e `rows`, e `cells`, que empacota cada célula em uma string. Cada célula é três números: o ponto de código do caractere, sua cor e sua cor de fundo. Uma cor é um número hexadecimal com dois dígitos cada para vermelho, verde e azul, como `0xc62828` para um vermelho, ou `0x01000000` para o padrão do terminal.

O app Desktop não tem `Raster`, então verifique `e.surface` e desenhe texto lá. Este corpo de painel desenha um mapa de calor de três por dois:

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

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

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

No terminal, o painel mostra a grade:

<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="Um painel no terminal que contém uma pequena grade de blocos coloridos, duas linhas de três. A linha superior é verde, âmbar e vermelho. A linha inferior é verde, verde e âmbar." width="360" height="132" data-path="images/mods-heat-map.svg" />

O array `rows` é a parte que você alteraria, e `cellsOf` a transforma na string empacotada. O hook desenha apenas em um painel cujo `id` é `heat`, então abra um com `$.ui.open({ id: 'heat' })` de um comando, como o exemplo [`hello-tabs`](#build-a-pane-with-tabs) abre seu painel.

Cada caractere tem que ser uma célula de largura. Para animar um `Raster` que já está na tela, chame `$.ui.blit` com o `id` do painel como `requestId`, a `key` do `Raster`, o mesmo tamanho e novas células. Para este exemplo, é `$.ui.blit({ requestId: 'heat', key: 'grid', columns: 3, rows: 2, cells: cellsOf(newRows) })`. Ele repinta apenas esse elemento sem executar seu hook `ui.render` novamente.

<h2 id="respond-to-presses-and-typing">
  Responder a pressionamentos e digitação
</h2>

Quando o usuário pressiona um botão, digita em um campo ou escolhe de uma lista que seu mod desenhou, o Claude Code chama a função que você deu a esse controle, e ela é executada em seu módulo. Cada controle leva seus próprios callbacks:

* **`Button`**: leva `onPress(e)`, onde `e.surface` é o app de onde veio o pressionamento
* **`Input`**: leva `onSubmit(value)` e `onInput(value)`
* **`Select`**: leva `onSelect(value)` com suas escolhas em `options`, uma lista de pelo menos uma escolha com valores únicos, como `[{ value: 'sm', label: 'Small' }, { value: 'lg', label: 'Large' }]`

Um teste pressiona ou digita em um controle por sua `key`, então dê a cada um uma. Cada uso de um controle também dispara [`ui.press`, `ui.input` ou `ui.select`](/docs/pt/plugins/mods/reference#interface) com a `key` em `e.element`, e outro mod pode fazer hook nesses eventos. Seu hook é executado antes de seu callback, então vê o que o usuário digita em seu `Input` e pode alterá-lo ou responder no lugar de seu callback. A API de mods não tem método que pressione o botão de outro mod.

<h3 id="know-which-keys-your-mod-can-receive">
  Foco do teclado e atalhos de teclado
</h3>

Seu mod nunca lê o teclado por si só. O usuário pressiona uma tecla, o Claude Code decide qual de seus controles é para, e o callback desse controle é executado. Além de um [atalho de teclado de dígito na faixa](/docs/pt/plugins/mods/reference#elements), isso acontece apenas enquanto seu painel ou faixa tem foco do teclado. O resto do tempo, as teclas vão para o prompt.

<h4 id="how-a-pane-gets-keyboard-focus">
  Como um painel obtém foco do teclado
</h4>

Um painel obtém foco do teclado de uma de três maneiras:

* Seu mod o abre com `focus: true` de um comando ou um pressionamento
* O usuário pressiona Ctrl+X depois Tab
* O usuário clica nele

O Claude Code concede `focus: true` apenas enquanto o prompt está vazio e nada mais tem foco do teclado. Um painel que abre enquanto o usuário está digitando não toma seus pressionamentos de tecla.

<h4 id="what-each-key-does">
  O que cada tecla faz
</h4>

Esta tabela lista o que uma tecla faz enquanto seu painel ou faixa tem foco do teclado:

| Tecla | O que faz |
| :- | :- |
| Tab | Move para o próximo controle |
| Para cima e Para baixo | Movem entre controles enquanto seu desenho cabe. Quando o painel ou faixa tem mais linhas do que pode mostrar, eles o rolam. |
| Enter | Pressiona o `Button` focado, envia o `Input` focado ou escolhe em um `Select` |
| Atalho de teclado de um botão | Pressiona esse botão. Enquanto um `Input` tem o foco, cada tecla imprimível vai para o campo. |
| Esc | Retorna o foco do teclado para o prompt. Com `closeOnEscape: true`, também fecha o painel. |

Um mod não pode vincular Tab ou as setas para nada mais, então um jogo direciona com `w`, `a`, `s` e `d`.

<h4 id="set-a-hotkey-and-the-first-focus">
  Definir um atalho de teclado e o primeiro foco
</h4>

Duas propriedades em um controle decidem como o teclado o alcança:

* **`hotkey`**: para deixar o usuário pressionar um `Button` com uma tecla, dê a ele um `hotkey` de um dígito ou uma letra minúscula, como em `hotkey: 'a'`
* **`autoFocus`**: para escolher qual controle tem o foco quando o painel abre, adicione `autoFocus: true` a ele. Deixe a propriedade de fora dos outros, porque o Claude Code recusa `autoFocus: false`.

Como um atalho de teclado mostra depende do botão e do app:

| Botão | No terminal | No app Desktop |
| :- | :- | :- |
| Com colchetes, o padrão | `[ Add one ]`, sem atalho de teclado mostrado | O rótulo com uma pequena tecla ao lado |
| Com `plain: true` | `1: One` | O rótulo com uma pequena tecla ao lado |

No terminal, nomeie a tecla no rótulo de um botão entre colchetes, ou use `plain: true`, para que o usuário possa ver o que pressionar. A [referência de elementos](/docs/pt/plugins/mods/reference#elements) tem as outras regras de `Button`: `action`, atalhos de teclado de dígito na faixa e dois botões em um atalho de teclado.

<h3 id="take-typed-input-and-draw-a-row-for-each-item">
  Tomar entrada digitada e desenhar uma linha para cada item
</h3>

Muitos painéis são um campo de texto com uma lista sob ele. O exemplo nesta seção é um painel de notas: você digita uma nota e pressiona Enter para adicioná-la, e cada nota tem um botão `x` que a deleta. Com duas notas adicionadas, o terminal desenha o painel desta forma:

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

O exemplo usa duas técnicas:

* **Tomar entrada digitada**: um `Input` chama `onSubmit(value)` com o texto do campo quando o usuário pressiona Enter, e `onInput(value)` em cada mudança
* **Desenhar uma lista**: mapeie seus dados para uma linha cada, e dê a cada botão de linha sua própria `key`

Este hook desenha o conteúdo do painel:

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

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

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

Para tentar o painel:

* **Adicionar uma nota**: digite uma linha e pressione Enter. A linha aparece como uma nova linha e o campo esvazia.
* **Deletar uma nota**: pressione Tab até o botão `x` da nota ter o foco, depois pressione Enter. O `x` é o rótulo do botão e não um atalho de teclado, então digitar a letra não o pressiona.

Cada mudança segue o mesmo ciclo de renderização que `hello-tabs`: o callback altera `notes`, chama `redraw` e salva a lista em `$.store`.

O campo esvazia após cada envio por causa de sua propriedade `value`. `value` é o texto que o campo contém quando é desenhado, e a digitação do usuário o substitui até seu hook desenhar o campo novamente. O exemplo sempre desenha o campo com `''`.

O exemplo salva as notas e não as carrega. Para trazê-las de volta na próxima sessão, leia-as em um hook `session.start`, da forma que `hello-tabs` lê `count`.

Três propriedades compõem a linha do campo, `Note: Type a note and press Enter ⏎ add`:

| Propriedade | No exemplo | O que é |
| :- | :- | :- |
| `label` | `Note` | O texto antes do campo. O terminal desenha `: ` após ele. |
| `placeholder` | `Type a note and press Enter` | Texto fraco que mostra enquanto o campo está vazio |
| `submitLabel` | `add` | A palavra após `⏎` que diz o que Enter faz |

Enviar um `Input` não inicia uma volta a menos que seu callback chame [`$.prompt.submit`](/docs/pt/plugins/mods/api#start-a-turn-from-a-background-job).

<h2 id="redraw-when-something-changes">
  Redesenhar um site
</h2>

Um desenho é um instantâneo: mostra o que seu hook `ui.render` retornou a última vez que o hook foi executado. Para mostrar algo novo, o hook tem que ser executado novamente. O Claude Code o executa novamente para algumas mudanças, e seu mod pede o resto.

<h3 id="when-claude-code-redraws-without-being-asked">
  Quando o Claude Code redesenha sem ser solicitado
</h3>

O Claude Code executa seu hook `ui.render` novamente quando as propriedades do site mudam ou a largura do terminal muda. Ele não executa o hook em um timer, e não pode dizer quando uma variável em seu módulo muda.

<h3 id="redraw-when-your-data-changes">
  Redesenhar quando seus dados mudam
</h3>

Para ter seus sites desenhados novamente após suas próprias mudanças de dados, chame `$.ui.invalidate('ui.render')`. Este painel conta pressionamentos. O callback do botão altera `count`, depois pede um redesenho:

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

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

Cada pressionamento levanta o número no painel. O exemplo [`hello-tabs`](#build-a-pane-with-tabs) envolve a mesma chamada em sua função `redraw`.

Um valor que você mantém em [`$.state`](#keep-a-value-in-\$-state) não precisa da chamada, porque escrever o valor redesenha os sites que o leem.

<h3 id="redraw-on-a-timer">
  Redesenhar em um timer
</h3>

Para manter um relógio, uma contagem regressiva ou um valor de fora da sessão atual, redesenhe em um cronograma. Inicie um timer no hook `session.start` do módulo. Se o módulo já tiver um, como `hello-tabs` tem, adicione a linha [`$.clock.every`](/docs/pt/plugins/mods/api#run-work-in-the-background) a ele:

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

O Claude Code agora executa seu hook `ui.render` uma vez por segundo. O timer para quando o módulo recarrega, e a nova cópia do módulo inicia o seu próprio.

<h3 id="how-often-a-site-can-redraw">
  Com que frequência um site pode redesenhar
</h3>

O Claude Code limita com que frequência redesenha um site, então seu mod pode chamar `$.ui.invalidate` com a frequência que seus dados mudam. O painel visível e a faixa têm um limite mais alto do que outros sites, e a [tabela de limites](/docs/pt/plugins/mods/reference#limits) tem os números.

Chamadas que vêm mais rápido que o limite são combinadas em um redesenho. Esse redesenho executa seu hook uma vez, e o hook lê seus dados como estão naquele momento, então o valor mais recente mostra e os valores no meio não. Uma animação não pode ser executada mais rápido que o limite.

<h2 id="keep-state">
  Manter estado
</h2>

Um mod tem três lugares para manter um valor, e diferem em quanto tempo o valor dura: até o módulo recarregar, até a sessão terminar ou de uma sessão para a próxima. Escolha por quanto tempo o valor tem que durar:

| Mantê-lo em | Dura até | Use para |
| :- | :- | :- |
| Uma variável no nível do módulo | O módulo recarrega, o que acontece toda vez que você salva um arquivo durante o desenvolvimento | Valores que você pode perder, como `tab` é em `hello-tabs` |
| `$.state` | A sessão termina, ou o usuário executa `/clear`, `/resume` ou `/branch` | Valores que um desenho depende que devem sobreviver a um recarregamento |
| `$.store` | Seu mod o deleta, ou nenhuma sessão lê ou escreve o armazenamento por [`cleanupPeriodDays`](/docs/pt/settings-reference#cleanupperioddays). O armazenamento é um armazenamento de chave-valor, salvo como um arquivo JSON do seu próprio plugin sob `~/.claude/plugins/store/`. | Configurações, histórico, qualquer coisa que o usuário espera encontrar na próxima vez |

`$.store.get(key)` resolve para o valor ou `undefined`, e `$.store.set(key, value)` leva qualquer valor JSON.

<h3 id="keep-a-value-in-state">
  Manter um valor em `$.state`
</h3>

`$.state` mantém valores pela duração de uma sessão, e redesenha para você. É estado reativo: um hook `ui.render` que lê um valor se inscreve nele, então o Claude Code redesenha esse site cada vez que você escreve o valor, e você não chama `$.ui.invalidate`. Um valor em `$.state` também sobrevive a um recarregamento do módulo, o que uma variável não faz.

Para configurá-lo, declare seus valores, aponte seu manifesto para a declaração, depois defina e use cada valor. Os exemplos movem o `count` de `hello-tabs` para `$.state`.

<h4 id="declare-the-values">
  Declarar os valores
</h4>

Declare os valores em um arquivo de tipos. A chave externa é o nome do seu plugin, e cada entrada sob ela é um valor e seu tipo. Salve isto como `hello-tabs/types/index.d.ts`:

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

<h4 id="point-the-manifest-at-the-declaration">
  Apontar o manifesto para a declaração
</h4>

Para deixar `claude plugin validate` verificar seu código contra esse arquivo, adicione um campo `types` ao manifesto com seu caminho:

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

<h4 id="define-read-and-write-a-value">
  Definir, ler e escrever um valor
</h4>

Em seu módulo, defina cada valor com um padrão, leia-o enquanto desenha e escreva-o de um callback. `atom` nomeia um valor e seu padrão, `read` o retorna e `update` o escreve. Os três ajudantes chamam `$.state.get` e `$.state.set` para você:

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

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

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

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

Porque o hook `ui.render` leu `count`, o Claude Code executa o hook novamente cada vez que o botão o escreve.

Três regras se aplicam ao código:

* **Escreva `plugin` e `key` como strings literais**: `claude plugin validate` as lê de sua fonte
* **Declare cada valor no arquivo de tipos**: caso contrário a validação falha com `hello-tabs.count is not declared`
* **Escreva de um callback ou hook de outro evento**: um hook `ui.render` pode ler estado e não pode escrevê-lo, então escreva de `onPress`, `onSubmit` ou um hook para outro evento

<h4 id="change-hello-tabs-to-use-state">
  Alterar `hello-tabs` para usar `$.state`
</h4>

Para mover `count` em `hello-tabs` para `$.state`, altere cada linha que o usa:

* **No topo do módulo**: adicione a linha `import` e substitua `let count = 0` pela linha `atom`
* **No hook `ui.render`**: adicione a linha `read` antes de `tabButton` e desenhe `'Count: ' + n` no `Text`
* **No botão Add one**: substitua `onPress` pelo da [Salvar de mais de uma sessão](#save-from-more-than-one-session), que salva a contagem bem como escreve-a
* **No hook `session.start`**: substitua as duas linhas que leem `saved` pela chamada `loadCount` de [Carregar um valor salvo novamente após `/clear`](#load-a-saved-value-again-after-clear)

Mantenha `redraw` para os botões de aba, porque `tab` ainda é uma variável.

<h3 id="load-a-saved-value-again-after-clear">
  Carregar um valor salvo novamente após `/clear`
</h3>

Se seu mod copia um valor salvo de `$.store` para `$.state` em `session.start`, tem que copiá-lo novamente após `/clear`, `/resume` ou `/branch`. Esses comandos colocam cada valor `$.state` de volta ao seu padrão, e `session.start` não dispara novamente. [`classic.SessionStart`](/docs/pt/plugins/mods/events#hook-the-settings-hook-events) dispara após cada um deles, com `e.source` definido como `clear`, `resume` ou `fork`, então copie o valor novamente em um hook nele. Caso contrário seu desenho mostra o padrão, e um callback que salva o valor `$.state` escreve o padrão sobre o que você armazenou.

Este código carrega `count` de ambos os hooks. Ele se baseia na versão `$.state` de `hello-tabs`, onde `count` é um atom e `update` é importado. Coloque `loadCount` acima de `register` e adicione a chamada `loadCount` ao hook `session.start` que você já tem. `classic.SessionStart` também dispara na inicialização e após compactação, que não redefine `$.state`, então o filtro em `source` mantém o hook aos três resets:

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

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

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

Com ambos os hooks em vigor, o painel mostra a contagem salva após `/clear` e não `0`, e o próximo pressionamento de **Add one** adiciona à contagem salva.

`loadCount` escreve o valor armazenado sobre o em `$.state`, e `session.start` dispara novamente cada vez que o módulo recarrega. Para manter o armazenamento de ficar para trás, salve em cada mudança, como o botão **Add one** faz.

Para verificar o recarregamento sem uma sessão, [teste o desenho após `/clear`](/docs/pt/plugins/mods/test#test-a-drawing-after-clear).

<h3 id="save-from-more-than-one-session">
  Salvar de mais de uma sessão
</h3>

Cada sessão em sua máquina que executa seu mod compartilha um `$.store`. Um `get` seguido por um `set` não é atômico. Quando duas sessões cada uma lê um valor, o altera e o escreve de volta, elas correm, e a segunda escrita substitui a primeira.

Duas escolhas tornam isso menos provável:

* **Dê a cada item sua própria chave**: um `set` altera apenas sua própria chave, então sessões que escrevem chaves diferentes não sobrescrevem uma à outra
* **Leia novamente logo antes de escrever**: para um valor que várias sessões alteram, `get` a chave no callback e construa o novo valor a partir disso, não de uma cópia que você carregou em `session.start`. Outra escrita de sessão ainda é perdida se cair entre seu `get` e seu `set`.

Este botão adiciona um ao que o armazenamento contém agora, depois atualiza o desenho:

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

Se uma segunda sessão pressionou seu próprio botão três vezes desde que esta sessão começou, este pressionamento mostra e salva uma contagem que inclui esses três.

<h2 id="next-steps">
  Próximos passos
</h2>

* [Reagir a eventos](/docs/pt/plugins/mods/events): alimente seu desenho de chamadas de ferramenta e voltas
* [Use a API de mods](/docs/pt/plugins/mods/api): alimente seu desenho de timers e chamadas de modelo
* [Teste um desenho](/docs/pt/plugins/mods/test#test-a-drawing): pressione seus botões de um teste, em mais de uma superfície
* [Sites de renderização](/docs/pt/plugins/mods/reference#render-sites) e [elementos](/docs/pt/plugins/mods/reference#elements): propriedades de cada site e propriedades de cada elemento
