feat(review): file comments in the diff + unified click-to-highlight comment UX#973
Merged
Conversation
…ent UX Render file-scoped comments inline in the code review UI and overhaul the comment-card UX for consistency. File comments - Single-file view: a full-text banner below the file path. - All-files view: rendered in the file header (expanded files only) — Pierre's virtualized custom header suppresses the body-prefix slot, and its measured item heights make a taller header safe. Guided-review external annotations can now drop the line-1 anchor hack. Click-to-highlight - Clicking a comment (inline card or sidebar) replays its stored line range as the controlled selection highlight; clicking it again clears it. - Scroll/navigate is sidebar- and findings-list-only (token-based signal) so clicking a comment in the diff never moves the viewport. Unified comment cards - Shared CommentMeta identity row (badges + author + relative time) across the inline, sidebar, and file-banner cards (were three hand-rolled headers). - Shared FileNameChip (basename, full path on hover) replaces the literal "file" badge everywhere. - Force the app sans font on cards (inline was inheriting the diff's monospace), consistent selected ring, spacing, and absolute hover actions so timestamps align across surfaces. Shared helpers: annotationScope (PR-scope / file-scope / line-range projection) and fileName (basename). Plumbs createdAt through DiffAnnotationMetadata so the inline cards can show timestamps.
Shared CommentActions component used by the inline diff card, the file-comment banner, and the sidebar — bottom-aligned, hover-revealed, order left→right edit · copy · delete (delete/close furthest right). - Inline + banner: actions moved from the top (where they overlapped the timestamp) to the bottom, and gained the copy button. - Sidebar: now uses the same component (copy + delete) — same icons, order, and styling. - Read-only (externally-sourced) file comments show copy only. - Extract commentCopyText() so every card's copy produces identical, self-describing text (location prefix + body + reasoning); drops the format that was hand-built in the sidebar and the agent-job panel. - Rename PRCommentsTab's local CommentActions -> PRCommentLinkActions to avoid the name clash with the shared component. - Remove the now-dead .review-comment-action(s) CSS and CommentMeta's trailing slot.
- all-files: let an active compose own the line highlight (pendingSelection guard) so clicking/deselecting a comment mid-compose no longer clobbers the toolbar's range — mirrors single-file's `pendingSelection ?? annotationRange`. - clear scrollTargetAnnotation on inline select so a remount (Refresh / base switch) doesn't re-scroll back to a stale sidebar/findings navigate target. - inline diff cards: hide edit/delete for external (source-set) findings (matches the file-comment card) and copy with the file:line location prefix via a precomputed metadata.copyText (commentCopyText), unifying the copy text. - add decorations + createdAt to the file-comment refresh signature so an external PATCH changing only those repaints the all-files header. - wrap finding reasoning text (overflow-wrap/word-break) so long file:line refs no longer bleed past the card edge.
… gaps Self-review follow-ups to the PR-feedback fixes: - Extract lineAnnotationMetadata() — the all-files and single-file diff surfaces built the identical 14-field DiffAnnotationMetadata; now one source of truth. - Add reviewProfileLabel + source + createdAt to the LINE-scope refresh signature: the unified CommentMeta now renders those on inline cards too, so an external PATCH changing only them must repaint the item (same gap that was just fixed for the file-comment tuple). - Wrap the agent-job panel's finding text + reasoning (overflow-wrap) so long file:line refs don't bleed past the panel edge, matching the inline card fix.
- all-files: set CodeView layout gap to 0 (Pierre default was 8) so files sit flush; sticky headers already mark file boundaries. Keep default edge padding. - FileHeader: path text leading-none -> leading-normal so the overflow-hidden ellipsis box has room for descenders (g/y/p were clipped by the box bottom).
…hlight - all-files file comments: re-measure the CodeView item when a comment expands/collapses/edits (FileCommentCard -> onHeightChange -> refreshItem) so Pierre repositions following items instead of overlapping them. Fixes the dynamic header-height bug; the remaining static sticky-offset drift is bounded by the new banner cap and stays the accepted trade-off. - cap the expanded comment body (max-h + overflow) so a long guided-review note doesn't fill the viewport / inflate the sticky header. - delete is now available on every surface (inline, banner) including external source-set findings; edit stays gated on !source. - restore the line highlight when a compose ends: the all-files pendingSelection effect now re-derives the selected comment's range instead of clearing (mirrors single-file's pendingSelection ?? annotationRange). - add reasoning to the file-comment refresh signature (copyText includes it). - guard handleNavigateToAnnotation with annotationMatchesPrScope so a stale sidebar/finding row can't navigate to an out-of-scope annotation.
…-measure Self-review follow-ups: - Extract lineSelectionForAnnotation() — the compose-end restore and the selection-replay effect built the same annotation→CodeViewLineSelection by hand; share it so they can't diverge. - Re-measure file-comment height via useLayoutEffect (before paint) instead of useEffect, so an expand/collapse lands without a one-frame overlap.
… switch - handleNavigateToAnnotation: return early when the annotation id is gone (deleted) as well as out-of-scope, so a stale row can't leave an orphan selection (ring with nothing to scroll to). Dismissed agent-panel rows are already non-clickable; this is the belt-and-suspenders for any other path. - fileSetKey now includes prUrl/prDiffScope so a pure layer<->full-stack scope switch (same file set) remounts and re-seeds annotations through the current scope filter — the incremental sync bails on an unchanged annotations ref and can't otherwise detect the filter change.
Drop the editable = !source gate on inline and file-comment cards. main always showed edit for external findings (it routes through updateExternalAnnotation), so gating it was a regression. Edit, delete, and copy are now all available on every comment surface regardless of source.
annotation is guaranteed non-null past the missing/out-of-scope early return.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Renders file-scoped comments inline in the code review UI and overhauls the comment-card UX so the three comment surfaces (inline diff, sidebar, file banner) look and behave consistently.
File comments
Previously file-scoped comments only showed a truncated preview in the sidebar; they now render in full in the diff:
Click-to-highlight (DiffsHub-style)
Unified comment cards
CommentMetaidentity row (leading badge + label + author + relative time) used by all three cards — they were three separately hand-rolled headers with different author sizes, timestamp placement, and fonts.FileNameChip(file basename, full path on hover) replaces the literal "FILE" badge everywhere it appeared.Refactors / shared helpers
annotationScope.ts—annotationMatchesPrScope,isFileScopedAnnotation,lineRangeForAnnotation(consolidates a predicate that was duplicated across the diff panels).fileName.ts—fileBasename.createdAt(+reviewProfileLabel/source) plumbed throughDiffAnnotationMetadataso inline cards show timestamps.Test plan
🤖 Generated with Claude Code