Skip to content

Comment on diff to give agent context#1003

Open
maskdotdev wants to merge 5 commits into
pingdotgg:mainfrom
maskdotdev:diff-context-comments
Open

Comment on diff to give agent context#1003
maskdotdev wants to merge 5 commits into
pingdotgg:mainfrom
maskdotdev:diff-context-comments

Conversation

@maskdotdev
Copy link
Copy Markdown
Contributor

@maskdotdev maskdotdev commented Mar 13, 2026

I'm aware that this is a large PR, and a big no no per the Contributing.md..., will be opening anyways since it may serve as a basis as Julius mentioned 🫡 .

closes #79

Here's a video of how it would work:

t3code-diff-context.mp4

This PR would cover:

  • Select lines in the diff panel and attach draft comments to a specific file and line range.
  • Those pending diff comments show up in the composer as attached context, just like we do with images.
  • Sending a message includes the diff comments as structured prompt context for the model, with line range, file path, etc.
  • The chat timeline hides that raw structured block and shows it as a compact "comments attached" indicator instead.
  • Pending diff comments can be edited or deleted from the diff view before send.
  • Draft comments persist in composer draft state and recover correctly across failed sends.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included a video for animation/interaction changes

Note

Add inline diff context comments to the chat composer and diff panel

  • Users can select lines in the diff panel and attach comments that appear as inline chips in the composer, then send them as part of a message with the comment content appended to the outgoing prompt.
  • DiffPanel.logic.ts introduces path normalization utilities, line range helpers, and useDiffContextCommentDrafts hook managing draft lifecycle (create, edit, delete, cancel).
  • composerDraftStore.ts adds full diff context comment state management including persistence, hydration, and placeholder synchronization in the prompt string.
  • ComposerPromptEditor.tsx introduces ComposerDiffContextCommentNode, a new inline chip token representing a diff comment, with backspace handling and controlled update support.
  • diffContextComments.ts and promptContextBlock.ts provide serialization, extraction, and placeholder utilities shared across the feature.
  • User messages in the timeline now parse and render inline diff comment chips, and MessagesTimeline copies the original message text including comment markers.
  • Risk: the prompt now contains a private-use Unicode placeholder character (U+E000) for each inline diff comment; any prompt processing that doesn't strip placeholders will see unexpected characters.
📊 Macroscope summarized f453ce5. 17 files reviewed, 4 issues evaluated, 1 issue filtered, 1 comment posted

🗂️ Filtered Issues

apps/web/src/components/ChatView.tsx — 0 comments posted, 2 evaluated, 1 filtered
  • line 2646: In the error-recovery guard (.catch handler of onSend), prompt, images, and terminal contexts are checked via refs (promptRef.current, composerImagesRef.current, composerTerminalContextsRef.current), but diff context comments are checked by reading the store via useComposerDraftStore.getState().getComposerDraft(composerDraftTarget)?.diffContextComments.length. Because clearComposerDraftContent already cleared the store on line 2539, the store read will always be 0 regardless of whether the user added new diff-context comments between the clear and the error. If a user rapidly adds a diff-context comment while the failed send is in flight, the guard will incorrectly evaluate to true and overwrite the user's newly-added comment with the snapshot from the failed send. The other three fields avoid this problem by using refs that the ChatComposer updates synchronously on user interaction. [ Failed validation ]

Note

Medium Risk
Adds a new persisted draft-comment flow that modifies composer draft storage, send payload construction, and message rendering; bugs could lead to lost draft state or malformed prompts, but changes are scoped to the chat/diff UI.

Overview
Enables selecting lines in the diff viewer to create/edit/delete draft diff comments that are attached to the composer as inline chips and persisted per-thread.

On send, the composer now appends a structured <diff_context_comments> block (with inline @diff: labels replacing placeholder tokens) and treats pending diff comments as sendable content even when the text prompt is empty; failed sends restore prompt/images/terminal contexts/diff comments from a single snapshot.

Adds a reusable promptContextBlock parser/builder and refactors terminal context serialization to use it, plus updates the editor/cursor tokenization and timeline rendering to recognize and display diff-comment chips while hiding the raw trailing block.

Reviewed by Cursor Bugbot for commit 1f98db7. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 13, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 97c9f967-5ebb-46d6-b368-1888a6c8f395

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ 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.

@github-actions github-actions Bot added size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Mar 13, 2026
@maskdotdev maskdotdev changed the title Add draft diff context comments for turn diffs Comment on diff to give agent context Mar 13, 2026
Comment thread apps/web/src/composerDraftStore.test.ts Outdated
Comment thread apps/web/src/composerDraftStore.ts Outdated
Comment thread apps/web/src/components/DiffPanel.logic.ts
@maskdotdev maskdotdev force-pushed the diff-context-comments branch from fac67c6 to f453ce5 Compare April 22, 2026 21:52
Comment thread apps/web/src/composerDraftStore.ts
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1f98db7. Configure here.

images: composerImages,
persistedAttachments: composerPersistedAttachments,
terminalContexts: composerTerminalContexts,
diffContextComments: composerDiffContextComments,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Send state derived inconsistently for diff context comments

