Review comments should contain line of code (vibe-kanban) (#731)

* **Changes**
- Capture the relevant code line in each draft/comment so it’s stored alongside metadata and rendered in the Markdown output, with backtick-safe formatting for inline/triple code blocks (`frontend/src/contexts/ReviewProvider.tsx:4-107`).
- Pull the plain line text from the diff when a comment widget opens and stash it on the draft before saving (`frontend/src/components/DiffCard.tsx:140-155`).
- Pass the captured line through when persisting a new review comment (`frontend/src/components/diff/CommentWidgetLine.tsx:28-34`).

- Tests: `pnpm run check`

Consider spot-checking the review markdown in the UI to confirm the new code line snippet displays as expected.

* Line capture now trims trailing newline characters so inline code renders on a single line (`frontend/src/components/DiffCard.tsx:140-154`). `pnpm run check` still passes. Let me know if you spot any other formatting quirks.

* Cleanup script changes for task attempt 93f0100f-256d-4177-839d-53cb700d2a3e

* Pulled the diff-line lookup into a reusable helper so `DiffCard` now grabs and normalizes the raw line content before drafting comments; this keeps the widget payload lean and avoids scattering newline-trimming logic (`frontend/src/components/DiffCard.tsx:1-161`). Added a `stripLineEnding` utility so future callers have a single place to remove CR/LF sequences without touching other whitespace (`frontend/src/utils/string.ts:24-29`).

Tests: `pnpm run check` (fails — TypeScript can’t resolve types for `markdown-to-jsx` in `frontend/src/components/ui/markdown-renderer.tsx`; worth checking that dependency’s types or a module declaration is in place before re-running).
This commit is contained in:
Louis Knight-Webb
2025-09-15 23:33:44 +01:00
committed by GitHub
parent 9951c0e44c
commit bd96b7c18b
4 changed files with 50 additions and 5 deletions

View File

@@ -1,10 +1,11 @@
import { Diff } from 'shared/types';
import { DiffModeEnum, DiffView, SplitSide } from '@git-diff-view/react';
import { generateDiffFile } from '@git-diff-view/file';
import { generateDiffFile, type DiffFile } from '@git-diff-view/file';
import { useMemo } from 'react';
import { useUserSystem } from '@/components/config-provider';
import { getHighLightLanguageFromPath } from '@/utils/extToLanguage';
import { getActualTheme } from '@/utils/theme';
import { stripLineEnding } from '@/utils/string';
import { Button } from '@/components/ui/button';
import {
ChevronRight,
@@ -45,6 +46,25 @@ function labelAndIcon(diff: Diff) {
return { label: undefined as string | undefined, Icon: PencilLine };
}
function readPlainLine(
diffFile: DiffFile | null,
lineNumber: number,
side: SplitSide
) {
if (!diffFile) return undefined;
try {
const rawLine =
side === SplitSide.old
? diffFile.getOldPlainLine(lineNumber)
: diffFile.getNewPlainLine(lineNumber);
if (rawLine?.value === undefined) return undefined;
return stripLineEnding(rawLine.value);
} catch (error) {
console.error('Failed to read line content for review comment', error);
return undefined;
}
}
export default function DiffCard({
diff,
expanded,
@@ -131,11 +151,13 @@ export default function DiffCard({
const handleAddWidgetClick = (lineNumber: number, side: SplitSide) => {
const widgetKey = `${filePath}-${side}-${lineNumber}`;
const codeLine = readPlainLine(diffFile, lineNumber, side);
const draft: ReviewDraft = {
filePath,
side,
lineNumber,
text: '',
...(codeLine !== undefined ? { codeLine } : {}),
};
setDraft(widgetKey, draft);
};

View File

@@ -30,6 +30,7 @@ export function CommentWidgetLine({
side: draft.side,
lineNumber: draft.lineNumber,
text: value.trim(),
codeLine: draft.codeLine,
});
}
setDraft(widgetKey, null);

View File

@@ -7,6 +7,7 @@ export interface ReviewComment {
lineNumber: number;
side: SplitSide;
text: string;
codeLine?: string;
}
export interface ReviewDraft {
@@ -14,6 +15,7 @@ export interface ReviewDraft {
side: SplitSide;
lineNumber: number;
text: string;
codeLine?: string;
}
interface ReviewContextType {
@@ -83,11 +85,23 @@ export function ReviewProvider({ children }: { children: ReactNode }) {
const commentsNum = comments.length;
const header = `## Review Comments (${commentsNum})\n\n`;
const formatCodeLine = (line?: string) => {
if (!line) return '';
if (line.includes('`')) {
return `\`\`\`\n${line}\n\`\`\``;
}
return `\`${line}\``;
};
const commentsMd = comments
.map(
(comment) =>
`**${comment.filePath}** (Line ${comment.lineNumber})\n\n> ${comment.text.trim()}\n`
)
.map((comment) => {
const codeLine = formatCodeLine(comment.codeLine);
const commentBody = comment.text.trim();
if (codeLine) {
return `**${comment.filePath}** (Line ${comment.lineNumber})\n${codeLine}\n\n> ${commentBody}\n`;
}
return `**${comment.filePath}** (Line ${comment.lineNumber})\n\n> ${commentBody}\n`;
})
.join('\n');
return header + commentsMd;

View File

@@ -20,3 +20,11 @@ export const generateProjectNameFromPath = (path: string): string => {
const dirName = path.split('/').filter(Boolean).pop() || '';
return dirName.replace(/[-_]/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase());
};
/**
* Removes a single trailing newline sequence from a string.
* Handles CRLF/CR/LF endings while leaving other trailing whitespace intact.
*/
export const stripLineEnding = (value: string): string => {
return value.replace(/(?:\r\n|\r|\n)$/, '');
};