claude-code.d.ts do Claude Code 2.1.289, em 05/10/2026. O mod de exemplo deste módulo passou no claude plugin validate nessa versão.
Desenhe na faixa acima do prompt
A faixa é o componente AbovePrompt: a área logo acima de onde você digita, a mesma em que aparecem as pesquisas de satisfação. É o lugar de um medidor, um contador, um bichinho. Fica sempre à vista, sem tomar a conversa.
Ela só é pedida no terminal e no desktop. A pessoa pode recolher ([-] ou ctrl+x ctrl+a) e focar (um clique ou ctrl+x tab): aí um Input recebe texto e as hotkey dos botões passam a valer.
🆕 Novo aqui? As props da faixa
hasSurvey— uma pesquisa está usando a faixa. O mod cede: devolvenext(e).isWorking— um turno do modelo está rodando.maxRows— quantas linhas cabem inteiras; uma árvore maior rola numa janela descroll.bodyRows.bodyColumns— a largura útil, já sem as cinco colunas do[-]. Dimensione por ela, não porviewport.columns.
Como ler o desenho: cada etiqueta à direita aponta para onde um jeito de avisar aparece. A faixa (roxa) fica colada no prompt e é desenhada por um hook ui.render. Os outros três são chamadas simples ao $.ui, sem hook de desenho.
Conviva com os outros mods
A faixa é uma instância só. Se o Rafa tem o medidor de contexto, o bichinho e o mod dele instalados, os três hooks recebem o mesmo AbovePrompt, em cadeia. Quem devolve uma árvore sem chamar next apaga o que viria dos outros.
A forma que convive (pegadinha 11 do COMO-FAZER-UM-MOD): const resto = await next(e) primeiro, depois uma coluna com a sua linha e o resto. Sem nada a mostrar, devolva o resto e saia.
Como ler o desenho: a fila de cima é a cadeia. Cada mod que chama next recebe o que os de baixo desenharam e acrescenta o seu. À esquerda, a faixa que todo mundo quer; à direita, o que acontece quando um mod responde sozinho.
Crie a pasta minha-faixa/ com estes quatro arquivos (o caminho de cada um está no comentário):
// minha-faixa/.claude-plugin/plugin.json
{
"name": "minha-faixa",
"version": "0.1.0",
"description": "Faixa do Rafa: quantas edições nesta sessão",
"author": { "name": "Rafa" },
"types": "./types/index.d.ts"
}
// minha-faixa/hooks/hooks.json
{ "modules": ["./register.tsx"] }
// minha-faixa/types/index.d.ts
export type Contagem = number
declare module 'claude-code' {
interface PluginState {
'minha-faixa': { editados: number }
}
}
// minha-faixa/hooks/register.tsx
import { atom, read, update } from 'claude-code'
import type { Register } from 'claude-code'
const editados = atom({ plugin: 'minha-faixa', key: 'editados' } as const, 0)
export const register: Register = on => {
on('tool.call', { tool: 'Edit' }, async ($, e, next) => {
const ran = await next(e)
if (ran.deny === undefined && ran.isError !== true) await update($, editados, n => n + 1)
return ran
})
on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
const resto = await next(e)
const n = await read($, editados)
if (e.props.hasSurvey || n === 0) return resto
const { Box, Text } = $.ui.resolve(e)
return (
<Box flexDirection="column">
<Text dimColor>{`Rafa: ${n} edição(ões) nesta sessão`}</Text>
{resto}
</Box>
)
})
}
claude plugin validate minha-faixa. Rodado aqui (2.1.289): a lista mostra hooks: tool.call{tool=Edit}, ui.render{component=AbovePrompt} e termina em ✔ Validation passed. (Os comentários de caminho não fazem parte dos arquivos.)✓ Convive
- ✓
await next(e)antes de montar a sua linha - ✓ Coluna com a sua linha e o
resto - ✓ Nada a mostrar ou pesquisa na faixa:
return resto
✗ Atropela
- ✗ Devolver
<Box>sem ter chamadonext - ✗ Ignorar
hasSurveye cobrir a pesquisa - ✗ Ocupar mais linhas do que
maxRowssem precisar
Mostre status sem abrir nada
Nem todo aviso merece um hook de desenho. $.ui.status(texto) fixa uma linha de status do seu plugin embaixo do prompt, ao lado dos avisos fixos do engine. Fica lá até a próxima chamada trocar.
É uma linha por plugin. $.ui.status(undefined) apaga. Pode ser chamada de qualquer hook, sem esperar (devolve void), e não começa turno nem chega ao modelo.
on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
const ran = await next({ ...e, command: e.command.trim() })
const hasFailed = ran.deny === undefined && ran.isError === true
$.ui.status(hasFailed ? `failed: ${e.command.slice(0, 40)}` : undefined)
return ran
})
undefined e ela some.💡 Status ou faixa?
Status é texto curto que não precisa de botão nem cor por trecho: "testes falharam", "modo gravação ligado". Se precisar de botão, gráfico ou mais de uma linha, é faixa. O Rafa usa status para "3 arquivos sem commit" e faixa para o contador com botão.
Avise com toast e log
$.ui.toast(texto) mostra uma caixinha por alguns segundos no canto de cima, com o nome do plugin. Some sozinha (4 segundos por padrão; mude com { timeoutMs }), some com um clique, fica enquanto o ponteiro estiver em cima.
$.ui.log(texto) acrescenta uma linha apagada na conversa, como um aviso do sistema, que o modelo não lê. Com { to: 'debug' } vai só para o debug log. Num claude -p ou no SDK a linha chega ao host como ui_log.
| Jeito | Dura | Fica na conversa? | Bom para |
|---|---|---|---|
Faixa (AbovePrompt) | enquanto o hook desenhar | não | medidor, contador com botão |
$.ui.status | até a próxima chamada | não | estado atual em uma linha |
$.ui.toast | alguns segundos | não | "aconteceu agora" |
$.ui.log | para sempre | sim (o modelo não lê) | registro que a pessoa vai rolar para ver |
O que olhar na tabela: só o log deixa rastro. Se o Rafa quer saber depois "quando foi que o teste quebrou", é log. Se só quer um susto na hora, é toast.
✓ Vai para o log
- ✓ "o teste quebrou às 14h"
- ✓ arquivo apagado, comando negado
- ✓ o que a pessoa vai procurar depois
✗ Não vai para o toast
- ✗ aviso que não pode se perder
- ✗ texto longo, de várias linhas
- ✗ algo que precisa de botão (isso é faixa ou painel)
⚠️ Toast pode esperar e até se perder
Enquanto um painel aberto com holdToasts estiver à vista (de qualquer plugin), todo toast espera sem ser desenhado, com o relógio parado. Um aviso que não pode se perder vai para o log, não para o toast.
sempre à vista
uma por plugin
some sozinho
fica na conversa
Disseque o exemplo oficial da faixa
A referência que vem com o Claude Code traz examples/band.tsx: uma faixa que mostra quanto durou o último turno e quantas ferramentas ele chamou, com um botão para esconder. São 61 linhas e usam quase tudo deste módulo.
Leia o render com atenção: ele tem uma diferença em relação à forma do tópico 2.
on('turn.complete', async ($, e, next) => {
const seconds = Math.round(((await $.clock.now()) - startedAt) / 1000)
const turn: Turn = { seconds, tools }
await update($, last, () => turn)
if (seconds > 120) {
$.ui.toast(`That turn took ${seconds}s`)
}
return next(e)
})
on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
const turn = await read($, last)
const isQuiet = e.props.hasSurvey || turn === null || (await read($, isHidden))
if (isQuiet) {
return next(e)
}
const { Box, Button, Text } = $.ui.resolve(e)
return (
<Box>
<Text dimColor>
Last turn: {turn.seconds}s, {turn.tools} tool calls{' '}
</Text>
<Button
key="hide"
label="Hide"
onPress={() => update($, isHidden, () => true)}
/>
</Box>
)
})
<Box> e não chama next. Sozinho funciona; ao lado de outra faixa, apaga a outra.Eventos escrevem
prompt.submit marca a hora, tool.call conta, turn.complete grava o resumo com update.
Turno longo vira toast
Acima de 120 s, um $.ui.toast. Avisa na hora e não ocupa a faixa.
O render cede quando deve
Pesquisa na faixa, nada gravado ou escondido: return next(e).
O botão escreve pelo handler
onPress com update; a faixa redesenha porque leu isHidden.
💡 Para conviver, mude duas linhas
Troque o começo por const resto = await next(e), o return next(e) por return resto, e ponha {resto} dentro de um <Box flexDirection="column"> junto com a sua linha. É o que o context-meter do kit da Prompt Advisers faz em .mjs: const original = await next(e) e depois Box({ flexDirection: "column", children: [original, meter] }).
Teste a faixa no terminal e no desktop
A faixa existe em duas superfícies, e cada uma tem sua tabela de elementos. A referência oficial pede: escreva o corpo do teste uma vez e rode em ['terminal', 'desktop'] as const. Assim o teste prova que o mod não depende de uma superfície.
O teste monta o componente com o mount do noun ui do kit de testes, passando { plugin, surface, component, props }, e procura o texto na árvore. O módulo 4.2 ensina o teste inteiro; aqui fica o esqueleto e o teste ao vivo.
for (const surface of ['terminal', 'desktop'] as const) {
// 1. monte a faixa nesta superfície (mount do noun ui do teste)
// 2. procure o Text pelo texto, não pela key (pegadinha 12)
// 3. desmonte antes da próxima volta
}
Raster sem estreitar e.surface, a volta do desktop falha.Na pasta que contém minha-faixa/, abra uma sessão só com esse mod carregado:
claude --plugin-dir minha-faixa
Peça ao Claude para editar um arquivo qualquer de teste. Para o desktop, aponte CLAUDE_CODE_PLUGIN_DIRS=<caminho absoluto> para a mesma pasta (módulo 1.3).
Rafa: 1 edição(ões) nesta sessão aparece acima do prompt. Com outro mod de faixa carregado junto (repita a flag --plugin-dir), as duas linhas aparecem.Teste rápido (opcional): o Rafa instalou o mod dele e o medidor de contexto sumiu da faixa. O que é mais provável?
🎓 Resumo do módulo
Próximo módulo:
3.3 — Painéis e botões