Skip to content

feat(review): file comments in the diff + unified click-to-highlight comment UX#973

Merged
backnotprop merged 10 commits into
mainfrom
feat/file-comments-render
Jun 26, 2026
Merged

feat(review): file comments in the diff + unified click-to-highlight comment UX#973
backnotprop merged 10 commits into
mainfrom
feat/file-comments-render

Conversation

@backnotprop

Copy link
Copy Markdown
Owner

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:

  • Single-file view: a full-text banner directly below the file path.
  • All-files view: in the file header, only when the file is expanded (collapsed files stay compact). Pierre's virtualized custom header suppresses the body-prefix slot, but its virtualizer measures item heights, so a taller header is safe. This lets guided-review workflows drop the "line-1 anchor" hack with the External Annotations API.

Click-to-highlight (DiffsHub-style)

  • Each comment stores the exact line range it was created from. Clicking a comment — inline card or sidebar — replays that range as the controlled selection highlight; clicking again clears it.
  • Scroll/navigate is sidebar- & findings-list-only (a token-based signal). Clicking a comment in the diff highlights its lines but never moves the viewport.

Unified comment cards

  • New shared CommentMeta identity 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.
  • New shared FileNameChip (file basename, full path on hover) replaces the literal "FILE" badge everywhere it appeared.
  • Force the app sans font on comment cards — inline comments had been inheriting the diff's monospace, which made them hard to read. Code spans still opt back into mono.
  • Consistent selected ring, card spacing, and absolute hover actions so timestamps line up across surfaces.

Refactors / shared helpers

  • annotationScope.tsannotationMatchesPrScope, isFileScopedAnnotation, lineRangeForAnnotation (consolidates a predicate that was duplicated across the diff panels).
  • fileName.tsfileBasename.
  • createdAt (+ reviewProfileLabel/source) plumbed through DiffAnnotationMetadata so inline cards show timestamps.

Test plan

  • Add a file comment (header "Comment" button) → renders below the path in single-file and in the header (expanded) in all-files; collapses with the file.
  • Click a comment in the diff → its lines highlight, viewport stays put; click again → clears.
  • Click a comment in the sidebar / a finding in an agent review → scrolls/expands to it.
  • Inline, sidebar, and file-banner cards share one identity row (author + "Xh ago"), sans font, aligned timestamps.
  • File-scope label shows the file name (not "FILE") in the comment, sidebar, and AI tab.

🤖 Generated with Claude Code

…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.
@backnotprop backnotprop merged commit e1efea8 into main Jun 26, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant