Skip to content

feat: add AI diff preview workflow#469

Open
Bhagy-Yelleti wants to merge 2 commits into
piyushdotcomm:mainfrom
Bhagy-Yelleti:feat/ai-diff-preview
Open

feat: add AI diff preview workflow#469
Bhagy-Yelleti wants to merge 2 commits into
piyushdotcomm:mainfrom
Bhagy-Yelleti:feat/ai-diff-preview

Conversation

@Bhagy-Yelleti

@Bhagy-Yelleti Bhagy-Yelleti commented Jun 4, 2026

Copy link
Copy Markdown

Summary

This PR implements an AI Diff Preview workflow before AI-generated code changes are applied to the editor.

Previously, AI-generated edits could be applied directly, making it difficult for users to understand what was changed or verify modifications before they affected their files. This update introduces a review step that allows users to inspect proposed changes and decide whether to apply or discard them.

What changed

  • Added a new AIDiffPreview component to display pending AI-generated file changes.
  • Introduced a review workflow that stages AI edits before applying them.
  • Added support for reviewing both single-file and multi-file AI modifications.
  • Implemented a visual diff preview showing added, removed, and modified lines.
  • Added Accept and Reject actions so users can control whether the changes should be applied.
  • Updated the AI chat flow to store pending changes and wait for user confirmation before writing modifications to the editor.
  • Preserved the existing editing workflow when changes are accepted.

Why it changed

The goal of this feature is to improve transparency and user control when working with AI-generated code.

Instead of immediately applying modifications, users can now review the proposed changes first, making it easier to:

  • Understand what the AI changed
  • Prevent accidental overwrites of existing code
  • Review larger refactors more confidently
  • Build trust in AI-assisted development workflows

Type of change

  • Bug fix
  • New feature
  • Refactor
  • Documentation
  • Test or CI improvement
  • Starter template change

Related issue

Closes #433

Validation

  • npm run lint
  • npm test
  • npm run build

List any additional manual verification you performed:

  • Tested the AI diff preview UI locally.
  • Verified that pending AI-generated changes are shown before being applied.
  • Verified Accept action applies the staged changes.
  • Verified Reject action discards the staged changes.
  • Tested review flow for both single-file and multi-file edits.
  • Confirmed existing editor functionality continues to work after accepting changes.

Checklist

  • I kept this PR focused on one primary change
  • I updated documentation if behavior changed
  • I did not commit secrets, local logs, or scratch files
  • I am requesting review on the correct scope

Summary by CodeRabbit

  • New Features

    • Added an AI review workflow: view per-file diff previews of pending AI changes and Accept or Reject them before they are applied.
  • Improvements

    • AI file-edit handling is more robust: edits are queued for review, read/delete actions remain immediate, and tool execution now respects streaming/duplicate guards and clearer status indicators.

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@github-actions github-actions Bot added the enhancement New feature or request label Jun 4, 2026
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

👋 Thanks for opening a PR, @Bhagy-Yelleti!

Your PR has entered the 🚦 PR Review Pipeline.

Standard PR detected — your PR will follow the standard review pipeline.


What happens next

Stage Reviewer Checks
Stage 1 — Automated Validation 🤖 Bot DCO · Format · AI/Slop · Duplicate
Stage 2 — Human Review 👥 Maintainer Code + Quality Review
Stage 3 — PA / Maintainer Review 🔑 Project Admin Final Merge Decision

A pipeline status comment will appear below and update automatically as your PR progresses.


