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
6 changes: 3 additions & 3 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 33 additions & 15 deletions packages/review-editor/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ import {
REVIEW_ALL_FILES_PANEL_ID,
REVIEW_CODE_NAV_PANEL_ID,
} from './dock/reviewPanelTypes';
import type { DiffFile } from './types';
import type { DiffFile, AnnotationScrollTarget } from './types';
import { annotationMatchesPrScope } from './utils/annotationScope';
import type { DiffOption, WorktreeInfo, GitContext } from '@plannotator/shared/types';
import type { PRMetadata } from '@plannotator/shared/pr-types';
import type { PRDiffScope, PRDiffScopeOption, PRStackInfo, PRStackTree } from '@plannotator/shared/pr-stack';
Expand Down Expand Up @@ -121,6 +122,11 @@ const ReviewApp: React.FC = () => {
const [activeFileIndex, setActiveFileIndex] = useState(0);
const [annotations, setAnnotations] = useState<CodeAnnotation[]>([]);
const [selectedAnnotationId, setSelectedAnnotationId] = useState<string | null>(null);
// Sidebar-initiated "scroll to this comment" signal. The token bumps on every
// sidebar click so re-selecting the same comment re-navigates. Selecting a
// comment in the diff sets selectedAnnotationId but NOT this — so it never
// moves the viewport.
const [scrollTargetAnnotation, setScrollTargetAnnotation] = useState<AnnotationScrollTarget | null>(null);
const [isAllFilesActive, setIsAllFilesActive] = useState(false);
// Mirror ref: handlers captured by Pierre slot portals (which only republish
// on item version bumps) and early-declared callbacks read the CURRENT value
Expand Down Expand Up @@ -1511,30 +1517,39 @@ const ReviewApp: React.FC = () => {
// handler is baked into Pierre slot portals, which only republish on item
// version bumps — a stale captured value would yank the user out of the
// all-files tab into the single-file panel when they click an annotation.
// Inline-card selection: toggle the highlight + ring only. No scroll, no file
// switch — the clicked card is already on screen. Clicking the selected card
// again (or a null id) clears it.
const handleSelectAnnotation = useCallback((id: string | null) => {
// An inline selection supersedes any pending sidebar/findings navigate target,
// so a later remount (Refresh / base switch) doesn't re-scroll back to it.
setScrollTargetAnnotation(null);
setSelectedAnnotationId(prev => (!id || prev === id ? null : id));
}, []);

// Sidebar navigation: select AND scroll-to the comment (DiffsHub "set +
// scroll"). The token bump re-fires the panels' scroll effect even when the
// same comment is clicked twice; in single-file mode it switches to the
// owning file first so the scroll target exists.
const handleNavigateToAnnotation = useCallback((id: string | null) => {
if (!id) {
setSelectedAnnotationId(null);
return;
}

// Find the annotation
const annotation = allAnnotationsRef.current.find(a => a.id === id);
if (!annotation) {
setSelectedAnnotationId(id);
// Ignore navigation to an annotation that's gone (deleted) or filtered out of
// the active PR/diff-scope — there's nothing in the current diff to scroll to
// or highlight, so don't fake a selection.
if (!annotation || !annotationMatchesPrScope(annotation, prMetadata?.url, prDiffScope)) {
return;
}

// In all-files mode, just set the selection — the panel's scroll-to-annotation
// effect handles expanding and scrolling. In single-file mode, switch to the file.
if (!isAllFilesActiveRef.current) {
const fileIndex = files.findIndex(f => f.path === annotation.filePath);
if (fileIndex !== -1) {
handleFileSwitch(fileIndex);
}
if (fileIndex !== -1) handleFileSwitch(fileIndex);
}

setSelectedAnnotationId(id);
}, [files, handleFileSwitch]);
setScrollTargetAnnotation(prev => ({ id, token: (prev?.token ?? 0) + 1 }));
}, [files, handleFileSwitch, prMetadata, prDiffScope]);

// Diff context bundled into local-mode feedback headers so the receiving
// agent knows which diff the annotations are anchored to. Uses committedBase
Expand Down Expand Up @@ -1596,6 +1611,7 @@ const ReviewApp: React.FC = () => {
allAnnotations,
externalAnnotations,
selectedAnnotationId,
scrollTargetAnnotation,
pendingSelection,
onLineSelection: handleLineSelection,
onAddAnnotation: handleAddAnnotation,
Expand All @@ -1604,6 +1620,7 @@ const ReviewApp: React.FC = () => {
onAddFileCommentForFile: handleAddFileCommentForFile,
onEditAnnotation: handleEditAnnotation,
onSelectAnnotation: handleSelectAnnotation,
onNavigateToAnnotation: handleNavigateToAnnotation,
onDeleteAnnotation: handleDeleteAnnotation,
viewedFiles,
onToggleViewed: handleToggleViewed,
Expand Down Expand Up @@ -1654,9 +1671,9 @@ const ReviewApp: React.FC = () => {
diffLineDiffType, diffShowLineNumbers, diffShowBackground,
diffExpandUnchanged, diffFontFamily, diffFontSize, activeDiffBase, committedBase, feedbackDiffContext, prReviewScopeLabel, prDiffScope, agentCwd,
allAnnotations, externalAnnotations,
selectedAnnotationId, pendingSelection, handleLineSelection,
selectedAnnotationId, scrollTargetAnnotation, pendingSelection, handleLineSelection,
handleAddAnnotation, handleAddFileComment, handleAddFileCommentForFile, handleEditAnnotation,
handleSelectAnnotation, handleDeleteAnnotation, viewedFiles,
handleSelectAnnotation, handleNavigateToAnnotation, handleDeleteAnnotation, viewedFiles,
handleToggleViewed, stagedFiles, stagingFile, stageFile,
canStageFiles, stageError, isSearchPending, debouncedSearchQuery,
activeFileSearchMatches, activeSearchMatchId, activeSearchMatch, searchMatches,
Expand Down Expand Up @@ -2575,6 +2592,7 @@ const ReviewApp: React.FC = () => {
files={files}
selectedAnnotationId={selectedAnnotationId}
onSelectAnnotation={handleSelectAnnotation}
onNavigateToAnnotation={handleNavigateToAnnotation}
onDeleteAnnotation={handleDeleteAnnotation}
feedbackMarkdown={feedbackMarkdown}
width={panelResize.width}
Expand Down
7 changes: 3 additions & 4 deletions packages/review-editor/components/AITab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { AIChatEntry, PendingPermission } from '../hooks/useAIChat';
import { renderChatMarkdown } from '../utils/renderChatMarkdown';
import { formatLineRange } from '../utils/formatLineRange';
import { formatRelativeTime } from '../utils/formatRelativeTime';
import { FileNameChip } from './FileNameChip';
import { SparklesIcon } from '@plannotator/ui/components/SparklesIcon';
import { CountBadge } from './CountBadge';
import { CopyButton } from './CopyButton';
Expand Down Expand Up @@ -352,10 +353,8 @@ const QAPair = memo<{
{formatLineRange(question.lineStart, question.lineEnd)}
</button>
)}
{scope === 'file' && (
<span className="text-[9px] font-semibold uppercase tracking-wider px-1.5 py-0.5 rounded bg-primary/10 text-primary">
file
</span>
{scope === 'file' && question.filePath && (
<FileNameChip path={question.filePath} />
)}
</div>
<span className="text-[10px] text-muted-foreground/50">
Expand Down
Loading