Medium Severity

deriveComposerSendState doesn't account for diffContextComments, so hasSendableContent is false when only diff comments are pending. ChatComposer patches this by overriding hasSendableContent after the fact, but the onSend handler in ChatView.tsx calls deriveComposerSendState independently and works around it with a separate hasPendingDiffContextComments check. This split means the expired-terminal-context toast logic at line 2431 can still fire even when there are valid diff comments to send — the hasSendableContent is false, so the code enters the "no sendable content" branch and shows a misleading warning before continuing.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1f98db7. Configure here.

extractedDiffComments.promptText,
);
const terminalContexts = displayedUserMessage.contexts;
const userMessageCopyText = row.message.text;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copy button exposes raw XML context block to user

Medium Severity

The user message copy text was changed from displayedUserMessage.copyText (which was the text after terminal context extraction) to row.message.text (the raw message including the <diff_context_comments> XML block). Clicking the copy button now puts the raw structured XML block into the user's clipboard, which is an unintended UX regression — the hidden context block is specifically designed to be invisible to the user.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1f98db7. Configure here.

@macroscopeapp
Copy link
Copy Markdown
Contributor

macroscopeapp Bot commented Apr 22, 2026

Approvability

Verdict: Needs human review

1 blocking correctness issue found. This PR introduces a new user-facing feature (diff context comments) with significant scope across multiple core components. Three unresolved review comments identify bugs: inconsistent send state handling, a copy-to-clipboard regression exposing raw XML, and a high-severity issue causing terminal context labels to appear twice. The combination of new feature complexity and identified bugs warrants human review.

You can customize Macroscope's approvability policy. Learn more.

Comment on lines +723 to +756
const inlineEntries = [
...props.terminalContexts.map((context) => ({
kind: "terminal" as const,
key: `user-terminal-context-inline:${context.header}`,
label: formatInlineTerminalContextLabel(context.header),
node: (
<UserMessageTerminalContextInlineLabel
key={`user-terminal-context-inline:${context.header}`}
context={context}
/>
),
})),
...props.diffContextComments.map((comment) => ({
kind: "diff" as const,
key: `user-diff-context-comment-inline:${comment.header}`,
label: formatInlineDiffContextCommentLabel(comment.header),
node: (
<UserMessageDiffContextCommentInlineLabel
key={`user-diff-context-comment-inline:${comment.header}`}
comment={comment}
/>
),
})),
];
const matchedInlineEntries = inlineEntries
.map((entry) => ({ ...entry, matchIndex: props.text.indexOf(entry.label) }))
.filter((entry) => entry.matchIndex >= 0)
.toSorted((left, right) => left.matchIndex - right.matchIndex);
let cursor = 0;

for (const context of props.terminalContexts) {
const label = formatInlineTerminalContextLabel(context.header);
const matchIndex = props.text.indexOf(label, cursor);
if (matchIndex === -1) {
inlineNodes.length = 0;
break;
}
if (matchIndex > cursor) {
inlineNodes.push(
<span key={`user-terminal-context-inline-before:${context.header}:${cursor}`}>
{props.text.slice(cursor, matchIndex)}
</span>,
);
if (matchedInlineEntries.length === inlineEntries.length) {
for (const entry of matchedInlineEntries) {
const matchIndex = props.text.indexOf(entry.label, cursor);
if (matchIndex < cursor) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 High chat/MessagesTimeline.tsx:723

When only terminal context labels are embedded in the message text (without diff comment labels), the code enters the inline replacement block but fails the matchedInlineEntries.length === inlineEntries.length check because inlineEntries contains both terminal and diff entries. It then falls back to appending all chips followed by the full props.text, causing embedded terminal labels to appear twice: once as chips and once as raw text.

-    if (hasEmbeddedInlineLabels || hasEmbeddedDiffLabels) {
+    if (hasEmbeddedInlineLabels || hasEmbeddedDiffLabels) {
       const inlineEntries = [
         ...props.terminalContexts.map((context) => ({
           kind: "terminal" as const,
@@ -747,3 +747,5 @@
         ...props.diffContextComments.map((comment) => ({
           kind: "diff" as const,
           key: `user-diff-context-comment-inline:${comment.header}`,
           label: formatInlineDiffContextCommentLabel(comment.header),
           node: (
@@ -751,2 +753,6 @@
           ),
         })),
-      ];
+      ].filter((entry) =>
+        entry.kind === "terminal"
+          ? hasEmbeddedInlineLabels
+          : hasEmbeddedDiffLabels
+      );
🤖 Copy this AI Prompt to have your agent fix this:
In file apps/web/src/components/chat/MessagesTimeline.tsx around lines 723-756:

When only terminal context labels are embedded in the message text (without diff comment labels), the code enters the inline replacement block but fails the `matchedInlineEntries.length === inlineEntries.length` check because `inlineEntries` contains both terminal and diff entries. It then falls back to appending all chips followed by the full `props.text`, causing embedded terminal labels to appear twice: once as chips and once as raw text.

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

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Comment on diff to give agent context

1 participant