Comment on diff to give agent context - #1003

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

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

Conversation

@maskdotdev

@maskdotdevmaskdotdev commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

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

coderabbitaiBot commented Mar 13, 2026

Copy link
Copy Markdown

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-actionsgithub-actionsBot 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
@maskdotdevmaskdotdev changed the title Add draft diff context comments for turn diffsComment on diff to give agent contextMar 13, 2026
Comment threadapps/web/src/composerDraftStore.test.ts Outdated
Comment threadapps/web/src/composerDraftStore.ts Outdated
Comment threadapps/web/src/components/DiffPanel.logic.ts
@maskdotdev
maskdotdevforce-pushed the diff-context-comments branch from 21614f6 to 37918a0CompareMarch 13, 2026 01:48
@maskdotdevmaskdotdev mentioned this pull request Mar 13, 2026
4 tasks
@maskdotdev
maskdotdevforce-pushed the diff-context-comments branch from fac67c6 to f453ce5CompareApril 22, 2026 21:52
Comment threadapps/web/src/composerDraftStore.ts

@cursorcursorBot left a comment

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.

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 CursorFix 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 CursorFix in Web

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

@macroscopeapp

macroscopeappBot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

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.

🟠 Highchat/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.

@juliusmarminge

Copy link
Copy Markdown
Member

Closing as part of an open-PR triage — main has moved a lot since this was opened (the branch conflicts and the files it touches have been substantially reworked), so it's no longer reviewable as-is. If you're still interested, please feel free to resubmit against current main. Thanks!

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR 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

2 participants

@maskdotdev@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Comment on diff to give agent context - #1003

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

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

Conversation

@maskdotdev

@maskdotdevmaskdotdev commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

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

coderabbitaiBot commented Mar 13, 2026

Copy link
Copy Markdown

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-actionsgithub-actionsBot 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
@maskdotdevmaskdotdev changed the title Add draft diff context comments for turn diffsComment on diff to give agent contextMar 13, 2026
Comment threadapps/web/src/composerDraftStore.test.ts Outdated
Comment threadapps/web/src/composerDraftStore.ts Outdated
Comment threadapps/web/src/components/DiffPanel.logic.ts
@maskdotdev
maskdotdevforce-pushed the diff-context-comments branch from 21614f6 to 37918a0CompareMarch 13, 2026 01:48
@maskdotdevmaskdotdev mentioned this pull request Mar 13, 2026
4 tasks
@maskdotdev
maskdotdevforce-pushed the diff-context-comments branch from fac67c6 to f453ce5CompareApril 22, 2026 21:52
Comment threadapps/web/src/composerDraftStore.ts

@cursorcursorBot left a comment

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.

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 CursorFix 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 CursorFix in Web

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

@macroscopeapp

macroscopeappBot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

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.

🟠 Highchat/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.

@juliusmarminge

Copy link
Copy Markdown
Member

Closing as part of an open-PR triage — main has moved a lot since this was opened (the branch conflicts and the files it touches have been substantially reworked), so it's no longer reviewable as-is. If you're still interested, please feel free to resubmit against current main. Thanks!

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR 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

2 participants

@maskdotdev@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Comment on diff to give agent context - #1003

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

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

Conversation

@maskdotdev

@maskdotdevmaskdotdev commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

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

coderabbitaiBot commented Mar 13, 2026

Copy link
Copy Markdown

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-actionsgithub-actionsBot 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
@maskdotdevmaskdotdev changed the title Add draft diff context comments for turn diffsComment on diff to give agent contextMar 13, 2026
Comment threadapps/web/src/composerDraftStore.test.ts Outdated
Comment threadapps/web/src/composerDraftStore.ts Outdated
Comment threadapps/web/src/components/DiffPanel.logic.ts
@maskdotdev
maskdotdevforce-pushed the diff-context-comments branch from 21614f6 to 37918a0CompareMarch 13, 2026 01:48
@maskdotdevmaskdotdev mentioned this pull request Mar 13, 2026
4 tasks
@maskdotdev
maskdotdevforce-pushed the diff-context-comments branch from fac67c6 to f453ce5CompareApril 22, 2026 21:52
Comment threadapps/web/src/composerDraftStore.ts

