Code suggestion
ProAn AI code edit suggestion where the model's proposed changes stream into a small editor panel as inline diff hunks, and you accept or reject each one, or all at once with Cmd+Enter. Accepted hunks fold into the file with a smooth height change.
Add retry with backoff
Claude Sonnet 5.5
Reading fetch-invoice.ts
- Showing an AI's proposed edit to a file as an inline diff the person accepts or rejects hunk by hunk.
- Approving an agent's file change before it is written, with a clear record of what was kept.
- Landing pages and docs that demonstrate an AI coding feature with a realistic, self playing edit.
- Use a dedicated diff tool for large multi file diffs or side by side comparisons; this card is for one file and a handful of hunks.
- Use code-magic-move to animate code from one version to another without a review step.
- Use code-block to show code without changes.
- Use tool-call for an agent step that is not a file edit.
- Use agent-approvals when the person approves actions in general (emails, payments, deploys) rather than code.
Installation
Pro source and install commands unlock with a Pro plan.
import { CodeSuggestion, type CodeSuggestionHunk } from "@/components/arc/code-suggestion/code-suggestion";import { ClaudeMark } from "@/components/arc/code-suggestion/code-suggestion-marks"; const hunks: CodeSuggestionHunk[] = [ { id: "timeout", start: 2, remove: 1, summary: "Add a 10s timeout", insert: [ " const res = await fetch(url, { signal: AbortSignal.timeout(10_000) });", ] },]; export function InlineEdit({ file, onSave }: { file: { name: string; text: string }; onSave: (text: string) => void }) { return ( <CodeSuggestion code={file.text} hunks={hunks} fileName={file.name} instruction="Time out slow requests" model={{ name: "Claude Opus 5.5", mark: <ClaudeMark aria-hidden style={{ color: "#D97757" }} /> }} onResolve={({ code }) => onSave(code)} /> );}CodeSuggestion
An AI code edit suggestion in a small editor panel, like an inline edit in Cursor or GitHub Copilot. The header shows the model mark, the instruction and the model; the panel shows a file tab with its language mark, a running +/− count and the code with each proposed change as an inline diff hunk: removed lines tinted red with a minus, new lines tinted green with a plus. New lines stream in, then every hunk gets Reject and Accept under it, and the footer offers Reject all and Accept all (Cmd or Ctrl + Enter). Accepting a hunk folds its removed lines away and settles the new lines into the file; rejecting folds the new lines away and restores the original. With no props it plays a simulated suggestion on a sample billing helper (Add retry with backoff, Claude Sonnet 5.5) that reviews itself and replays until someone touches it; pass code and hunks to show a real one.
codestringsample fetch-invoice.tsThe original file contents, lines split on \n.hunksCodeSuggestionHunk[]–The proposed changes. Each is { id, start, remove?, insert?, summary? }: it replaces `remove` lines starting at the zero based line `start` with the `insert` lines (remove 0 inserts before start, an empty insert deletes). Overlapping hunks after the first are ignored. Leave it unset to play the built-in simulated suggestion.status"thinking" | "streaming" | "ready" | "error""ready"Controlled status when you pass hunks. thinking shows a shimmer label over the plain file; streaming shows hunks as they grow (append lines to insert and they fade in); ready shows Accept and Reject; error shows the error message and Retry. Going back to thinking clears earlier decisions.instructionstring"Add retry with backoff"What the person asked for, shown as the header title.model{ name: string; mark?: ReactNode } | nullClaude Sonnet 5.5 with the Claude markThe model that wrote the suggestion. Mono marks take the text color; pass style={{ color }} for the brand color. null hides the model line.fileNamestring"fetch-invoice.ts"File name in the tab.filePathstring"src/billing" for the sampleFolder shown after the file name from a 520px card.fileMarkReactNode | nullTypeScript markLanguage mark in the tab. null hides it.errorstring"The model stopped responding. Nothing was changed."Message for the error status.onDecision(hunk: CodeSuggestionHunk, decision: "accepted" | "rejected", code: string) => void–Called for every hunk accepted or rejected (Accept all calls it once per hunk), with the file as it now stands: accepted hunks applied, the rest original.onResolve(result: { code: string; accepted: string[]; rejected: string[] }) => void–Called once every hunk has a decision, with the final code and the hunk ids. Write the file here.onStop() => void–Shows Stop while thinking or streaming. Abort your request here. The simulated suggestion stops on its own and keeps only the hunks it finished.onRegenerate() => void–Shows Regenerate once the review is done (or there were no changes) and Retry on error. The simulated suggestion replays on its own.autoPlaybooleantrueSimulated suggestion only: after streaming it accepts and rejects hunks on its own (alternating one by one and Accept all) and replays about 2.6s after it finishes. Any pointer, key or focus inside the card hands control to the person for that run. Off under reduced motion.shortcutsbooleantrueCmd or Ctrl + Enter accepts all and Cmd or Ctrl + Backspace rejects all while focus is inside the card. Accept all shows the hint.maxCodeHeightnumber–Caps the code area height in pixels; longer files scroll inside it.classNamestring–Class on the root element.styleCSSProperties–Inline style on the root.- Tab
- Moves to the code area (scrollable, and where the shortcuts work), then each hunk's Reject and Accept, then the footer actions.
- EnterorSpace
- Presses the focused Accept, Reject, Accept all, Reject all, Stop or Regenerate. After a hunk is decided, focus moves to the next pending hunk's Accept, or to the code area when none are left.
- CmdorCtrl+Enter
- Accepts every pending hunk while focus is inside the card.
- CmdorCtrl+Backspace
- Rejects every pending hunk while focus is inside the card.
- Arrow leftorArrow right
- Scrolls long lines when the code area has focus.
- The card is a labelled group ("Code suggestion: Add retry with backoff") and the code is a focusable region named after the file.
- Each pending removed or added line starts with a visually hidden "Removed:" or "Added:", so the diff never relies on the red and green tints; the visible minus and plus signs carry it for sighted readers too.
- Accept and Reject name their hunk ("Accept Retry 429 and 5xx with backoff"). Accept all and Reject all expose their shortcuts with aria-keyshortcuts.
- A polite status region speaks the state ("3 changes to review"), each decision ("Accepted: Add retry limits. 2 changes to review") and the result. The simulated suggestion adds "Simulated, nothing was written to disk" when it finishes. Streaming is announced once, not per line.
- Status always pairs an icon with a label: a spinner for reading and writing, a dot for review, a check for applied, a cross for rejected and an alert for errors.
- Focus never falls back to the page when the pressed control leaves with its hunk or state. Pointer focus draws no ring; keyboard focus uses Arc's shared focus outline.
- Thinking lasts about 0.8s with a calm shimmer on "Reading fetch-invoice.ts". Then each hunk opens: its removed lines tint red in 240ms and its new lines stream in one every 38ms, each opening to its 20px row in 120ms while it fades in. The whole sample streams in about 1.6s.
- Review bars open under every hunk at once on a critically damped spring (0.24s, no bounce).
- Accept folds the removed lines and the bar to zero height on the same spring (0.26s), so the code below glides up, while the green tint and the plus signs fade out. Reject does the same for the new lines and lets the red fade. Rows have fixed heights, so nothing is measured mid animation and nothing jitters.
- The footer status icon, label and actions each swap with one short crossfade in place (old out in 100ms, new in 160ms, the label with a 2px rise). The icon only swaps when its kind changes, so the spinner keeps turning from Reading to Writing and a count change swaps only the text. Zero counts in the tab turn muted.
- Reduced motion opens on the finished suggestion with no streaming, no autoplay and instant folds; decisions still apply and the status still updates. Autoplay waits until the card is in view and its replay waits until the card is back in view.
- The card fills its container up to 720px. Long lines scroll sideways inside the code area while the line numbers stay pinned on the left and each review bar stays pinned to the visible width, so Accept is always in reach.
- The folder after the file name hides below a 520px card.
- Below a 440px card the gutter narrows, code drops to 12px, hunk summaries hide (the buttons keep their names for screen readers) and the footer stacks the status above full width actions. It holds down to 320px without page scroll.
- Rows are fixed 20px lines animated between known heights; nothing is measured during animation.
- Each line is highlighted once and memoized by its text, so streaming a new line never re-highlights the others.
- The simulation runs on single timeouts per step, starts only when the card is in view and stops its loop when it scrolls away. Pass maxCodeHeight for long files.
Notes for AI
Give your coding assistant the Markdown reference instead of screenshots.
- Use it wherever an AI proposes edits to one file and a person approves them: inline edit in an editor, a code review assistant, a config or SQL change preview, an agent's file edit approval.
- Map a unified diff to hunks: start is the zero based line of the first removed line (or the insert position), remove is the removed line count and insert the added lines. Keep hunks sorted and non overlapping, and give each a summary of two to six words.
- To stream, set status="streaming" and grow the hunks array and each hunk's insert as tokens arrive, then set status="ready". Wire onStop to abort the request with an AbortController.
- Write the file in onResolve (or progressively in onDecision); the component never writes anything itself. Say in surrounding copy when content is a sample.
- Copy marks from code-suggestion-marks.tsx (Claude, OpenAI, TypeScript) or pass any ReactNode with aria-hidden. Brand marks are sample content, never an implied endorsement.
- The highlighter is a small TypeScript and JavaScript tokenizer. For other languages, the code still renders cleanly in the plain text color.
The full library index for assistants is at /llms.txt.