feat(web): add file blame view to code browser#1160
Conversation
Wires up the /api/blame endpoint and CodeMirror gutter extension that
together render a GitHub-style blame view in the code preview panel.
URL state plumbing:
- New BLAME_QUERY_PARAM ('blame') in browse/hooks/utils.ts; getBrowsePath
forwards `blame: true` as ?blame=true.
- page.tsx parses searchParams.blame and passes it to <CodePreviewPanel>.
Server-side:
- codePreviewPanel.tsx fetches blame data alongside file source via the
existing parallel Promise.all when blame mode is enabled.
- getFileBlameApi.ts now coalesces adjacent same-commit ranges in porcelain
output so the API surface presents one range per visual region (porcelain
emits a fresh group whenever source-line numbering is discontinuous in
the commit's snapshot, even when the final-file lines are contiguous and
attributed to the same commit).
CodeMirror extension (blameGutterExtension.ts):
- Renders a 400px-wide gutter to the left of line numbers (Prec.high to
jump it ahead of the basicSetup lineNumbers gutter).
- Each region's first line shows: relative date, author avatar (via the
/api/avatar resolver), commit message, and a square-stack icon button
for reblaming. Continuation lines are blank filler.
- Cells are built with raw DOM + Tailwind class strings (avoids React
mounting in CM markers, which had async-render flicker and lifecycle
errors when wrapped in flushSync).
- StateField + DecorationSet + gutterLineClass facet highlight every
line of the cursor's commit (GitLens-style peer highlight) in both
the source and gutter columns.
- Reblame button navigates to the previous commit's hash + path, with
blame mode preserved, using full revisionName context shift.
pureCodePreviewPanel.tsx:
- Accepts blame data, mounts the extension when present, supplies the
click and reblame callbacks (router-driven via getBrowsePath).
- Disables foldGutter, highlightActiveLine, and highlightActiveLineGutter
in basicSetup when blame mode is on (they collide visually with the
blame gutter and our cursor-driven peer highlight).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a segmented toggle next to the path header that switches between "Code" (plain source) and "Blame" (gutter view). The toggle is hidden when previewRef is set since the preview banner handles that state. Also displays line count and file size next to the toggle (e.g. "1,246 lines · 42.6 KB"). Line count is derived from the source string (newlines, ignoring trailing); byte size uses Buffer.byteLength on the already-fetched source (no extra git call). Pulls in @radix-ui/react-toggle-group and a shadcn toggle-group.tsx component to render the segmented control. Items are styled with gap-0 + rounded-*-none + -ml-px to share a single border at the seam, matching the GitHub-style segmented control look. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Caution Review failedPull request was closed or merged during review WalkthroughAdds a git-blame mode for the code browser: a Code/Blame toggle, CodeMirror blame gutter showing per-line commit metadata and avatars, cursor-driven highlighting of lines sharing a commit, and a “reblame” action to navigate blame history. Changes
Sequence DiagramsequenceDiagram
participant User
participant Toggle as BlameViewToggle
participant Router as Next.js Router
participant Panel as CodePreviewPanel
participant API as getFileBlame API
participant Editor as PureCodePreviewPanel
participant Gutter as blameGutterExtension
User->>Toggle: select "Blame"
Toggle->>Router: push URL with ?blame=true
Router->>Panel: render with blame=true
Panel->>API: fetch file + blame data
API-->>Panel: return FileBlameResponse (ranges, commits)
Panel->>Editor: pass blame data
Editor->>Gutter: register extension with blame
Gutter-->>Editor: render gutter (commits, avatars, reblame)
User->>Editor: click commit message
Editor->>Router: navigate to blob preview (diff=true)
User->>Editor: click reblame
Editor->>Router: navigate with previous hash & blame=true
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.Comment |
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
License Audit
Weak Copyleft Packages (informational)
Resolved Packages (12)
|
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/web/src/app/(app)/browse/[...path]/page.tsx (1)
111-137:⚠️ Potential issue | 🟠 Major | ⚡ Quick winSuppress blame mode when a preview ref is active.
searchParams.blamestill reachesCodePreviewPanelwhenpreviewRefis set, so preview mode can fetch/render blame data even though the toggle is hidden. Gate it here so preview-ref URLs stay source/diff-only.🔧 Proposed fix
- const isBlameMode = searchParams.blame === 'true'; + const isBlameMode = searchParams.blame === 'true' && !previewRef;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/web/src/app/`(app)/browse/[...path]/page.tsx around lines 111 - 137, The blame flag from searchParams still gets passed to CodePreviewPanel even when a preview ref is active; compute an effective blame value (e.g., const effectiveBlame = isBlameMode && !previewRef) and pass that to CodePreviewPanel instead of isBlameMode so that when previewRef is set (preview mode) blame is suppressed; update the CodePreviewPanel prop (blame={...}) accordingly and keep previewRef, isDiffMode, and focused diff logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@packages/web/src/app/`(app)/browse/[...path]/components/codePreviewPanel/blameGutterExtension.ts:
- Around line 72-80: The reblame button created in blameGutterExtension.ts uses
only title for identification which is not sufficient for assistive tech; update
the reblameBtn element (const reblameBtn) to include an explicit accessible name
by setting an appropriate aria-label (e.g., `aria-label="Blame prior to
<short-hash>"`) that mirrors the title and includes the previous.hash.slice(0,7)
so screen-reader and keyboard users can discover the action; keep the existing
title and event handler (onReblameClick) unchanged.
---
Outside diff comments:
In `@packages/web/src/app/`(app)/browse/[...path]/page.tsx:
- Around line 111-137: The blame flag from searchParams still gets passed to
CodePreviewPanel even when a preview ref is active; compute an effective blame
value (e.g., const effectiveBlame = isBlameMode && !previewRef) and pass that to
CodePreviewPanel instead of isBlameMode so that when previewRef is set (preview
mode) blame is suppressed; update the CodePreviewPanel prop (blame={...})
accordingly and keep previewRef, isDiffMode, and focused diff logic unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 44904e11-aaed-47ba-950c-a8e9be0b1e16
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (11)
CHANGELOG.mdpackages/web/package.jsonpackages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameGutterExtension.tspackages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameViewToggle.tsxpackages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsxpackages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/pureCodePreviewPanel.tsxpackages/web/src/app/(app)/browse/[...path]/page.tsxpackages/web/src/app/(app)/browse/hooks/utils.tspackages/web/src/components/ui/toggle-group.tsxpackages/web/src/ee/features/codeNav/components/symbolHoverPopup/index.tsxpackages/web/src/features/git/getFileBlameApi.ts
Summary
Adds a GitHub-style file blame view to the code browser, accessible via a Code / Blame toggle next to the path header.
Visual features
?blame=truequery param so refresh, deep links, and back/forward all work. Hidden in preview-ref mode (where the existing "Previewing file at revision X" banner already covers that state).1,246 lines · 42.6 KB. Line count from newlines in the source; byte size fromBuffer.byteLengthon the already-fetched content (no extra git call)./api/avatarresolver — auto profile pictures where the email matches a Sourcebot user, identicon fallback), commit message (clickable → opens the focused commit diff for that commit), and a square-stack "reblame" button that walks one step back in history.bg-accenthighlight, in both the source and gutter columns.Architecture notes
/api/blame(already shipped in feat(web): add /api/blame endpoint #1158): server now coalesces adjacent same-commit ranges via a single pass inparsePorcelainBlame. Non-adjacent same-hash ranges are preserved as separate regions.blameGutterExtension.ts: CodeMirror gutter built with raw DOM + Tailwind class strings (no React mounting in markers — earlier attempts hitflushSynclifecycle errors and async-render flicker). UsesPrec.highto register beforelineNumbers()frombasicSetup. AStateFieldderives the active commit from the cursor's main selection and providesDecoration.linedecorations + agutterLineClassRangeSetfor the peer highlight.pureCodePreviewPanel.tsx: when blame is on, disablesfoldGutter,highlightActiveLine, andhighlightActiveLineGutterinbasicSetup(they collide with the blame gutter and the cursor-driven peer highlight). Wires router-drivenonCommitClickandonReblameClickcallbacks to the extension.UserAvatar: bypassed earlier-PR work — the avatar resolver and the raw<img>swap landed in feat(web): add /api/avatar resolver #1159, so the gutter inherits sync-paint avatars for free.Drive-by
fix: set activeBottomPanelTab to explore when goto refs / defs) that I picked up on the branch — feel free to ask me to split it out if you'd prefer it as its own PR.🤖 Generated with Claude Code
Summary by CodeRabbit