While you wait

  • Sign all commits (git commit -s)
  • Link your issue (Closes #123)
  • Use a feature branch (not main)
  • Avoid unrelated changes

This comment is posted only once.

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 08e1f8e6-2212-4278-85dd-d637bcc4304d

📥 Commits

Reviewing files that changed from the base of the PR and between 384da6f and 45773bc.

📒 Files selected for processing (1)
  • modules/playground/components/ai-chat-panel.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • modules/playground/components/ai-chat-panel.tsx

Walkthrough

This PR implements an AI Diff Preview workflow: AI-generated edit_file and edit_multiple_files operations are now captured into pending state instead of applied immediately. A new AIDiffPreview component displays the proposed changes with green/red highlighting and accept/reject buttons. The chat panel integrates this review UI so users can review and approve AI modifications before they are persisted.

Changes

AI Diff Preview Feature

Layer / File(s) Summary
Review workflow state and handlers
modules/playground/components/ai-chat-panel.tsx
pendingChanges and isReviewPending state track unpplied edits; handleAcceptChanges applies updates to files and persists via saveTemplateData; handleRejectChanges discards and closes the review UI. PendingChange interface structures file diffs awaiting review.
Tool execution refactor for edit diffs
modules/playground/components/ai-chat-panel.tsx
Tool-part processing adds processedToolCallIds set to skip re-execution and streaming guards. edit_file and edit_multiple_files now capture paths and old/new content into pendingChanges and return a "pending review" result instead of applying immediately. read_file and delete_file execute directly. Debug logging added for tool detection.
Diff preview component and rendering
modules/playground/components/ai-diff-preview.tsx
New AIDiffPreview React component displays file-by-file diffs using diffLines, truncated to 100 non-empty lines with green/red styling. UI shows file list with "New file" labels for additions and footer buttons for accept/reject actions.
Chat panel integration with review UI
modules/playground/components/ai-chat-panel.tsx
AIDiffPreview component integrated into panel render path, driven by pendingChanges and isReviewPending, with accept/reject handlers wired. Message input, tool display, and provider picker UI coexist with the review workflow.
Chat UI utilities and keyboard handling
modules/playground/components/ai-chat-panel.tsx
Keyboard handling to send on Enter, chat clearing via setMessages([]), and provider display logic preserved. Send-message gating updated to consider unresolved tool calls robustly.
sequenceDiagram
  participant AIModel
  participant ChatPanel as ai-chat-panel
  participant DiffPreview as AIDiffPreview
  participant Storage as TemplateStorage
  participant User

  AIModel->>ChatPanel: emits tool part (edit/read/delete)
  ChatPanel->>ChatPanel: create pendingChanges & set isReviewPending (for edits)
  ChatPanel->>DiffPreview: show diffs (isOpen)
  User->>DiffPreview: Accept / Reject
  DiffPreview->>ChatPanel: onAccept / onReject
  ChatPanel->>Storage: saveTemplateData (apply) / discard (reject)
Loading

🎯 3 (Moderate) | ⏱️ ~22 minutes

🐰 AI once moved swift and sure,
But now we pause—let changes endure!
A diff so bright, in green and red,
Accept or not, this thought we've led.
Review before the code takes flight,
A rabbit's gift—the wisdom's right! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add AI diff preview workflow' clearly and directly summarizes the primary change: introducing a diff preview workflow for AI-generated code changes.
Description check ✅ Passed The PR description follows the template structure with Summary, Type of change, Related issue, Validation, and Checklist sections; all required elements are present and well-detailed.
Linked Issues check ✅ Passed All coding requirements from issue #433 are met: diff preview displays pending changes before applying, added/removed/modified lines are visually highlighted, accept/reject functionality is implemented, and existing editor functionality is preserved.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the AI diff preview workflow. The refactoring in ai-chat-panel.tsx and new AIDiffPreview component align with the feature requirements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@modules/playground/components/ai-chat-panel.tsx`:
- Around line 90-93: The current pendingChanges state (PendingChange | null) is
overwritten on every edit_file/edit_multiple_files result, losing earlier staged
diffs; change pendingChanges to hold a collection (e.g., PendingChange[]) or
implement a locking/queue so new tool results are appended instead of replacing
existing ones, and prevent new appends while a review is being resolved if you
want a lock. Update all usages and setters (pendingChanges, setPendingChanges)
and the handlers that process edit_file/edit_multiple_files (and places around
isReviewPending) to push/merge incoming changes into the list (or enqueue them)
and adjust the UI/consumer logic to iterate over the array rather than assuming
a single PendingChange. Ensure any code that clears or accepts changes drains
the queue appropriately.
- Around line 246-257: The client code in AIChatPanel that constructs toolParts
from rawParts and logs JSON.stringify(toolParts, null, 2) leaks full tool
payloads (including file contents); remove that detailed logging and instead log
only safe metadata (e.g., tool type, name, and size) or nothing at all, and wrap
any dev-only metadata logging behind an environment/dev-mode guard (use existing
dev flag or process.env.NODE_ENV === "development"); also find the duplicate raw
tool-payload console.log occurrences elsewhere in the same component (the other
console.log calls that output toolParts) and apply the same removal/replacement
with metadata-only, dev-guarded logging.
- Around line 210-214: The open-buffer reconciliation incorrectly matches
buffers using change.path.endsWith(fullName), causing different files with the
same filename to be updated; change the comparison to match the exact file path
used by the rest of the system (e.g., use change.path === f.path or the
equivalent fullPath property on the open file object) inside the
currentOpenFiles map/update logic (the block that builds fullName and returns {
...f, content: change.newContent, hasUnsavedChanges: true }) and make the same
exact-path change in the other accept/delete handler (the similar code around
the second occurrence). Ensure this uses the same path identifier as
findFileByPath / addOrUpdateFile / deleteFileByPath so open buffers are
reconciled by exact path.
- Around line 130-144: The unresolved-tool detection in hasUnresolvedTools
currently only checks p.toolInvocation.state === "call", which misses AI SDK v3
static tool parts that lack toolInvocation; update the predicate in the
parts.some(...) (used with hasUnresolvedTools / lastMessage / parts /
MessagePart) to also treat v3 tool parts as unresolved when p.type
startsWith("tool-") and either p.toolInvocation?.state === "call" OR
(p.toolCallId is present and p.state === "call"); ensure you check p.toolCallId
and p.state safely (type guard for object) so v3 parts with toolCallId/state are
considered unresolved.
- Around line 220-227: The code applies UI updates and clears the pending review
before persistence completes; change the flow so
saveTemplateData(updatedTemplate) is awaited and only on success call
setTemplateData(updatedTemplate), setOpenFiles(currentOpenFiles),
toast.success(...), setPendingChanges(null), and setIsReviewPending(false); on
failure do not clear pendingChanges or close the review—catch the error, surface
an error toast (or processLogger/error handler) and leave pendingChanges intact
so the user can retry. Use the existing saveTemplateData, setTemplateData,
setOpenFiles, setPendingChanges, setIsReviewPending, pendingChanges,
currentItems and currentOpenFiles identifiers to locate and update the logic.

In `@modules/playground/components/ai-diff-preview.tsx`:
- Around line 24-29: The component currently only supports global accept/reject
via AIDiffPreviewProps (onAccept, onReject) and renders all-or-nothing footer
buttons; update it to support file-by-file actions by extending the props and
UI: change AIDiffPreviewProps to accept either a PendingChange[] or a
PendingChange that contains a files array and add per-file handlers (e.g.,
onAcceptFile(fileId: string) and onRejectFile(fileId: string)) alongside the
existing global onAccept/onReject, then update the AIDiffPreview component
render logic (where it maps pending changes / files — see the file list
rendering around lines 136-173) to show Accept/Reject buttons for each file
entry that call the new handlers and keep the footer global buttons for “accept
all/reject all” wired to onAccept/onReject; ensure state updates or callback
payloads include the file identifier so the parent can process per-file
decisions.
- Around line 55-88: renderDiffPreview currently truncates the top-of-file
output from diffLines and can omit the first changed hunk; change truncation to
be hunk-aware by ensuring at least one part with added/removed === true is
included. Iterate diffParts (diffLines) accumulating lines into truncatedDiff up
to maxLines as you do now, but if you reach maxLines without encountering any
changed part, locate the first part where part.added||part.removed and
replace/truncate the accumulated context to include that hunk plus a small
configurable context (e.g., N lines before/after) while still respecting
maxLines; update uses of truncatedDiff, DiffPart, renderDiffPreview, maxLines
and lineCount to implement this logic so previews always include the first edit
hunk.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a726c39a-017c-4632-8263-50370edb94a4

📥 Commits

Reviewing files that changed from the base of the PR and between f12b223 and 384da6f.

📒 Files selected for processing (2)
  • modules/playground/components/ai-chat-panel.tsx
  • modules/playground/components/ai-diff-preview.tsx

Comment on lines +90 to +93
const [pendingChanges, setPendingChanges] = useState<PendingChange | null>(
null,
);
const [isReviewPending, setIsReviewPending] = useState(false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Queue or lock staged edits instead of overwriting them.

pendingChanges only holds one tool result, and every edit_file / edit_multiple_files call replaces it. If the model emits multiple edit tool parts in one assistant message, or the user sends another prompt before resolving the review, earlier staged diffs are silently discarded.

Also applies to: 147-149, 323-328, 362-367

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/playground/components/ai-chat-panel.tsx` around lines 90 - 93, The
current pendingChanges state (PendingChange | null) is overwritten on every
edit_file/edit_multiple_files result, losing earlier staged diffs; change
pendingChanges to hold a collection (e.g., PendingChange[]) or implement a
locking/queue so new tool results are appended instead of replacing existing
ones, and prevent new appends while a review is being resolved if you want a
lock. Update all usages and setters (pendingChanges, setPendingChanges) and the
handlers that process edit_file/edit_multiple_files (and places around
isReviewPending) to push/merge incoming changes into the list (or enqueue them)
and adjust the UI/consumer logic to iterate over the array rather than assuming
a single PendingChange. Ensure any code that clears or accepts changes drains
the queue appropriately.

Comment thread modules/playground/components/ai-chat-panel.tsx
Comment on lines +210 to +214
currentOpenFiles = currentOpenFiles.map((f) => {
const ext = f.fileExtension ? `.${f.fileExtension}` : "";
const fullName = `${f.filename}${ext}`;
if (change.path.endsWith(fullName)) {
return { ...f, content: change.newContent, hasUnsavedChanges: true };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Match open buffers by exact path, not filename suffix.

Both accept/delete paths reconcile openFiles with change.path.endsWith(fullName). That will update or close every open index.tsx when only one of src/index.tsx or tests/index.tsx changed. The underlying file mutations are already path-driven via findFileByPath / addOrUpdateFile / deleteFileByPath in modules/playground/hooks/useAI.ts:168-291, so the open-buffer contract needs the same identifier.

Also applies to: 381-384

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/playground/components/ai-chat-panel.tsx` around lines 210 - 214, The
open-buffer reconciliation incorrectly matches buffers using
change.path.endsWith(fullName), causing different files with the same filename
to be updated; change the comparison to match the exact file path used by the
rest of the system (e.g., use change.path === f.path or the equivalent fullPath
property on the open file object) inside the currentOpenFiles map/update logic
(the block that builds fullName and returns { ...f, content: change.newContent,
hasUnsavedChanges: true }) and make the same exact-path change in the other
accept/delete handler (the similar code around the second occurrence). Ensure
this uses the same path identifier as findFileByPath / addOrUpdateFile /
deleteFileByPath so open buffers are reconciled by exact path.

Comment thread modules/playground/components/ai-chat-panel.tsx Outdated
Comment thread modules/playground/components/ai-chat-panel.tsx Outdated
Comment on lines +24 to +29
interface AIDiffPreviewProps {
pendingChanges: PendingChange | null;
isOpen: boolean;
onAccept: () => void;
onReject: () => void;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

The file-by-file review requirement still isn't implemented.

The linked objective calls for accepting/rejecting pending changes globally or file-by-file, but this component only exposes global onAccept / onReject handlers and renders all-or-nothing footer buttons. In a multi-file edit, users still can't keep one file and reject another.

Also applies to: 136-173

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/playground/components/ai-diff-preview.tsx` around lines 24 - 29, The
component currently only supports global accept/reject via AIDiffPreviewProps
(onAccept, onReject) and renders all-or-nothing footer buttons; update it to
support file-by-file actions by extending the props and UI: change
AIDiffPreviewProps to accept either a PendingChange[] or a PendingChange that
contains a files array and add per-file handlers (e.g., onAcceptFile(fileId:
string) and onRejectFile(fileId: string)) alongside the existing global
onAccept/onReject, then update the AIDiffPreview component render logic (where
it maps pending changes / files — see the file list rendering around lines
136-173) to show Accept/Reject buttons for each file entry that call the new
handlers and keep the footer global buttons for “accept all/reject all” wired to
onAccept/onReject; ensure state updates or callback payloads include the file
identifier so the parent can process per-file decisions.

Comment on lines +55 to +88
const renderDiffPreview = (oldContent: string, newContent: string) => {
const diff = diffLines(oldContent, newContent);
const maxLines = 100;
let lineCount = 0;
const truncatedDiff: DiffPart[] = [];

for (const part of diff) {
const lines = part.value
.split("\n")
.filter((line: string) => line !== "");
if (lineCount + lines.length > maxLines) {
const remaining = maxLines - lineCount;
if (remaining > 0) {
const truncatedValue = part.value
.split("\n")
.slice(0, remaining)
.join("\n");
truncatedDiff.push({
value:
truncatedValue +
(remaining < lines.length ? "\n... (truncated)" : ""),
added: part.added,
removed: part.removed,
});
}
break;
}
truncatedDiff.push({
value: part.value,
added: part.added,
removed: part.removed,
});
lineCount += lines.length;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

This truncation can hide the actual edit hunk.

The preview clips the first 100 lines of the raw diffLines output. For a large file whose first change is after line 100, the card shows only unchanged context and never renders the added/removed lines the user is supposed to approve. Truncation needs to be hunk-aware, not top-of-file aware.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/playground/components/ai-diff-preview.tsx` around lines 55 - 88,
renderDiffPreview currently truncates the top-of-file output from diffLines and
can omit the first changed hunk; change truncation to be hunk-aware by ensuring
at least one part with added/removed === true is included. Iterate diffParts
(diffLines) accumulating lines into truncatedDiff up to maxLines as you do now,
but if you reach maxLines without encountering any changed part, locate the
first part where part.added||part.removed and replace/truncate the accumulated
context to include that hunk plus a small configurable context (e.g., N lines
before/after) while still respecting maxLines; update uses of truncatedDiff,
DiffPart, renderDiffPreview, maxLines and lineCount to implement this logic so
previews always include the first edit hunk.

@Bhagy-Yelleti

Copy link
Copy Markdown
Author

Hi @piyushdotcomm I've made the requested changes and pushed the updates.
I tested everything locally and the checks are passing now. Whenever you're free, could you please review the PR?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Add AI Diff Preview Before Applying Code Changes

1 participant