Skip to content

Diff Review

Premium

Review an agent's proposed code edits change by change: read the reason, keep or discard each one, then apply only what you kept.

AI Agent UIAI coding agentsCode review toolsPull request assistants

Fix the time zone offset in parseDate

2 files6 lines added, 2 lines removed

Tests pass · 58 of 58

Keep the time zone offset when parsing ISO strings

export function parseDate(input: string) {
Removed: const d = new Date(input.replace(/Z|[+-]\d\d:\d\d$/, ''))
Added: const d = new Date(input)
if (Number.isNaN(d.getTime())) return null
return d
}
export function toDay(date: Date) {
return date.toISOString().slice(0, 10)
}
export function isSameDay(a: Date, b: Date) {
return toDay(a) === toDay(b)
}
// Accepts YYYY-MM-DD and returns midnight UTC.

Return null for an empty string instead of throwing

export function parseDay(input: string) {
Removed: if (!input) throw new Error('empty date')
Added: if (!input.trim()) return null
return parseDate(input + 'T00:00:00Z')

Add a test for a +09:00 offset

it('returns null for an invalid date', () => expect(parseDate('nope')).toBeNull())
Added: it('keeps a +09:00 offset', () => {
Added: const d = parseDate('2026-03-01T09:00:00+09:00')
Added: expect(d?.toISOString()).toBe('2026-03-01T00:00:00.000Z')
Added: })
})

3 left to review

Review every change first

Examples

Review multiple changes

Fix the timezone bug

2 files4 lines added, 1 line removed

58 tests pass with these changes

Keep the time zone offset when parsing ISO strings

export function parseDate(input: string) {
Removed: const d = new Date(input.replace(/Z|[+-]\d\d:\d\d$/, ''))
Added: const d = new Date(input)
if (Number.isNaN(d.getTime())) return null
return d
}

Return null for an empty string instead of throwing

export function toDay(input: string) {
Added: if (input.trim() === '') return null
return parseDate(input)?.toISOString().slice(0, 10) ?? null

A test for the offset case, with a reason long enough to wrap onto a second line in the hunk header

Added: it('keeps the offset', () => expect(parseDate('2026-11-05T09:00:00+02:00')?.getUTCHours()).toBe(7))
Added: // aVeryLongIdentifierWithoutAnySpacesThatMustWrapOrTruncateCleanlyInsideTheCard_0123456789aVeryLongIdentifierWithoutAnySpacesThatMustWrapOrTruncateCleanlyInsideTheCard_0123456789

3 left to review

Review every change first
Review one change

Rename helper

1 file1 line added, 0 lines removed

Return null for an empty string instead of throwing

export function toDay(input: string) {
Added: if (input.trim() === '') return null
return parseDate(input)?.toISOString().slice(0, 10) ?? null

1 left to review

Review every change first
No changes to review

No changes

0 files0 lines added, 0 lines removed

No changes to review

Add to your projectPremium component

One command adds this component to your project.

1

Run the following command. New project? Run npx shadcn@latest init first to set up Tailwind and path aliases.

npx shadcn@latest add @aicanvas/••••••••••••
2

For dark mode, add the dark class to your <html> element:

Optional
<html class="dark">

Install with AI Canvas MCP

With AI Canvas MCP, your AI knows every component we ship. Ask for one inside Claude Code, Codex, or Cursor and it installs the component you pick. Works with any AI Canvas account, free or premium.

Props

Every prop this component accepts. Any other prop is forwarded to the underlying element.

DiffLine

PropTypeDefaultDescription
kindreqDiffLineKind-Selects context, addition, deletion or folded-line rendering.
textreqstring-Line text, or newline-joined hidden lines when kind is fold.
oldNonumber-First old-file line number shown for the line or fold; left out, the old side is blank.
newNonumber-First new-file line number shown for the line or fold; left out, the new side is blank.
marks[number, number][]-Zero-based text ranges emphasized within the line; left out, no text is emphasized.
countnumber-Number of hidden lines represented by a fold; left out, the fold derives its count from the hidden text.

DiffHunk

PropTypeDefaultDescription
idreqstring-Stable hunk identifier that must be unique across all hunks in the files list.
reasonreqstring-Explanation shown above the hunk decision controls.
linesreqDiffLine[]-Diff lines rendered for the hunk.

DiffFile

PropTypeDefaultDescription
pathreqstring-File path used as the collapsible file heading.
hunksreqDiffHunk[]-Reviewable changes contained in the file.

ChangeReview

PropTypeDefaultDescription
titlestring-Sets the review heading; left out, the demo uses its sample title and host mode shows an empty heading, while passing it turns the demo off.
checkSummarystring-Shows a check result below the heading; left out, the demo uses its sample result and host mode shows none, while passing it turns the demo off.
filesDiffFile[]-Supplies the files and hunks to review; left out, the demo uses sample changes, while passing it turns the demo off.
onDecide(hunkId: string, decision: HunkDecision | null) => void-Reports keep, discard and cleared decisions while decisions are still tracked locally; left out, no decision updates are reported.
onApply(keptIds: string[]) => void-Receives the kept hunk ids and makes the Apply control available outside the demo; left out, host reviews have no Apply control.
onUndo() => void-Handles undoing an applied review and makes the receipt Undo control available outside the demo; left out, host receipts have no Undo control.
classNamestring-Adds classes to the outermost root; left out, only the built-in classes are used.
tuningPartial<Tuning>-Overrides review measurements, timing and motion values; left out, every value in TUNING is used.

About Diff Review

Change Review shows a code change the agent proposes as a diff per file, with Accept and Reject on every hunk and Apply once each hunk is decided. Changed words are highlighted inside changed lines so a one word edit is obvious, file tabs carry added and removed counts with a dot for undecided hunks, and long unchanged stretches fold away. J and K move between hunks, A and R decide them, arrows switch files. Apply collapses the panel into a one line receipt with a five second undo. Files, hunks and decisions are props; without them a demo reviews and applies its own change.

Built with

MotionTailwind CSS

Frequently asked questions

Is Diff Review free to use?
Diff Review is a Premium AI Canvas component. Premium unlocks the full source code, the full remix prompt, and one-command installs for every Premium component, block, and template.
How do I install Diff Review?
Premium unlocks one-command installs: run the shadcn CLI with your AI Canvas token, or connect the AI Canvas MCP server and ask your AI editor to install it for you. Premium also unlocks the full remix prompt in the Remix panel.
What is Diff Review built with?
Diff Review is built with React and TypeScript, using Motion and Tailwind CSS. It ships with both light and dark styling.
Where would I use Diff Review?
Common uses include AI coding agents, Code review tools, and Pull request assistants. Like every AI Canvas component, it is self-contained and drops into any React project.
Can I remix Diff Review with AI?
Yes. Open "Remix with AI" on this page to read the setup and the constants Diff Review is built from. The engine, the markup and the notes on how it works are part of Premium. For the exact component, install it with the one-command CLI.

Featured in collections

More like this