Diff Proposal

A model-proposed edit with accept and reject. The decision is reported, never applied — and once made, the actions are replaced by the outcome so a stale card cannot be clicked twice.

src/components/ui/diff-proposal.tsx

About

From the component's own source, where it was written.

A model-proposed edit, with accept and reject.

The decision is reported, never applied here — this component has no idea what the file system or editor buffer is. It also refuses to look decided: once a choice is made the actions are replaced by a stated outcome, so a stale card in a transcript cannot be clicked a second time.

rationale sits above the diff rather than below it. The reason for a change is what you read before deciding, and putting it after a forty-line diff means nobody reads it at all.

Install

npx astralyx-ui add diff-proposal

Usage

import { DiffProposal } from '@/components/ui/diff-proposal'

<DiffProposal
  file={file}
  rationale="The md size hard-codes a radius the token already defines."
  onDecide={(decision) => apply(decision)}
/>

Composer

Change the props and the source below follows.

Use the control radius token

The md size hard-codes rounded-lg, but --radius-control-md already defines this step. Hard-coding it means the size stops following the token.

src/lib/styles.ts+1−1
@@ -74,7 +74,7 @@ export const controlSize = {
sm: 'h-8 gap-1.5 px-3.5 text-sm',
- md: 'h-9 gap-2 px-4.5 text-sm rounded-lg',
+ md: 'h-9 gap-2 px-4.5 text-sm rounded-[var(--radius-control-md)]',
lg: 'h-10 gap-2 px-6 text-sm',
Props
tsx
<DiffProposal
  file={file}
  rationale="…"
  view="unified"
  onDecide={apply}
/>

Examples

Proposal

API

Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.

DiffProposal

Extends Omit<ComponentProps<'div'>, 'title'>.

PropTypeDefaultDescription
fileDiffFilerequiredSame shape DiffView takes — use parseUnifiedDiff when you only have patch text.
titleReactNode——
rationaleReactNode—Rendered above the diff. The reason for a change is what you read before deciding; after forty lines of diff nobody reads it.
view'unified' | 'split''unified'Passed through to DiffView.
decisionProposalDecision—Controlled or uncontrolled. The component never applies anything — it has no idea what your editor buffer is.
onDecide(decision: ProposalDecision) => void—Controlled or uncontrolled. The component never applies anything — it has no idea what your editor buffer is.
acceptLabelReactNode'Accept'—
rejectLabelReactNode'Reject'—
acceptedLabelReactNode'Accepted'Shown once the change is accepted.
rejectedLabelReactNode'Rejected'—

Types

TypeDefinitionDescription
ProposalDecision
'accepted' | 'rejected'
A model-proposed edit, with accept and reject.