Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ jobs:
packages/ui/components/sidebar/FileBrowser.test.ts
packages/editor/editableDocumentsHook.test.tsx
packages/review-editor/components/ReviewSubmissionDialog.ui.test.tsx
packages/review-editor/components/FileHeader.edit.test.tsx
packages/review-editor/edit/useEditSession.recovery.test.tsx
packages/review-editor/edit/selectionActionPopover.test.ts
packages/review-editor/hooks/useReviewSearch.test.tsx
packages/ui/components/AnnotationPanel.props.test.tsx
packages/ui/components/Viewer.consumer.test.tsx
Expand Down
65 changes: 65 additions & 0 deletions packages/review-editor/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import { useResizablePanel } from '@plannotator/ui/hooks/useResizablePanel';
import { useCodeAnnotationDraft } from '@plannotator/ui/hooks/useCodeAnnotationDraft';
import { useGitAdd } from './hooks/useGitAdd';
import { generateId } from './utils/generateId';
import type { SuggestionHunk } from './edit/deriveSuggestions';
import type { EditSelectionComment } from './edit/useEditSession';
import { useAIChat } from './hooks/useAIChat';
import { toast, Toaster } from 'sonner';
import { useCodeNav, type CodeNavRequest } from './hooks/useCodeNav';
Expand Down Expand Up @@ -246,6 +248,8 @@ const ReviewApp: React.FC = () => {
const diffFontFamily = useConfigValue('diffFontFamily');
const diffFontSize = useConfigValue('diffFontSize');
const diffTabSize = useConfigValue('diffTabSize');
// EXPERIMENTAL: edit code in place to author suggestions (default OFF).
const editSuggestionsEnabled = useConfigValue('editSuggestions');
// Global plan-look preference; surfaced here only by the shared 0.20.0
// look-and-feel announcement (the grid/clean chooser applies to plan review).
const gridEnabled = useConfigValue('gridEnabled');
Expand Down Expand Up @@ -1489,6 +1493,63 @@ const ReviewApp: React.FC = () => {
setPendingSelection(null);
}, [pendingSelection, identity, withPRContext]);

// Sink for the experimental edit-to-suggestion flow: a completed edit
// session delivers one hunk per contiguous changed region, each becoming a
// normal suggestion annotation (same shape SuggestionModal produces —
// type 'comment' carrying suggestedCode/originalCode) so it flows through
// rendering, sidebar, and feedback export unchanged. The browser never
// writes files; the agent applies these suggestions.
const handleAddSuggestionsForFile = useCallback((filePath: string, hunks: SuggestionHunk[]) => {
if (hunks.length === 0) return;
const now = Date.now();
setAnnotations(prev => [
...prev,
...hunks.map((hunk) => withPRContext({
id: generateId(),
type: 'comment' as CodeAnnotationType,
scope: 'line' as const,
filePath,
lineStart: hunk.lineStart,
lineEnd: hunk.lineEnd,
side: 'new' as const,
// A fully-emptied file derives an empty suggestion; the export
// template skips falsy suggestedCode, so describe it in text instead.
text: hunk.suggestedCode === '' ? 'Suggested change: remove these lines.' : undefined,
suggestedCode: hunk.suggestedCode === '' ? undefined : hunk.suggestedCode,
originalCode: hunk.originalCode === '' ? undefined : hunk.originalCode,
createdAt: now,
author: identity,
})),
]);
}, [identity, withPRContext]);

// Sink for the edit session's "Make annotation" selection action: a plain
// line-scoped comment whose anchor was mapped from the edited buffer to
// PRISTINE new-side coordinates at selection time (edit/selectionAnchor.ts),
// so it renders and exports correctly whether the session later completes
// or is discarded. `selectedText` preserves what was actually highlighted;
// `selectedTextFromEdits` flags an approximate anchor (selection overlapped
// in-session edits) so the export can label it honestly.
const handleAddEditorCommentForFile = useCallback((filePath: string, comment: EditSelectionComment) => {
const trimmed = comment.text.trim();
if (!trimmed) return;
const newAnnotation: CodeAnnotation = {
id: generateId(),
type: 'comment',
scope: 'line',
filePath,
lineStart: comment.lineStart,
lineEnd: comment.lineEnd,
side: 'new',
text: trimmed,
selectedText: comment.selectedText || undefined,
...(comment.exact ? {} : { selectedTextFromEdits: true }),
createdAt: Date.now(),
author: identity,
};
setAnnotations(prev => [...prev, withPRContext(newAnnotation)]);
}, [identity, withPRContext]);

const handleAddAnnotation = useCallback((
type: CodeAnnotationType,
text?: string,
Expand Down Expand Up @@ -2376,6 +2437,9 @@ const ReviewApp: React.FC = () => {
onLineSelection: handleLineSelection,
onAddAnnotation: handleAddAnnotation,
onAddAnnotationForFile: handleAddAnnotationForFile,
editSuggestionsEnabled,
onAddSuggestionsForFile: handleAddSuggestionsForFile,
onAddEditorCommentForFile: handleAddEditorCommentForFile,
onAddFileComment: handleAddFileComment,
onAddFileCommentForFile: handleAddFileCommentForFile,
onEditAnnotation: handleEditAnnotation,
Expand Down Expand Up @@ -2474,6 +2538,7 @@ const ReviewApp: React.FC = () => {
isPRContextLoading, prContextError, fetchPRContext, platformUser, openDiffFile,
handleOpenTour, handleOpenGuide, isAllFilesActive, allFilesOrder, allFilesAllCollapsed, onToggleAllFilesCollapsed, registerAllFilesCollapseToggle, commitInfo, isSemanticDiffActive, semanticDiffAvailable,
handleSemanticDiffUnavailable, handleSemanticDiffLoadError, handleSemanticDiffLoadSuccess, handleAddAnnotationForFile,
editSuggestionsEnabled, handleAddSuggestionsForFile, handleAddEditorCommentForFile,
handleCodeNavRequest, codeNav.result, codeNav.isLoading, codeNav.activeSymbol,
]);

Expand Down
Loading