Skip to content

Add GitHub-style suggestion blocks for code review comments#199

Open
takumi0706 wants to merge 9 commits intoyoshiko-pg:mainfrom
takumi0706:feature/suggestion-comments
Open

Add GitHub-style suggestion blocks for code review comments#199
takumi0706 wants to merge 9 commits intoyoshiko-pg:mainfrom
takumi0706:feature/suggestion-comments

Conversation

@takumi0706
Copy link

@takumi0706 takumi0706 commented Feb 4, 2026

Summary

  • Add suggestion block feature that allows reviewers to propose code changes directly in comments using ```suggestion syntax
  • Display suggestions in GitHub-style diff view with original/suggested code comparison (red for removal, green for addition)
  • Include structured prompt output format (ORIGINAL/SUGGESTED blocks) for AI-assisted code review workflows
  • Add "Suggestion" button to comment form that inserts a template with the selected code

Testing

  • Unit tests added for suggestion parsing and comment formatting utilities
  • Manual testing needed for UI rendering

For Example

UI

image

Prompt

src/client/components/SideBySideDiffChunk.tsx:L424-L425
ORIGINAL:
``
                      const target = e.target;
                      if (!(target instanceof HTMLElement)) return;
``
SUGGESTED:
``
                      const target = e.target as HTMLlement;
``

Closes #197

Add TypeScript interface for parsed suggestion block data structure
containing original code, suggested code, language, and position info.
- hasSuggestionBlock: Check if text contains suggestion block
- parseSuggestionBlocks: Parse suggestion blocks with original code
- createSuggestionTemplate: Generate suggestion template for editor
- extractFirstSuggestion: Extract first suggestion code from body
- formatSuggestionForPrompt: Format suggestion for AI prompt output

Includes comprehensive test coverage (20 tests).
- Add selectedCode and language props
- Add toolbar with "Add suggestion" button
- Button inserts suggestion template into textarea
- Button disabled when no code is selected
- Add getSelectedCodeContent() to extract code from selected lines
- Pass selectedCode and language props to CommentForm
- Include codeContent when adding comments for suggestion support
- Add SuggestionBlockRenderer component
- Display original code with red background and "-" prefix
- Display suggested code with green background and "+" prefix
- Parse suggestion blocks using suggestionUtils
- Format suggestions with ORIGINAL/SUGGESTED blocks for AI prompts
- Extract context text before suggestion blocks
- Add tests for suggestion prompt formatting
- Replace type assertions with instanceof checks in SideBySideDiffChunk
- Fix language detection to use file path instead of code content
- Simplify hasSuggestionBlock regex without global flag
Pass codeSnapshot.content to formatCommentPrompt so that ORIGINAL
block is included in the generated prompt for suggestion comments.
@takumi0706
Copy link
Author

@yoshiko-pg Oops, sorry! I thought I had pasted the UI and prompt, but it turns out I was still editing 😅
It’s updated now — would love your thoughts!

@yoshiko-pg
Copy link
Owner

@takumi0706 Thank you!
I do not think I will have a chance to look at it for the next few days, so it might take me a little time 🙏

Copy link
Owner

@yoshiko-pg yoshiko-pg left a comment

Choose a reason for hiding this comment

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

@takumi0706 Thank you for waiting! I've reviewed it!
It seems there are multiple instances of dead code, so I'd appreciate it if you could remove them first, and then address the other comments!
Please let me know if there are any contents that are difficult to handle.

const end = textarea.selectionEnd;
const before = body.slice(0, start);
const after = body.slice(end);
const newBody = before + (before && !before.endsWith('\n') ? '\n' : '') + template + after;
Copy link
Owner

Choose a reason for hiding this comment

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

When inserting a suggestion in the middle of existing text, after is appended immediately after the closing fence. If after doesn't start with \n, the closing ``` and trailing text can end up on the same line, breaking the suggestion block.
Could we add a trailing newline when needed (similar to the leading newline logic)?

Copy link
Author

Choose a reason for hiding this comment

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

Fixed. Added a newline between the template and after text when after doesn't start with \n, so the closing fence won't merge with trailing text.


// Check if body contains a suggestion block
if (hasSuggestionBlock(body)) {
const suggestedCode = extractFirstSuggestion(body);
Copy link
Owner

Choose a reason for hiding this comment

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

This only uses extractFirstSuggestion() and keeps text before the first block, so text after the first suggestion (and additional suggestion blocks) is dropped.
Could we parse all suggestion blocks and preserve surrounding text in order?

Copy link
Author

Choose a reason for hiding this comment

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

Replaced extractFirstSuggestion() with parseSuggestionBlocks() and now iterate over all blocks, preserving text between and after them 👍

</div>
);
})}
</div>
Copy link
Owner

Choose a reason for hiding this comment

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

Could we reuse the existing diff line renderer here instead of hand-building +/- rows?
This duplicates diff-row rendering logic and can drift from the main file diff view (styles, highlighting behavior, future UI changes).

Copy link
Author

Choose a reason for hiding this comment

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

The existing DiffLineRow is -based with many required props, so direct reuse wasn't practical here. Instead, extracted a shared SuggestionDiffLine component that uses the same Tailwind diff classes (bg-diff-addition-bg / bg-diff-deletion-bg) to keep the visual style consistent!

/**
* Parse all suggestion blocks from a comment body
*/
export function parseSuggestionBlocks(
Copy link
Owner

Choose a reason for hiding this comment

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

parseSuggestionBlocks currently mixes parsing with injected view context (originalCode/language). Would it be simpler to keep this function as a pure parser of body only, and attach rendering context at the caller side?

Copy link
Author

Choose a reason for hiding this comment

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

Refactored to a pure parser — now only takes body and returns { suggestedCode, startIndex, endIndex }. Rendering context (originalCode) is attached at the caller side.

/**
* Format a suggestion for prompt output with ORIGINAL/SUGGESTED structure
*/
export function formatSuggestionForPrompt(
Copy link
Owner

Choose a reason for hiding this comment

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

formatSuggestionForPrompt looks unused in runtime code. Can we remove unused helper APIs for now and keep this module minimal until we have a concrete caller?

Copy link
Author

Choose a reason for hiding this comment

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

Removed. The equivalent functionality is already covered by formatCommentPrompt() in commentFormatting.ts.

startIndex: number; // Start position in the comment body
endIndex: number; // End position in the comment body
}

Copy link
Owner

Choose a reason for hiding this comment

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

SuggestionBlock seems to be an internal shape used only by suggestion parsing/rendering utilities. Would it be better to keep this type local to that module instead of placing it in global diff types?

Copy link
Author

Choose a reason for hiding this comment

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

Moved SuggestionBlock to suggestionUtils.ts and removed it from global diff types.

/**
* Create a suggestion template with the given code
*/
export function createSuggestionTemplate(code: string, _language?: string): string {
Copy link
Owner

Choose a reason for hiding this comment

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

createSuggestionTemplate(code, _language?) accepts a language argument that is currently unused. If we don't plan to use it soon, could we drop it from the signature and simplify the call sites?

Copy link
Author

Choose a reason for hiding this comment

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

Dropped the language parameter from the signature🫳

onSubmit={handleSubmitComment}
onCancel={handleCancelComment}
selectedCode={getSelectedCodeContent()}
language={filename ? getLanguageFromPath(filename) : undefined}
Copy link
Owner

Choose a reason for hiding this comment

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

language={filename ? getLanguageFromPath(filename) : undefined} is passed into CommentForm, but this value is not used in the current suggestion flow. This looks like dead prop plumbing; could we remove the prop (and related imports) to keep the implementation minimal?

Copy link
Author

Choose a reason for hiding this comment

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

Removed the language prop from CommentForm and cleaned up related imports in DiffChunk and SideBySideDiffChunk🧹

- Remove dead code: formatSuggestionForPrompt, extractFirstSuggestion
- Remove unused language parameter from createSuggestionTemplate
- Remove language prop plumbing from DiffChunk/SideBySideDiffChunk/CommentForm
- Localize SuggestionBlock type to suggestionUtils module
- Make parseSuggestionBlocks a pure body-only parser
- Handle all suggestion blocks in prompt output (not just the first)
- Extract SuggestionDiffLine component for suggestion diff rendering
- Fix newline insertion position in CommentForm suggestion template
@takumi0706
Copy link
Author

@yoshiko-pg
Thank you so much for the thorough and thoughtful review! Every comment was a great learning experience for me — especially the points about keeping parsers pure, localizing types to where they're actually used, and removing dead code to keep the module minimal. These are principles I'll carry forward beyond this PR.

I've addressed all the comments. For the diff line renderer, I wasn't able to directly reuse DiffLineRow since it's -based with many required props, but I extracted a shared SuggestionDiffLine component using the same Tailwind diff classes to keep the visual style consistent. Happy to adjust if you had a different approach in mind.

Thanks again for taking the time to review — really appreciate it!❤️‍🔥

@takumi0706 takumi0706 requested a review from yoshiko-pg February 7, 2026 06:05
Copy link
Owner

@yoshiko-pg yoshiko-pg left a comment

Choose a reason for hiding this comment

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

@takumi0706 Thank you for your quick response!
I've confirmed that it has been corrected!
I've just commented on one point that I overlooked; if you could address that, I'd like to merge it!

[comments],
);

const generateAllCommentsPrompt = useCallback((): string => {
Copy link
Owner

Choose a reason for hiding this comment

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

I think generateAllCommentsPrompt() still drops suggestion context for ORIGINAL: blocks. In transformedComments, we pass file/line/body/timestamp but not codeContent (from comment.codeSnapshot?.content), so suggestion prompts generated via "Copy All Comments" lose the original code context even though single-comment prompt generation keeps it.

And could we add a test for generateAllCommentsPrompt() with a suggestion comment that includes codeSnapshot.content? Right now the tests cover regular comments, but they don't assert that "Copy All Comments" preserves ORIGINAL + SUGGESTED output for suggestion blocks.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Add GitHub-style code suggestion in inline comments

2 participants