Diff Review
Revise as edições de um agente em vários arquivos antes que sejam aplicadas. Uma árvore de arquivos com contagens, aceite ou rejeite cada mudança, cada arquivo ou tudo de uma vez, comentários em qualquer linha ou intervalo que voltam para o agente, visões unificada e dividida, destaques por palavra, desfazer, edições em streaming e um resumo "4 arquivos editados" para o chat.
Agentes de código alteram vários arquivos de uma vez, e as pessoas precisam manter parte disso e descartar o resto. O Diff Review coloca cada edição em uma única lista rolável com uma árvore de arquivos ao lado, para que você possa aceitar ou rejeitar uma única alteração, um arquivo inteiro ou tudo, pelo mouse ou pelo teclado. O DiffSummary é o card "4 arquivos editados +120 −34" para o chat, com as mesmas ações em cada arquivo.
Cada decisão afeta exatamente uma alteração. Uma alteração decidida se dobra em um resumo de uma linha com seu resultado, Show e Undo, e um arquivo se dobra quando todas as suas alterações foram decididas. As ações em massa informam quantas alterações cobrem, e toda decisão, inclusive Accept all e Reject all, pode ser desfeita com U ou ⌘Z. "Next" sempre significa a próxima alteração que você ainda não decidiu. As contagens e a barra de progresso vêm do que ainda está pendente, então a revisão termina com um claro "All reviewed".
Passe o mouse sobre uma linha e pressione + na margem, ou foque uma linha e pressione Enter ou C, para comentá-la. Arraste ou use Shift+clique nos números de linha, ou use Shift+↑↓, para comentar um intervalo. O composer abre como um pequeno popover sob a linha, e um comentário enviado permanece na linha como uma thread que você pode editar, excluir com desfazer ou recolher, com uma contagem na margem. As linhas removidas são reportadas no lado antigo e as linhas adicionadas ou inalteradas no lado novo, então os números de linha sempre correspondem ao arquivo.
Passe um diff unificado, ou o conteúdo antes e depois e deixe o bloco calcular o diff. Com o conteúdo completo, você pode mostrar as linhas inalteradas entre as alterações, 20 por vez para cima ou para baixo, ou todas de uma vez quando menos de 20 estão ocultas. As linhas removidas têm uma barra fina listrada na margem e as adicionadas uma barra sólida, então a diferença nunca depende de cor. As linhas são realçadas com o Shiki como no Code Block, e as palavras alteradas dentro de uma linha são marcadas quando as duas linhas são parecidas o bastante para isso ajudar. O layout muda para a visão dividida quando há espaço, o que por padrão é pelo menos 900px. Os arquivos que ainda estão em streaming aparecem ao vivo e ainda não podem ser decididos. Arquivos que mudaram no disco podem ser rejeitados, mas não aceitos. Diffs com mais de 400 linhas alteradas esperam atrás de Load diff, e suas ações no nível do arquivo continuam funcionando.
Adicione o registro Pro ao components.json
components.json Adicione seu token
Crie um token na sua página de conta e coloque-o em
.env.localcomoHEXTAUI_PRO_TOKEN.Adicione o bloco
pnpm dlx shadcn@latest add @hextaui-pro/diff-review
Com o AI SDK
Transforme chamadas de ferramenta de edição em arquivos, inclusive entradas que ainda estão em streaming: um diff unificado, o conteúdo de um arquivo novo, ou old_string e new_string do Claude Code. O resumo vai no chat e a revisão ao lado, compartilhando um único provider.
Envie comentários de linha ao agente
onComment recebe o arquivo, o lado, os números de linha e um trecho do diff, para que você possa enviá-lo como mensagem de acompanhamento. Retorne a promise de sendMessage e cada comentário mostra Sending… e depois Sent to the agent.
Grave o que foi aceito
applyDecisions reconstrói cada arquivo a partir dos hunks que você manteve, usando o conteúdo original ou o editado. Também retorna um patch apenas com os hunks aceitos e informa quando um arquivo deve ser excluído ou nunca criado.
Um diff somente leitura
O DiffView mostra as alterações de um arquivo com o mesmo realce, barras na margem e expansão de linhas inalteradas, sem controles de revisão. Ele cresce com o conteúdo, então coloque-o dentro de um pai com rolagem.
A partir de um git diff
parsePatch divide a saída do git diff ou de um diff unificado simples em arquivos, com renomeados, novos, excluídos e binários.
Anatomia
As partes que você compõe, de fora para dentro.
| Parte | Descrição |
|---|---|
DiffReviewProvider | Guarda os arquivos, as decisões e o histórico de desfazer, para que um resumo no chat e um painel de revisão permaneçam sincronizados. |
DiffReview | A superfície de revisão: barra de ferramentas, árvore de arquivos, as alterações e a barra de revisão com progresso, navegação e ações em massa. |
DiffSummary | Um card para o chat: "4 arquivos editados" com contagens, cada arquivo com aceitar e rejeitar, e ações em massa. |
DiffView | Um diff somente leitura de um arquivo, para mostrar as mudanças entre versões. |
Comment threads | Comentários sob uma linha ou intervalo, com autor, horário, status de entrega, editar, excluir e recolher. |
parsePatch, applyDecisions | Lê a saída do git diff em arquivos e transforma decisões de volta em conteúdo de arquivo e em um patch apenas com o que foi aceito. |
useDiffReview | O estado compartilhado, para criar seus próprios controles dentro do provider. |
DiffReviewProvider
DiffReview e DiffSummary recebem as mesmas props quando usados isoladamente.
| Prop | Tipo | Padrão |
|---|---|---|
filesOs arquivos alterados, na ordem em que o agente os editou. | DiffFile[] | – |
decisionsDecisões controladas por id de alteração. Alterações sem entrada ficam pendentes. | Record<string, "accepted" | "rejected"> | – |
defaultDecisionsDecisões iniciais quando não controlado. | Record<string, "accepted" | "rejected"> | – |
onDecisionsChangeChamado com as próximas decisões após cada ação, inclusive o desfazer. | (decisions) => void | – |
onDecideChamado uma vez por ação com a decisão, sua origem (change, file, all ou undo) e as alterações que ela afetou. Use para gravar ou reverter arquivos. | (event: DiffDecideEvent) => void | – |
commentsComentários controlados. | DiffComment[] | – |
defaultCommentsComentários iniciais quando não controlado. | DiffComment[] | – |
onCommentsChangeChamado depois que um comentário é adicionado, editado, excluído ou restaurado. | (comments: DiffComment[]) => void | – |
onCommentChamado quando um comentário é enviado, e de novo com o mesmo id quando é editado. Retorne uma promise para mostrar Sending… e Sent to the agent, ou Couldn’t send com Retry se ela for rejeitada. | (comment: DiffComment) => unknown | – |
authorExibido nos comentários novos. | { name: string; image?: string } | { name: "You" } |
| Prop | Tipo | Padrão |
|---|---|---|
pathO caminho do arquivo. Quando um agente edita o mesmo caminho duas vezes, as duas edições são listadas. | string | – |
patchUm diff unificado deste arquivo. Cabeçalhos de hunk sem números de linha, CRLF e "No newline at end of file" são aceitos. | string | – |
beforeO conteúdo original. Com after, o diff é calculado para você e as linhas inalteradas podem ser mostradas. | string | – |
afterO conteúdo editado. Sozinho, descreve um arquivo novo. | string | – |
oldPathO caminho anterior de um arquivo renomeado. | string | – |
statusCalculado a partir do conteúdo quando omitido. | "added" | "deleted" | "modified" | "renamed" | – |
languageId de linguagem do Shiki. O padrão é a extensão do arquivo. | string | – |
binaryMostra "Binary file not shown" e decide o arquivo como um todo. | boolean | – |
streamingO agente ainda está escrevendo este arquivo. Ele é atualizado ao vivo e ainda não pode ser decidido. | boolean | – |
staleO arquivo mudou no disco depois da edição. Aceitar fica desativado até você passar um diff atualizado. | boolean | – |
| Prop | Tipo | Padrão |
|---|---|---|
viewLayout controlado. Auto é dividido quando a área do diff tem pelo menos 900px de largura. Split volta a unificado abaixo de 600px. | "auto" | "unified" | "split" | – |
defaultViewLayout inicial quando não controlado. | "auto" | "unified" | "split" | "auto" |
onViewChangeChamado quando alguém escolhe um layout. | (view: "unified" | "split") => void | – |
advanceDepois de decidir pelo teclado, vai para a próxima alteração que ainda está pendente. | boolean | true |
largeDiffLinesArquivos com mais linhas alteradas do que isto esperam atrás de Load diff. | number | 400 |
classNameDê uma altura a ele, ou coloque-o em uma coluna flex. A lista rola por dentro. | string | – |
| Prop | Tipo | Padrão |
|---|---|---|
onReviewMostra Review e faz cada arquivo abrir a revisão naquele arquivo. | (path?: string) => void | – |
foldAfterDobra listas mais longas atrás de "Show 3 more files". | number | 6 |
| Prop | Tipo | Padrão |
|---|---|---|
pathO arquivo a que o comentário se refere. | string | – |
sideO lado da última linha: old para uma linha removida, new para uma adicionada ou inalterada. | "old" | "new" | – |
startSideO lado da primeira linha. Difere de side quando um intervalo vai de linhas removidas a linhas adicionadas. | "old" | "new" | – |
linesNúmeros de linha no arquivo em startSide e side. | { start: number; end: number } | – |
excerptAs linhas selecionadas em forma de diff, como "-old" e "+new", para que o agente veja o código mesmo depois que os números de linha mudarem. | string | – |
textO que o leitor escreveu. | string | – |
id, createdAt, authorPreenchido quando o comentário é criado. | string, number, { name; image? } | – |
DiffView
Exportado de diff-view.tsx. Ele não tem um contêiner de rolagem próprio: cresce com o conteúdo e os cabeçalhos de suas alterações ficam fixos no pai com rolagem mais próximo, então coloque-o em um, como um elemento min-h-0 flex-1 overflow-y-auto.
| Prop | Tipo | Padrão |
|---|---|---|
beforeO conteúdo anterior. | string | – |
afterO conteúdo posterior. | string | – |
patchUm diff unificado, em vez de antes e depois. | string | – |
languageId de linguagem do Shiki. O padrão é a extensão de path. | string | – |
pathUsado para a linguagem e o cabeçalho opcional. | string | "file" |
viewSplit volta a unificado abaixo de 600px. | "unified" | "split" | "unified" |
wrapQuebra as linhas longas. O Split sempre quebra. | boolean | false |
headerMostra o caminho e as contagens +N / −N acima das alterações. | boolean | false |
classNameClasses para a raiz. | string | – |
| Prop | Tipo | Padrão |
|---|---|---|
decisionPending quando uma decisão foi desfeita. | "accepted" | "rejected" | "pending" | – |
sourceO que a pessoa fez. | "change" | "file" | "all" | "undo" | – |
changesApenas as alterações cuja decisão realmente mudou. | { file: string; path: string; id: string }[] | – |
applyDecisions(file, decisions, options?)
Retorna { path, decision, content?, deleted, patch }. As alterações pendentes contam como rejeitadas, a menos que options.pending seja "accepted".
| Prop | Tipo | Padrão |
|---|---|---|
contentO arquivo após a revisão, construído a partir de before ou after. Undefined para arquivos binários ou quando nenhum dos dois é conhecido. | string | undefined | – |
deletedTrue quando o arquivo não deveria existir: uma exclusão aceita ou um arquivo novo rejeitado. | boolean | – |
patchApenas os hunks aceitos, renumerados para que o git apply funcione no arquivo original. | string | – |
| Tecla | Ação |
|---|---|
| J | Vai para a próxima alteração que ainda está pendente. K volta. |
| N | Vai para o próximo arquivo. P vai para o anterior. |
| Y | Aceita a alteração atual. ⌘↵ faz o mesmo. |
| X | Rejeita a alteração atual. ⌘⌫ faz o mesmo. |
| ⇧Y | Aceita todas as alterações pendentes no arquivo atual. ⇧X as rejeita. |
| ⌘⇧↵ | Aceita todas as alterações que estão prontas. ⌘⇧⌫ as rejeita. |
| U | Desfaz a última decisão, inclusive as em massa. ⌘Z faz o mesmo. |
| ↑↓ | Percorre a árvore de arquivos. Enter salta para o arquivo, ← e → dobram as pastas, e digitar salta para um arquivo pelo nome. |
| Tab | Entra nas linhas de uma alteração. Cada alteração é uma parada, e ↑ ↓ Home End se movem entre suas linhas; na visão dividida, ← → alternam os lados. |
| ⇧↓ | Seleciona um intervalo de linhas. ⇧↑ o estende para cima, e Esc o limpa. |
| Enter | Abre o composer de comentário para a linha em foco ou o intervalo selecionado. C faz o mesmo. |
| ⌘↵ | Envia o comentário do composer. Esc o fecha e mantém o que você escreveu para aquela linha. |
- A revisão é uma região chamada "Review changes". Cada alteração é um grupo com um nome completo, como "Alteração 2 de 9, app/page.tsx, linhas 40–52, 3 linhas adicionadas, 1 linha removida, pendente".
- As decisões, as ações em massa e o desfazer são anunciados por uma região live polite com o que resta, por exemplo "Alteração 2 de 9 aceita em app/page.tsx. Restam 7 alterações."
- Os atalhos só funcionam enquanto o foco está dentro da revisão. São ignorados em campos de texto, e as letras na árvore de arquivos saltam para arquivos pelo nome. Depois de decidir pelo teclado, o foco vai para a próxima alteração pendente. Depois de clicar, vai para o botão Undo no mesmo lugar.
- As linhas adicionadas e removidas são lidas como "Adicionada:" e "Removida:", então não dependem de cor. As palavras alteradas são sublinhadas no modo de alto contraste, e o progresso também é exposto como uma progressbar.
- As alterações dobradas são inertes, então são ignoradas pelo Tab e pelos leitores de tela até você mostrá-las. Com movimento reduzido, dobrar, as contagens e o progresso são atualizados sem animação.
- As linhas de cada alteração são uma única parada de Tab com foco móvel, como em uma grade. A linha em foco mostra um tom e um + na margem, e o modo de cores forçadas desenha um contorno no lugar.
- Abrir o composer move o foco para dentro dele e a linha permanece destacada. Esc devolve o foco à linha de forma visível. Depois de enviar ou clicar fora, o foco volta para a linha sem indicador de foco, para que a próxima tecla de seta continue a partir dali.
- As linhas removidas e adicionadas têm uma barra de margem listrada ou sólida além dos sinais − e +, e as barras permanecem visíveis no modo de cores forçadas.
- Adicionar, editar, excluir e restaurar comentários são anunciados, e os comentários excluídos podem ser restaurados por alguns segundos.
Construído com
Os componentes gratuitos do HextaUI de que Diff Review é feito. Cada um é instalado separadamente.
Código
13 arquivos, adicionados a components/blocks/diff-review.