From f974e43aaeeacbc7d193ec0de98511433f2561be Mon Sep 17 00:00:00 2001 From: Chad Miller Date: Thu, 27 Aug 2026 19:42:56 -0700 Subject: [PATCH] feat(git-ui): the anchored-comment preview shows the whole range A range comment stored its span, but the timeline preview labelled only its first line and marked only its last, since the placement keys the thread on the last line. The label reads the span, `path:first-last`, and the preview marks every row from the first line to the last rather than one. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01RomHvaB4nyAckbRD7W36Ue --- .../src/components/molecules/timeline.jsx | 15 ++++++++++----- packages/git-ui/src/pages/commit.jsx | 19 +++++++++++++++---- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/packages/git-ui/src/components/molecules/timeline.jsx b/packages/git-ui/src/components/molecules/timeline.jsx index 5c024f9..f2ade13 100644 --- a/packages/git-ui/src/components/molecules/timeline.jsx +++ b/packages/git-ui/src/components/molecules/timeline.jsx @@ -45,11 +45,12 @@ function AnchoredCode({ anchor, context }) { className={cn( 'flex whitespace-pre px-3', ROW_STYLE[type], - // The line the comment is about, marked over whatever side it - // is on. + // The lines the comment is about, marked over whatever side + // they are on. A single line is a span of one. context && - index === context.at && - 'ring-1 ring-key/40 ring-inset', + index >= context.from && + index <= context.to && + 'bg-key/10', )} > @@ -226,7 +227,11 @@ export function Timeline({
{anchor.path} - {anchor.line ? `:${anchor.line}` : ''} + {anchor.line + ? anchor.endLine && anchor.endLine !== anchor.line + ? `:${anchor.line}-${anchor.endLine}` + : `:${anchor.line}` + : ''} {inDiff && threadHref ? ( diff --git a/packages/git-ui/src/pages/commit.jsx b/packages/git-ui/src/pages/commit.jsx index c19a872..a8f7536 100644 --- a/packages/git-ui/src/pages/commit.jsx +++ b/packages/git-ui/src/pages/commit.jsx @@ -248,10 +248,21 @@ export function CommitPage({ const file = data.files.find((one) => one.path === anchor.path); if (!file?.hunks) return null; const rows = file.hunks.flatMap((hunk) => hunk.lines); - const at = rows.findIndex((row) => lineKey(row) === anchorKey(anchor)); - if (at < 0) return null; - const from = Math.max(0, at - CONTEXT_LINES); - return { lines: rows.slice(from, at + CONTEXT_LINES + 1), at: at - from }; + // The span the comment covers: its first line to its last. A single-line + // comment has the two the same. anchorKey keys the last line, and the + // first is the anchor's own side and line. + const side = anchor.side === 'old' ? 'old' : 'new'; + const startKey = `${side}:${anchor.line}`; + const startAt = rows.findIndex((row) => lineKey(row) === startKey); + const endAt = rows.findIndex((row) => lineKey(row) === anchorKey(anchor)); + if (startAt < 0 || endAt < 0) return null; + const [lo, hi] = startAt <= endAt ? [startAt, endAt] : [endAt, startAt]; + const from = Math.max(0, lo - CONTEXT_LINES); + return { + lines: rows.slice(from, hi + CONTEXT_LINES + 1), + from: lo - from, + to: hi - from, + }; }; // A thread anchored to a line is a place in the diff, which is the other -- 2.51.2