@cursorcursorBot left a comment

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.

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 CursorFix 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 CursorFix in Web

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

@macroscopeapp

macroscopeappBot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

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.

🟠 Highchat/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.

@juliusmarminge

Copy link
Copy Markdown
Member

Closing as part of an open-PR triage — main has moved a lot since this was opened (the branch conflicts and the files it touches have been substantially reworked), so it's no longer reviewable as-is. If you're still interested, please feel free to resubmit against current main. Thanks!

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR 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

2 participants

@maskdotdev@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Comment on diff to give agent context - #1003

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

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

Conversation

@maskdotdev

@maskdotdevmaskdotdev commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

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

coderabbitaiBot commented Mar 13, 2026

Copy link
Copy Markdown

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-actionsgithub-actionsBot 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
@maskdotdevmaskdotdev changed the title Add draft diff context comments for turn diffsComment on diff to give agent contextMar 13, 2026
Comment threadapps/web/src/composerDraftStore.test.ts Outdated
Comment threadapps/web/src/composerDraftStore.ts Outdated
Comment threadapps/web/src/components/DiffPanel.logic.ts
@maskdotdev
maskdotdevforce-pushed the diff-context-comments branch from 21614f6 to 37918a0CompareMarch 13, 2026 01:48
@maskdotdevmaskdotdev mentioned this pull request Mar 13, 2026
4 tasks
@maskdotdev
maskdotdevforce-pushed the diff-context-comments branch from fac67c6 to f453ce5CompareApril 22, 2026 21:52
Comment threadapps/web/src/composerDraftStore.ts

@cursorcursorBot left a comment

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.

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 CursorFix 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 CursorFix in Web

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

@macroscopeapp

macroscopeappBot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

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.

🟠 Highchat/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.

@juliusmarminge

Copy link
Copy Markdown
Member

Closing as part of an open-PR triage — main has moved a lot since this was opened (the branch conflicts and the files it touches have been substantially reworked), so it's no longer reviewable as-is. If you're still interested, please feel free to resubmit against current main. Thanks!

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR 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

2 participants

@maskdotdev@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Comment on diff to give agent context - #1003

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

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

Conversation

@maskdotdev

@maskdotdevmaskdotdev commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

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

coderabbitaiBot commented Mar 13, 2026

Copy link
Copy Markdown

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-actionsgithub-actionsBot 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
@maskdotdevmaskdotdev changed the title Add draft diff context comments for turn diffsComment on diff to give agent contextMar 13, 2026
Comment threadapps/web/src/composerDraftStore.test.ts Outdated
Comment threadapps/web/src/composerDraftStore.ts Outdated
Comment threadapps/web/src/components/DiffPanel.logic.ts
@maskdotdev
maskdotdevforce-pushed the diff-context-comments branch from 21614f6 to 37918a0CompareMarch 13, 2026 01:48
@maskdotdevmaskdotdev mentioned this pull request Mar 13, 2026
4 tasks
@maskdotdev
maskdotdevforce-pushed the diff-context-comments branch from fac67c6 to f453ce5CompareApril 22, 2026 21:52
Comment threadapps/web/src/composerDraftStore.ts

@cursorcursorBot left a comment

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.

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 CursorFix 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 CursorFix in Web

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

@macroscopeapp

macroscopeappBot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

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.

🟠 Highchat/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.

@juliusmarminge

Copy link
Copy Markdown
Member

Closing as part of an open-PR triage — main has moved a lot since this was opened (the branch conflicts and the files it touches have been substantially reworked), so it's no longer reviewable as-is. If you're still interested, please feel free to resubmit against current main. Thanks!

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR 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

2 participants

@maskdotdev@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Comment on diff to give agent context - #1003

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

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

Conversation

@maskdotdev

@maskdotdevmaskdotdev commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

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

coderabbitaiBot commented Mar 13, 2026

Copy link
Copy Markdown

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-actionsgithub-actionsBot 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
@maskdotdevmaskdotdev changed the title Add draft diff context comments for turn diffsComment on diff to give agent contextMar 13, 2026
Comment threadapps/web/src/composerDraftStore.test.ts Outdated
Comment threadapps/web/src/composerDraftStore.ts Outdated
Comment threadapps/web/src/components/DiffPanel.logic.ts
@maskdotdev
maskdotdevforce-pushed the diff-context-comments branch from 21614f6 to 37918a0CompareMarch 13, 2026 01:48
@maskdotdevmaskdotdev mentioned this pull request Mar 13, 2026
4 tasks
@maskdotdev
maskdotdevforce-pushed the diff-context-comments branch from fac67c6 to f453ce5CompareApril 22, 2026 21:52
Comment threadapps/web/src/composerDraftStore.ts

@cursorcursorBot left a comment

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.

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 CursorFix 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 CursorFix in Web

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

@macroscopeapp

macroscopeappBot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

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.

🟠 Highchat/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.

@juliusmarminge

Copy link
Copy Markdown
Member

Closing as part of an open-PR triage — main has moved a lot since this was opened (the branch conflicts and the files it touches have been substantially reworked), so it's no longer reviewable as-is. If you're still interested, please feel free to resubmit against current main. Thanks!

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR 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

2 participants

@maskdotdev@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Comment on diff to give agent context - #1003

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

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

Conversation

@maskdotdev

@maskdotdevmaskdotdev commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

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

coderabbitaiBot commented Mar 13, 2026

Copy link
Copy Markdown

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-actionsgithub-actionsBot 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
@maskdotdevmaskdotdev changed the title Add draft diff context comments for turn diffsComment on diff to give agent contextMar 13, 2026
Comment threadapps/web/src/composerDraftStore.test.ts Outdated
Comment threadapps/web/src/composerDraftStore.ts Outdated
Comment threadapps/web/src/components/DiffPanel.logic.ts
@maskdotdev
maskdotdevforce-pushed the diff-context-comments branch from 21614f6 to 37918a0CompareMarch 13, 2026 01:48
@maskdotdevmaskdotdev mentioned this pull request Mar 13, 2026
4 tasks
@maskdotdev
maskdotdevforce-pushed the diff-context-comments branch from fac67c6 to f453ce5CompareApril 22, 2026 21:52
Comment threadapps/web/src/composerDraftStore.ts

@cursorcursorBot left a comment

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.

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 CursorFix 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 CursorFix in Web

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

@macroscopeapp

macroscopeappBot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

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.

🟠 Highchat/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.

@juliusmarminge

Copy link
Copy Markdown
Member

Closing as part of an open-PR triage — main has moved a lot since this was opened (the branch conflicts and the files it touches have been substantially reworked), so it's no longer reviewable as-is. If you're still interested, please feel free to resubmit against current main. Thanks!

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR 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

2 participants

@maskdotdev@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Comment on diff to give agent context - #1003

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

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

Conversation

@maskdotdev

@maskdotdevmaskdotdev commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

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

coderabbitaiBot commented Mar 13, 2026

Copy link
Copy Markdown

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-actionsgithub-actionsBot 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
@maskdotdevmaskdotdev changed the title Add draft diff context comments for turn diffsComment on diff to give agent contextMar 13, 2026
Comment threadapps/web/src/composerDraftStore.test.ts Outdated
Comment threadapps/web/src/composerDraftStore.ts Outdated
Comment threadapps/web/src/components/DiffPanel.logic.ts
@maskdotdev
maskdotdevforce-pushed the diff-context-comments branch from 21614f6 to 37918a0CompareMarch 13, 2026 01:48
@maskdotdevmaskdotdev mentioned this pull request Mar 13, 2026
4 tasks
@maskdotdev
maskdotdevforce-pushed the diff-context-comments branch from fac67c6 to f453ce5CompareApril 22, 2026 21:52
Comment threadapps/web/src/composerDraftStore.ts

@cursorcursorBot left a comment

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.

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 CursorFix 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 CursorFix in Web

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

@macroscopeapp

macroscopeappBot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

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.

🟠 Highchat/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.

@juliusmarminge

Copy link
Copy Markdown
Member

Closing as part of an open-PR triage — main has moved a lot since this was opened (the branch conflicts and the files it touches have been substantially reworked), so it's no longer reviewable as-is. If you're still interested, please feel free to resubmit against current main. Thanks!

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR 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

2 participants

@maskdotdev@juliusmarminge