From 8a6e9a649e5a009bdbd788f9b228df71bfa97f35 Mon Sep 17 00:00:00 2001 From: flex-yj-kim Date: Mon, 2 Mar 2026 00:24:21 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9C=A8=20Add=20code=20suggestions,=20inl?= =?UTF-8?q?ine=20editing,=20and=20refactor=20DiffViewer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GitHub-style suggestion blocks with syntax-highlighted diff view - Inline annotation editing via toolbar (edit button on inline comments) - Expandable two-pane code modal for writing suggestions - Inline markdown rendering (bold, italic, code) in review comments - Original code extraction from unified diff patches - Decompose DiffViewer (615→200 lines) into focused components: FileHeader, InlineAnnotation, SuggestionBlock, SuggestionDiff, SuggestionModal, AnnotationToolbar, HighlightedCode - Extract hooks: useAnnotationToolbar, useTabIndent - Extract utils: detectLanguage, formatLineRange, patchParser, renderInlineMarkdown - Fix suggestedCode trim stripping meaningful indentation - Fix stale closure in useTabIndent consumers --- bun.lock | 8 +- packages/review-editor/App.tsx | 19 +- .../components/AnnotationToolbar.tsx | 140 ++++++++ .../review-editor/components/DiffViewer.tsx | 320 ++++-------------- .../review-editor/components/FileHeader.tsx | 77 +++++ .../components/HighlightedCode.tsx | 19 ++ .../components/InlineAnnotation.tsx | 67 ++++ .../review-editor/components/ReviewPanel.tsx | 38 ++- .../components/SuggestionBlock.tsx | 27 ++ .../components/SuggestionDiff.tsx | 26 ++ .../components/SuggestionModal.tsx | 129 +++++++ .../hooks/useAnnotationToolbar.ts | 221 ++++++++++++ packages/review-editor/hooks/useTabIndent.ts | 21 ++ packages/review-editor/index.css | 218 +++++++++++- packages/review-editor/package.json | 1 + .../review-editor/utils/detectLanguage.ts | 14 + .../review-editor/utils/formatLineRange.ts | 6 + packages/review-editor/utils/patchParser.ts | 58 ++++ .../utils/renderInlineMarkdown.tsx | 68 ++++ packages/ui/types.ts | 2 + 20 files changed, 1210 insertions(+), 269 deletions(-) create mode 100644 packages/review-editor/components/AnnotationToolbar.tsx create mode 100644 packages/review-editor/components/FileHeader.tsx create mode 100644 packages/review-editor/components/HighlightedCode.tsx create mode 100644 packages/review-editor/components/InlineAnnotation.tsx create mode 100644 packages/review-editor/components/SuggestionBlock.tsx create mode 100644 packages/review-editor/components/SuggestionDiff.tsx create mode 100644 packages/review-editor/components/SuggestionModal.tsx create mode 100644 packages/review-editor/hooks/useAnnotationToolbar.ts create mode 100644 packages/review-editor/hooks/useTabIndent.ts create mode 100644 packages/review-editor/utils/detectLanguage.ts create mode 100644 packages/review-editor/utils/formatLineRange.ts create mode 100644 packages/review-editor/utils/patchParser.ts create mode 100644 packages/review-editor/utils/renderInlineMarkdown.tsx diff --git a/bun.lock b/bun.lock index b3aa583..abad462 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "plannotator", @@ -53,7 +54,7 @@ }, "apps/opencode-plugin": { "name": "@plannotator/opencode", - "version": "0.9.2", + "version": "0.9.3", "dependencies": { "@opencode-ai/plugin": "^1.1.10", }, @@ -74,7 +75,7 @@ }, "apps/pi-extension": { "name": "@plannotator/pi-extension", - "version": "0.9.2", + "version": "0.9.3", "peerDependencies": { "@mariozechner/pi-coding-agent": ">=0.53.0", }, @@ -134,6 +135,7 @@ "dependencies": { "@pierre/diffs": "^1.0.4", "@plannotator/ui": "workspace:*", + "highlight.js": "^11.11.1", "react": "^19.2.3", "react-dom": "^19.2.3", "tailwindcss": "^4.1.18", @@ -141,7 +143,7 @@ }, "packages/server": { "name": "@plannotator/server", - "version": "0.9.2", + "version": "0.9.3", "dependencies": { "@plannotator/shared": "workspace:*", }, diff --git a/packages/review-editor/App.tsx b/packages/review-editor/App.tsx index 326df76..b114fcb 100644 --- a/packages/review-editor/App.tsx +++ b/packages/review-editor/App.tsx @@ -8,7 +8,7 @@ import { storage } from '@plannotator/ui/utils/storage'; import { CompletionOverlay } from '@plannotator/ui/components/CompletionOverlay'; import { getIdentity } from '@plannotator/ui/utils/identity'; import { getAgentSwitchSettings, getEffectiveAgentName } from '@plannotator/ui/utils/agentSwitch'; -import { CodeAnnotation, CodeAnnotationType, SelectedLineRange, DiffAnnotationMetadata } from '@plannotator/ui/types'; +import { CodeAnnotation, CodeAnnotationType, SelectedLineRange } from '@plannotator/ui/types'; import { useResizablePanel } from '@plannotator/ui/hooks/useResizablePanel'; import { ResizeHandle } from '@plannotator/ui/components/ResizeHandle'; import { DiffViewer } from './components/DiffViewer'; @@ -255,7 +255,8 @@ const ReviewApp: React.FC = () => { const handleAddAnnotation = useCallback(( type: CodeAnnotationType, text?: string, - suggestedCode?: string + suggestedCode?: string, + originalCode?: string ) => { if (!pendingSelection || !files[activeFileIndex]) return; @@ -272,6 +273,7 @@ const ReviewApp: React.FC = () => { side: pendingSelection.side === 'additions' ? 'new' : 'old', text, suggestedCode, + originalCode, createdAt: Date.now(), author: identity, }; @@ -280,6 +282,18 @@ const ReviewApp: React.FC = () => { setPendingSelection(null); }, [pendingSelection, files, activeFileIndex, identity]); + // Edit annotation + const handleEditAnnotation = useCallback(( + id: string, + text?: string, + suggestedCode?: string, + originalCode?: string + ) => { + setAnnotations(prev => prev.map(ann => + ann.id === id ? { ...ann, text, suggestedCode, originalCode } : ann + )); + }, []); + // Delete annotation const handleDeleteAnnotation = useCallback((id: string) => { setAnnotations(prev => prev.filter(a => a.id !== id)); @@ -755,6 +769,7 @@ const ReviewApp: React.FC = () => { pendingSelection={pendingSelection} onLineSelection={handleLineSelection} onAddAnnotation={handleAddAnnotation} + onEditAnnotation={handleEditAnnotation} onSelectAnnotation={handleSelectAnnotation} onDeleteAnnotation={handleDeleteAnnotation} isViewed={viewedFiles.has(activeFile.path)} diff --git a/packages/review-editor/components/AnnotationToolbar.tsx b/packages/review-editor/components/AnnotationToolbar.tsx new file mode 100644 index 0000000..f5950d4 --- /dev/null +++ b/packages/review-editor/components/AnnotationToolbar.tsx @@ -0,0 +1,140 @@ +import React from 'react'; +import { ToolbarState } from '../hooks/useAnnotationToolbar'; +import { useTabIndent } from '../hooks/useTabIndent'; +import { formatLineRange } from '../utils/formatLineRange'; + +interface AnnotationToolbarProps { + toolbarState: ToolbarState; + toolbarRef: React.RefObject; + commentText: string; + setCommentText: (text: string) => void; + suggestedCode: string; + setSuggestedCode: React.Dispatch>; + showSuggestedCode: boolean; + setShowSuggestedCode: (show: boolean) => void; + isEditing?: boolean; + setShowCodeModal: (show: boolean) => void; + onSubmit: () => void; + onDismiss: () => void; + onCancel: () => void; +} + +/** Floating comment input form that appears after line selection */ +export const AnnotationToolbar: React.FC = ({ + toolbarState, + toolbarRef, + commentText, + setCommentText, + suggestedCode, + setSuggestedCode, + showSuggestedCode, + setShowSuggestedCode, + isEditing = false, + setShowCodeModal, + onSubmit, + onDismiss, + onCancel, +}) => { + const handleTabIndent = useTabIndent(setSuggestedCode); + + return ( +
+
+
+ + {isEditing ? 'Edit annotation' : formatLineRange(toolbarState.range.start, toolbarState.range.end)} + + +
+ +