feat(web): add file blame view to code browser - #1160

Merged
brendan-kellam merged 5 commits into
mainfrom
brendan/file-blame-ui
Apr 30, 2026
Merged

feat(web): add file blame view to code browser#1160
brendan-kellam merged 5 commits into
mainfrom
brendan/file-blame-ui

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Apr 30, 2026

Copy link
Copy Markdown
Contributor
imageimage

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

  • Code / Blame segmented toggle next to the path header. URL-driven via the new ?blame=true query 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).
  • File stats next to the toggle: 1,246 lines · 42.6 KB. Line count from newlines in the source; byte size from Buffer.byteLength on the already-fetched content (no extra git call).
  • Blame gutter rendered to the left of line numbers when blame is enabled. Each region's first line shows: relative date, author avatar (via the /api/avatar resolver — 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.
  • Cursor-driven peer highlighting (GitLens-style): when the cursor is on a line attributed to commit X, every other line attributed to X gets a subtle bg-accent highlight, in both the source and gutter columns.
  • Region grouping: porcelain emits a fresh group whenever source-line numbering is discontinuous in a commit's snapshot. The API now coalesces adjacent same-commit ranges so consumers see one range per visual region.

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 in parsePorcelainBlame. 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 hit flushSync lifecycle errors and async-render flicker). Uses Prec.high to register before lineNumbers() from basicSetup. A StateField derives the active commit from the cursor's main selection and provides Decoration.line decorations + a gutterLineClassRangeSet for the peer highlight.
  • pureCodePreviewPanel.tsx: when blame is on, disables foldGutter, highlightActiveLine, and highlightActiveLineGutter in basicSetup (they collide with the blame gutter and the cursor-driven peer highlight). Wires router-driven onCommitClick and onReblameClick callbacks 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

  • Includes one unrelated commit (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

  • New Features
    • Added a Code/Blame toggle to switch views in the file preview.
    • Cursor-driven highlighting that marks all lines from the selected blame commit.
    • "Reblame" action to navigate to prior commits for a line range.
    • Blame UI shows commit metadata (author, message, relative date, optional avatar).
    • Visual age legend with color ramp for commit ages; header now shows line count and file size.
    • Improved handling when blame data fails to load.

brendan-kellamand others added 3 commits April 29, 2026 16:46
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>
@coderabbitai

coderabbitaiBot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

Pull request was closed or merged during review

Walkthrough

Adds 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

Cohort / File(s)Summary
Changelog & package
CHANGELOG.md, packages/web/package.json
Added changelog bullet; bumped @radix-ui/react-toggle and added @radix-ui/react-toggle-group.
Blame gutter extension
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameGutterExtension.ts
New CodeMirror Extension that builds per-line blame index from FileBlameResponse, renders a left blame gutter with commit info/avatars, reblame button, and decorations to highlight all lines for the active commit.
Blame UI & toggle
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameViewToggle.tsx, packages/web/src/components/ui/toggle-group.tsx
New BlameViewToggle component and a Radix-backed ToggleGroup wrapper for segmented control styling/behavior.
Code preview integration
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx, .../pureCodePreviewPanel.tsx
Added optional blame prop; conditional fetch of blame data; header UI updates (file stats, toggle, legend); wires blameGutterExtension into editor and navigation handlers for commit preview and reblame.
Page routing & URL utils
packages/web/src/app/(app)/browse/[...path]/page.tsx, packages/web/src/app/(app)/browse/hooks/utils.ts
Added blame=true search param support, BLAME_QUERY_PARAM constant, and blame?: boolean on blob BrowseProps; getBrowsePath appends blame query when set.
Blame parsing
packages/web/src/features/git/getFileBlameApi.ts
parsePorcelainBlame now merges contiguous ranges with the same commit hash to simplify ranges returned.
Blame age UI
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameAgeColors.ts, .../blameAgeLegend.tsx
New 10-step age color ramp with bucket computation and a legend component rendering the color scale.
Navigation state
packages/web/src/ee/features/codeNav/components/symbolHoverPopup/index.tsx
Now includes activeBottomPanelTab: 'explore' in navigation state when opening Explore for definitions/references.

Sequence Diagram

sequenceDiagram
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • msukkari
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main feature: adding a file blame view to the code browser, which aligns with the PR's primary objective of implementing GitHub-style blame functionality.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/file-blame-ui

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.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@github-actions

github-actionsBot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

License Audit

⚠️Status: PASS

MetricCount
Total packages2068
Resolved (non-standard)12
Unresolved0
Strong copyleft0
Weak copyleft39

Weak Copyleft Packages (informational)

PackageVersionLicense
@img/sharp-libvips-darwin-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.0.5LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-ppc641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-riscv641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.2.4LGPL-3.0-or-later
@img/sharp-wasm320.33.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-wasm320.34.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-win32-arm640.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.34.5Apache-2.0 AND LGPL-3.0-or-later
axe-core4.10.3MPL-2.0
dompurify3.4.0(MPL-2.0 OR Apache-2.0)
lightningcss1.32.0MPL-2.0
lightningcss-android-arm641.32.0MPL-2.0
lightningcss-darwin-arm641.32.0MPL-2.0
lightningcss-darwin-x641.32.0MPL-2.0
lightningcss-freebsd-x641.32.0MPL-2.0
lightningcss-linux-arm-gnueabihf1.32.0MPL-2.0
lightningcss-linux-arm64-gnu1.32.0MPL-2.0
lightningcss-linux-arm64-musl1.32.0MPL-2.0
lightningcss-linux-x64-gnu1.32.0MPL-2.0
lightningcss-linux-x64-musl1.32.0MPL-2.0
lightningcss-win32-arm64-msvc1.32.0MPL-2.0
lightningcss-win32-x64-msvc1.32.0MPL-2.0
Resolved Packages (12)
PackageVersionOriginalResolvedSource
@react-grab/cli0.1.23UNKNOWNMITGitHub repo aidenybai/react-grab (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
@react-grab/cli0.1.29UNKNOWNMITGitHub repo aidenybai/react-grab (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
@react-grab/mcp0.1.29UNKNOWNMITGitHub repo aidenybai/react-grab (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
codemirror-lang-elixir4.0.0UNKNOWNApache-2.0GitHub repo livebook-dev/codemirror-lang-elixir (GitHub license API)
element-source0.0.3UNKNOWNMITGitHub repo aidenybai/element-source (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
lezer-elixir1.1.2UNKNOWNApache-2.0GitHub repo livebook-dev/lezer-elixir (GitHub license API)
map-stream0.1.0UNKNOWNMITGitHub repo dominictarr/map-stream (GitHub license API)
memorystream0.3.1UNKNOWNMITnpm registry licenses array: [{"type":"MIT","url":"http://github.com/JSBizon/node-memorystream/raw/master/LICENSE"}]
obug2.1.1UNKNOWNMITnpm registry license field
pause-stream0.0.11["MIT","Apache2"]MIT AND Apache-2.0GitHub repo dominictarr/pause-stream LICENSE file (dual-licensed MIT and Apache 2)
posthog-js1.369.0SEE LICENSE IN LICENSEApache-2.0GitHub repo PostHog/posthog-js LICENSE file (Apache License, Version 2.0)
valid-url1.0.9UNKNOWNMITGitHub repo ogt/valid-url LICENSE file

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Suppress blame mode when a preview ref is active.

searchParams.blame still reaches CodePreviewPanel when previewRef is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6faeb6d and 8e719ed.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (11)
  • CHANGELOG.md
  • packages/web/package.json
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameGutterExtension.ts
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameViewToggle.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/pureCodePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/page.tsx
  • packages/web/src/app/(app)/browse/hooks/utils.ts
  • packages/web/src/components/ui/toggle-group.tsx
  • packages/web/src/ee/features/codeNav/components/symbolHoverPopup/index.tsx
  • packages/web/src/features/git/getFileBlameApi.ts

@brendan-kellam
brendan-kellam merged commit 38d2274 into mainApr 30, 2026
8 of 9 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/file-blame-ui branch April 30, 2026 01:05
@github-actionsgithub-actionsBot mentioned this pull request Apr 30, 2026
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(web): add file blame view to code browser - #1160

Merged
brendan-kellam merged 5 commits into
mainfrom
brendan/file-blame-ui
Apr 30, 2026
Merged

feat(web): add file blame view to code browser#1160
brendan-kellam merged 5 commits into
mainfrom
brendan/file-blame-ui

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Apr 30, 2026

Copy link
Copy Markdown
Contributor
imageimage

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

  • Code / Blame segmented toggle next to the path header. URL-driven via the new ?blame=true query 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).
  • File stats next to the toggle: 1,246 lines · 42.6 KB. Line count from newlines in the source; byte size from Buffer.byteLength on the already-fetched content (no extra git call).
  • Blame gutter rendered to the left of line numbers when blame is enabled. Each region's first line shows: relative date, author avatar (via the /api/avatar resolver — 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.
  • Cursor-driven peer highlighting (GitLens-style): when the cursor is on a line attributed to commit X, every other line attributed to X gets a subtle bg-accent highlight, in both the source and gutter columns.
  • Region grouping: porcelain emits a fresh group whenever source-line numbering is discontinuous in a commit's snapshot. The API now coalesces adjacent same-commit ranges so consumers see one range per visual region.

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 in parsePorcelainBlame. 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 hit flushSync lifecycle errors and async-render flicker). Uses Prec.high to register before lineNumbers() from basicSetup. A StateField derives the active commit from the cursor's main selection and provides Decoration.line decorations + a gutterLineClassRangeSet for the peer highlight.
  • pureCodePreviewPanel.tsx: when blame is on, disables foldGutter, highlightActiveLine, and highlightActiveLineGutter in basicSetup (they collide with the blame gutter and the cursor-driven peer highlight). Wires router-driven onCommitClick and onReblameClick callbacks 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

  • Includes one unrelated commit (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

  • New Features
    • Added a Code/Blame toggle to switch views in the file preview.
    • Cursor-driven highlighting that marks all lines from the selected blame commit.
    • "Reblame" action to navigate to prior commits for a line range.
    • Blame UI shows commit metadata (author, message, relative date, optional avatar).
    • Visual age legend with color ramp for commit ages; header now shows line count and file size.
    • Improved handling when blame data fails to load.

brendan-kellamand others added 3 commits April 29, 2026 16:46
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>
@coderabbitai

coderabbitaiBot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

Pull request was closed or merged during review

Walkthrough

Adds 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

Cohort / File(s)Summary
Changelog & package
CHANGELOG.md, packages/web/package.json
Added changelog bullet; bumped @radix-ui/react-toggle and added @radix-ui/react-toggle-group.
Blame gutter extension
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameGutterExtension.ts
New CodeMirror Extension that builds per-line blame index from FileBlameResponse, renders a left blame gutter with commit info/avatars, reblame button, and decorations to highlight all lines for the active commit.
Blame UI & toggle
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameViewToggle.tsx, packages/web/src/components/ui/toggle-group.tsx
New BlameViewToggle component and a Radix-backed ToggleGroup wrapper for segmented control styling/behavior.
Code preview integration
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx, .../pureCodePreviewPanel.tsx
Added optional blame prop; conditional fetch of blame data; header UI updates (file stats, toggle, legend); wires blameGutterExtension into editor and navigation handlers for commit preview and reblame.
Page routing & URL utils
packages/web/src/app/(app)/browse/[...path]/page.tsx, packages/web/src/app/(app)/browse/hooks/utils.ts
Added blame=true search param support, BLAME_QUERY_PARAM constant, and blame?: boolean on blob BrowseProps; getBrowsePath appends blame query when set.
Blame parsing
packages/web/src/features/git/getFileBlameApi.ts
parsePorcelainBlame now merges contiguous ranges with the same commit hash to simplify ranges returned.
Blame age UI
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameAgeColors.ts, .../blameAgeLegend.tsx
New 10-step age color ramp with bucket computation and a legend component rendering the color scale.
Navigation state
packages/web/src/ee/features/codeNav/components/symbolHoverPopup/index.tsx
Now includes activeBottomPanelTab: 'explore' in navigation state when opening Explore for definitions/references.

Sequence Diagram

sequenceDiagram
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • msukkari
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main feature: adding a file blame view to the code browser, which aligns with the PR's primary objective of implementing GitHub-style blame functionality.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/file-blame-ui

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.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@github-actions

github-actionsBot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

License Audit

⚠️Status: PASS

MetricCount
Total packages2068
Resolved (non-standard)12
Unresolved0
Strong copyleft0
Weak copyleft39

Weak Copyleft Packages (informational)

PackageVersionLicense
@img/sharp-libvips-darwin-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.0.5LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-ppc641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-riscv641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.2.4LGPL-3.0-or-later
@img/sharp-wasm320.33.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-wasm320.34.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-win32-arm640.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.34.5Apache-2.0 AND LGPL-3.0-or-later
axe-core4.10.3MPL-2.0
dompurify3.4.0(MPL-2.0 OR Apache-2.0)
lightningcss1.32.0MPL-2.0
lightningcss-android-arm641.32.0MPL-2.0
lightningcss-darwin-arm641.32.0MPL-2.0
lightningcss-darwin-x641.32.0MPL-2.0
lightningcss-freebsd-x641.32.0MPL-2.0
lightningcss-linux-arm-gnueabihf1.32.0MPL-2.0
lightningcss-linux-arm64-gnu1.32.0MPL-2.0
lightningcss-linux-arm64-musl1.32.0MPL-2.0
lightningcss-linux-x64-gnu1.32.0MPL-2.0
lightningcss-linux-x64-musl1.32.0MPL-2.0
lightningcss-win32-arm64-msvc1.32.0MPL-2.0
lightningcss-win32-x64-msvc1.32.0MPL-2.0
Resolved Packages (12)
PackageVersionOriginalResolvedSource
@react-grab/cli0.1.23UNKNOWNMITGitHub repo aidenybai/react-grab (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
@react-grab/cli0.1.29UNKNOWNMITGitHub repo aidenybai/react-grab (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
@react-grab/mcp0.1.29UNKNOWNMITGitHub repo aidenybai/react-grab (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
codemirror-lang-elixir4.0.0UNKNOWNApache-2.0GitHub repo livebook-dev/codemirror-lang-elixir (GitHub license API)
element-source0.0.3UNKNOWNMITGitHub repo aidenybai/element-source (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
lezer-elixir1.1.2UNKNOWNApache-2.0GitHub repo livebook-dev/lezer-elixir (GitHub license API)
map-stream0.1.0UNKNOWNMITGitHub repo dominictarr/map-stream (GitHub license API)
memorystream0.3.1UNKNOWNMITnpm registry licenses array: [{"type":"MIT","url":"http://github.com/JSBizon/node-memorystream/raw/master/LICENSE"}]
obug2.1.1UNKNOWNMITnpm registry license field
pause-stream0.0.11["MIT","Apache2"]MIT AND Apache-2.0GitHub repo dominictarr/pause-stream LICENSE file (dual-licensed MIT and Apache 2)
posthog-js1.369.0SEE LICENSE IN LICENSEApache-2.0GitHub repo PostHog/posthog-js LICENSE file (Apache License, Version 2.0)
valid-url1.0.9UNKNOWNMITGitHub repo ogt/valid-url LICENSE file

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Suppress blame mode when a preview ref is active.

searchParams.blame still reaches CodePreviewPanel when previewRef is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6faeb6d and 8e719ed.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (11)
  • CHANGELOG.md
  • packages/web/package.json
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameGutterExtension.ts
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameViewToggle.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/pureCodePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/page.tsx
  • packages/web/src/app/(app)/browse/hooks/utils.ts
  • packages/web/src/components/ui/toggle-group.tsx
  • packages/web/src/ee/features/codeNav/components/symbolHoverPopup/index.tsx
  • packages/web/src/features/git/getFileBlameApi.ts

@brendan-kellam
brendan-kellam merged commit 38d2274 into mainApr 30, 2026
8 of 9 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/file-blame-ui branch April 30, 2026 01:05
@github-actionsgithub-actionsBot mentioned this pull request Apr 30, 2026
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(web): add file blame view to code browser - #1160

Merged
brendan-kellam merged 5 commits into
mainfrom
brendan/file-blame-ui
Apr 30, 2026
Merged

feat(web): add file blame view to code browser#1160
brendan-kellam merged 5 commits into
mainfrom
brendan/file-blame-ui

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Apr 30, 2026

Copy link
Copy Markdown
Contributor
imageimage

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

  • Code / Blame segmented toggle next to the path header. URL-driven via the new ?blame=true query 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).
  • File stats next to the toggle: 1,246 lines · 42.6 KB. Line count from newlines in the source; byte size from Buffer.byteLength on the already-fetched content (no extra git call).
  • Blame gutter rendered to the left of line numbers when blame is enabled. Each region's first line shows: relative date, author avatar (via the /api/avatar resolver — 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.
  • Cursor-driven peer highlighting (GitLens-style): when the cursor is on a line attributed to commit X, every other line attributed to X gets a subtle bg-accent highlight, in both the source and gutter columns.
  • Region grouping: porcelain emits a fresh group whenever source-line numbering is discontinuous in a commit's snapshot. The API now coalesces adjacent same-commit ranges so consumers see one range per visual region.

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 in parsePorcelainBlame. 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 hit flushSync lifecycle errors and async-render flicker). Uses Prec.high to register before lineNumbers() from basicSetup. A StateField derives the active commit from the cursor's main selection and provides Decoration.line decorations + a gutterLineClassRangeSet for the peer highlight.
  • pureCodePreviewPanel.tsx: when blame is on, disables foldGutter, highlightActiveLine, and highlightActiveLineGutter in basicSetup (they collide with the blame gutter and the cursor-driven peer highlight). Wires router-driven onCommitClick and onReblameClick callbacks 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

  • Includes one unrelated commit (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

  • New Features
    • Added a Code/Blame toggle to switch views in the file preview.
    • Cursor-driven highlighting that marks all lines from the selected blame commit.
    • "Reblame" action to navigate to prior commits for a line range.
    • Blame UI shows commit metadata (author, message, relative date, optional avatar).
    • Visual age legend with color ramp for commit ages; header now shows line count and file size.
    • Improved handling when blame data fails to load.

brendan-kellamand others added 3 commits April 29, 2026 16:46
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>
@coderabbitai

coderabbitaiBot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

Pull request was closed or merged during review

Walkthrough

Adds 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

Cohort / File(s)Summary
Changelog & package
CHANGELOG.md, packages/web/package.json
Added changelog bullet; bumped @radix-ui/react-toggle and added @radix-ui/react-toggle-group.
Blame gutter extension
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameGutterExtension.ts
New CodeMirror Extension that builds per-line blame index from FileBlameResponse, renders a left blame gutter with commit info/avatars, reblame button, and decorations to highlight all lines for the active commit.
Blame UI & toggle
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameViewToggle.tsx, packages/web/src/components/ui/toggle-group.tsx
New BlameViewToggle component and a Radix-backed ToggleGroup wrapper for segmented control styling/behavior.
Code preview integration
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx, .../pureCodePreviewPanel.tsx
Added optional blame prop; conditional fetch of blame data; header UI updates (file stats, toggle, legend); wires blameGutterExtension into editor and navigation handlers for commit preview and reblame.
Page routing & URL utils
packages/web/src/app/(app)/browse/[...path]/page.tsx, packages/web/src/app/(app)/browse/hooks/utils.ts
Added blame=true search param support, BLAME_QUERY_PARAM constant, and blame?: boolean on blob BrowseProps; getBrowsePath appends blame query when set.
Blame parsing
packages/web/src/features/git/getFileBlameApi.ts
parsePorcelainBlame now merges contiguous ranges with the same commit hash to simplify ranges returned.
Blame age UI
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameAgeColors.ts, .../blameAgeLegend.tsx
New 10-step age color ramp with bucket computation and a legend component rendering the color scale.
Navigation state
packages/web/src/ee/features/codeNav/components/symbolHoverPopup/index.tsx
Now includes activeBottomPanelTab: 'explore' in navigation state when opening Explore for definitions/references.

Sequence Diagram

sequenceDiagram
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • msukkari
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main feature: adding a file blame view to the code browser, which aligns with the PR's primary objective of implementing GitHub-style blame functionality.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/file-blame-ui

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.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@github-actions

github-actionsBot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

License Audit

⚠️Status: PASS

MetricCount
Total packages2068
Resolved (non-standard)12
Unresolved0
Strong copyleft0
Weak copyleft39

Weak Copyleft Packages (informational)

PackageVersionLicense
@img/sharp-libvips-darwin-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.0.5LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-ppc641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-riscv641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.2.4LGPL-3.0-or-later
@img/sharp-wasm320.33.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-wasm320.34.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-win32-arm640.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.34.5Apache-2.0 AND LGPL-3.0-or-later
axe-core4.10.3MPL-2.0
dompurify3.4.0(MPL-2.0 OR Apache-2.0)
lightningcss1.32.0MPL-2.0
lightningcss-android-arm641.32.0MPL-2.0
lightningcss-darwin-arm641.32.0MPL-2.0
lightningcss-darwin-x641.32.0MPL-2.0
lightningcss-freebsd-x641.32.0MPL-2.0
lightningcss-linux-arm-gnueabihf1.32.0MPL-2.0
lightningcss-linux-arm64-gnu1.32.0MPL-2.0
lightningcss-linux-arm64-musl1.32.0MPL-2.0
lightningcss-linux-x64-gnu1.32.0MPL-2.0
lightningcss-linux-x64-musl1.32.0MPL-2.0
lightningcss-win32-arm64-msvc1.32.0MPL-2.0
lightningcss-win32-x64-msvc1.32.0MPL-2.0
Resolved Packages (12)
PackageVersionOriginalResolvedSource
@react-grab/cli0.1.23UNKNOWNMITGitHub repo aidenybai/react-grab (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
@react-grab/cli0.1.29UNKNOWNMITGitHub repo aidenybai/react-grab (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
@react-grab/mcp0.1.29UNKNOWNMITGitHub repo aidenybai/react-grab (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
codemirror-lang-elixir4.0.0UNKNOWNApache-2.0GitHub repo livebook-dev/codemirror-lang-elixir (GitHub license API)
element-source0.0.3UNKNOWNMITGitHub repo aidenybai/element-source (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
lezer-elixir1.1.2UNKNOWNApache-2.0GitHub repo livebook-dev/lezer-elixir (GitHub license API)
map-stream0.1.0UNKNOWNMITGitHub repo dominictarr/map-stream (GitHub license API)
memorystream0.3.1UNKNOWNMITnpm registry licenses array: [{"type":"MIT","url":"http://github.com/JSBizon/node-memorystream/raw/master/LICENSE"}]
obug2.1.1UNKNOWNMITnpm registry license field
pause-stream0.0.11["MIT","Apache2"]MIT AND Apache-2.0GitHub repo dominictarr/pause-stream LICENSE file (dual-licensed MIT and Apache 2)
posthog-js1.369.0SEE LICENSE IN LICENSEApache-2.0GitHub repo PostHog/posthog-js LICENSE file (Apache License, Version 2.0)
valid-url1.0.9UNKNOWNMITGitHub repo ogt/valid-url LICENSE file

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Suppress blame mode when a preview ref is active.

searchParams.blame still reaches CodePreviewPanel when previewRef is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6faeb6d and 8e719ed.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (11)
  • CHANGELOG.md
  • packages/web/package.json
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameGutterExtension.ts
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameViewToggle.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/pureCodePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/page.tsx
  • packages/web/src/app/(app)/browse/hooks/utils.ts
  • packages/web/src/components/ui/toggle-group.tsx
  • packages/web/src/ee/features/codeNav/components/symbolHoverPopup/index.tsx
  • packages/web/src/features/git/getFileBlameApi.ts

@brendan-kellam
brendan-kellam merged commit 38d2274 into mainApr 30, 2026
8 of 9 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/file-blame-ui branch April 30, 2026 01:05
@github-actionsgithub-actionsBot mentioned this pull request Apr 30, 2026
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(web): add file blame view to code browser - #1160

Merged
brendan-kellam merged 5 commits into
mainfrom
brendan/file-blame-ui
Apr 30, 2026
Merged

feat(web): add file blame view to code browser#1160
brendan-kellam merged 5 commits into
mainfrom
brendan/file-blame-ui

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Apr 30, 2026

Copy link
Copy Markdown
Contributor
imageimage

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

  • Code / Blame segmented toggle next to the path header. URL-driven via the new ?blame=true query 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).
  • File stats next to the toggle: 1,246 lines · 42.6 KB. Line count from newlines in the source; byte size from Buffer.byteLength on the already-fetched content (no extra git call).
  • Blame gutter rendered to the left of line numbers when blame is enabled. Each region's first line shows: relative date, author avatar (via the /api/avatar resolver — 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.
  • Cursor-driven peer highlighting (GitLens-style): when the cursor is on a line attributed to commit X, every other line attributed to X gets a subtle bg-accent highlight, in both the source and gutter columns.
  • Region grouping: porcelain emits a fresh group whenever source-line numbering is discontinuous in a commit's snapshot. The API now coalesces adjacent same-commit ranges so consumers see one range per visual region.

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 in parsePorcelainBlame. 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 hit flushSync lifecycle errors and async-render flicker). Uses Prec.high to register before lineNumbers() from basicSetup. A StateField derives the active commit from the cursor's main selection and provides Decoration.line decorations + a gutterLineClassRangeSet for the peer highlight.
  • pureCodePreviewPanel.tsx: when blame is on, disables foldGutter, highlightActiveLine, and highlightActiveLineGutter in basicSetup (they collide with the blame gutter and the cursor-driven peer highlight). Wires router-driven onCommitClick and onReblameClick callbacks 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

  • Includes one unrelated commit (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

  • New Features
    • Added a Code/Blame toggle to switch views in the file preview.
    • Cursor-driven highlighting that marks all lines from the selected blame commit.
    • "Reblame" action to navigate to prior commits for a line range.
    • Blame UI shows commit metadata (author, message, relative date, optional avatar).
    • Visual age legend with color ramp for commit ages; header now shows line count and file size.
    • Improved handling when blame data fails to load.

brendan-kellamand others added 3 commits April 29, 2026 16:46
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>
@coderabbitai

coderabbitaiBot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

Pull request was closed or merged during review

Walkthrough

Adds 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

Cohort / File(s)Summary
Changelog & package
CHANGELOG.md, packages/web/package.json
Added changelog bullet; bumped @radix-ui/react-toggle and added @radix-ui/react-toggle-group.
Blame gutter extension
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameGutterExtension.ts
New CodeMirror Extension that builds per-line blame index from FileBlameResponse, renders a left blame gutter with commit info/avatars, reblame button, and decorations to highlight all lines for the active commit.
Blame UI & toggle
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameViewToggle.tsx, packages/web/src/components/ui/toggle-group.tsx
New BlameViewToggle component and a Radix-backed ToggleGroup wrapper for segmented control styling/behavior.
Code preview integration
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx, .../pureCodePreviewPanel.tsx
Added optional blame prop; conditional fetch of blame data; header UI updates (file stats, toggle, legend); wires blameGutterExtension into editor and navigation handlers for commit preview and reblame.
Page routing & URL utils
packages/web/src/app/(app)/browse/[...path]/page.tsx, packages/web/src/app/(app)/browse/hooks/utils.ts
Added blame=true search param support, BLAME_QUERY_PARAM constant, and blame?: boolean on blob BrowseProps; getBrowsePath appends blame query when set.
Blame parsing
packages/web/src/features/git/getFileBlameApi.ts
parsePorcelainBlame now merges contiguous ranges with the same commit hash to simplify ranges returned.
Blame age UI
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameAgeColors.ts, .../blameAgeLegend.tsx
New 10-step age color ramp with bucket computation and a legend component rendering the color scale.
Navigation state
packages/web/src/ee/features/codeNav/components/symbolHoverPopup/index.tsx
Now includes activeBottomPanelTab: 'explore' in navigation state when opening Explore for definitions/references.

Sequence Diagram

sequenceDiagram
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • msukkari
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main feature: adding a file blame view to the code browser, which aligns with the PR's primary objective of implementing GitHub-style blame functionality.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/file-blame-ui

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.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@github-actions

github-actionsBot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

License Audit

⚠️Status: PASS

MetricCount
Total packages2068
Resolved (non-standard)12
Unresolved0
Strong copyleft0
Weak copyleft39

Weak Copyleft Packages (informational)

PackageVersionLicense
@img/sharp-libvips-darwin-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.0.5LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-ppc641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-riscv641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.2.4LGPL-3.0-or-later
@img/sharp-wasm320.33.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-wasm320.34.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-win32-arm640.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.34.5Apache-2.0 AND LGPL-3.0-or-later
axe-core4.10.3MPL-2.0
dompurify3.4.0(MPL-2.0 OR Apache-2.0)
lightningcss1.32.0MPL-2.0
lightningcss-android-arm641.32.0MPL-2.0
lightningcss-darwin-arm641.32.0MPL-2.0
lightningcss-darwin-x641.32.0MPL-2.0
lightningcss-freebsd-x641.32.0MPL-2.0
lightningcss-linux-arm-gnueabihf1.32.0MPL-2.0
lightningcss-linux-arm64-gnu1.32.0MPL-2.0
lightningcss-linux-arm64-musl1.32.0MPL-2.0
lightningcss-linux-x64-gnu1.32.0MPL-2.0
lightningcss-linux-x64-musl1.32.0MPL-2.0
lightningcss-win32-arm64-msvc1.32.0MPL-2.0
lightningcss-win32-x64-msvc1.32.0MPL-2.0
Resolved Packages (12)
PackageVersionOriginalResolvedSource
@react-grab/cli0.1.23UNKNOWNMITGitHub repo aidenybai/react-grab (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
@react-grab/cli0.1.29UNKNOWNMITGitHub repo aidenybai/react-grab (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
@react-grab/mcp0.1.29UNKNOWNMITGitHub repo aidenybai/react-grab (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
codemirror-lang-elixir4.0.0UNKNOWNApache-2.0GitHub repo livebook-dev/codemirror-lang-elixir (GitHub license API)
element-source0.0.3UNKNOWNMITGitHub repo aidenybai/element-source (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
lezer-elixir1.1.2UNKNOWNApache-2.0GitHub repo livebook-dev/lezer-elixir (GitHub license API)
map-stream0.1.0UNKNOWNMITGitHub repo dominictarr/map-stream (GitHub license API)
memorystream0.3.1UNKNOWNMITnpm registry licenses array: [{"type":"MIT","url":"http://github.com/JSBizon/node-memorystream/raw/master/LICENSE"}]
obug2.1.1UNKNOWNMITnpm registry license field
pause-stream0.0.11["MIT","Apache2"]MIT AND Apache-2.0GitHub repo dominictarr/pause-stream LICENSE file (dual-licensed MIT and Apache 2)
posthog-js1.369.0SEE LICENSE IN LICENSEApache-2.0GitHub repo PostHog/posthog-js LICENSE file (Apache License, Version 2.0)
valid-url1.0.9UNKNOWNMITGitHub repo ogt/valid-url LICENSE file

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Suppress blame mode when a preview ref is active.

searchParams.blame still reaches CodePreviewPanel when previewRef is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6faeb6d and 8e719ed.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (11)
  • CHANGELOG.md
  • packages/web/package.json
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameGutterExtension.ts
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameViewToggle.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/pureCodePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/page.tsx
  • packages/web/src/app/(app)/browse/hooks/utils.ts
  • packages/web/src/components/ui/toggle-group.tsx
  • packages/web/src/ee/features/codeNav/components/symbolHoverPopup/index.tsx
  • packages/web/src/features/git/getFileBlameApi.ts

@brendan-kellam
brendan-kellam merged commit 38d2274 into mainApr 30, 2026
8 of 9 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/file-blame-ui branch April 30, 2026 01:05
@github-actionsgithub-actionsBot mentioned this pull request Apr 30, 2026
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(web): add file blame view to code browser - #1160

Merged
brendan-kellam merged 5 commits into
mainfrom
brendan/file-blame-ui
Apr 30, 2026
Merged

feat(web): add file blame view to code browser#1160
brendan-kellam merged 5 commits into
mainfrom
brendan/file-blame-ui

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Apr 30, 2026

Copy link
Copy Markdown
Contributor
imageimage

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

  • Code / Blame segmented toggle next to the path header. URL-driven via the new ?blame=true query 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).
  • File stats next to the toggle: 1,246 lines · 42.6 KB. Line count from newlines in the source; byte size from Buffer.byteLength on the already-fetched content (no extra git call).
  • Blame gutter rendered to the left of line numbers when blame is enabled. Each region's first line shows: relative date, author avatar (via the /api/avatar resolver — 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.
  • Cursor-driven peer highlighting (GitLens-style): when the cursor is on a line attributed to commit X, every other line attributed to X gets a subtle bg-accent highlight, in both the source and gutter columns.
  • Region grouping: porcelain emits a fresh group whenever source-line numbering is discontinuous in a commit's snapshot. The API now coalesces adjacent same-commit ranges so consumers see one range per visual region.

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 in parsePorcelainBlame. 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 hit flushSync lifecycle errors and async-render flicker). Uses Prec.high to register before lineNumbers() from basicSetup. A StateField derives the active commit from the cursor's main selection and provides Decoration.line decorations + a gutterLineClassRangeSet for the peer highlight.
  • pureCodePreviewPanel.tsx: when blame is on, disables foldGutter, highlightActiveLine, and highlightActiveLineGutter in basicSetup (they collide with the blame gutter and the cursor-driven peer highlight). Wires router-driven onCommitClick and onReblameClick callbacks 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

  • Includes one unrelated commit (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

  • New Features
    • Added a Code/Blame toggle to switch views in the file preview.
    • Cursor-driven highlighting that marks all lines from the selected blame commit.
    • "Reblame" action to navigate to prior commits for a line range.
    • Blame UI shows commit metadata (author, message, relative date, optional avatar).
    • Visual age legend with color ramp for commit ages; header now shows line count and file size.
    • Improved handling when blame data fails to load.

brendan-kellamand others added 3 commits April 29, 2026 16:46
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>
@coderabbitai

coderabbitaiBot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

Pull request was closed or merged during review

Walkthrough

Adds 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

Cohort / File(s)Summary
Changelog & package
CHANGELOG.md, packages/web/package.json
Added changelog bullet; bumped @radix-ui/react-toggle and added @radix-ui/react-toggle-group.
Blame gutter extension
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameGutterExtension.ts
New CodeMirror Extension that builds per-line blame index from FileBlameResponse, renders a left blame gutter with commit info/avatars, reblame button, and decorations to highlight all lines for the active commit.
Blame UI & toggle
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameViewToggle.tsx, packages/web/src/components/ui/toggle-group.tsx
New BlameViewToggle component and a Radix-backed ToggleGroup wrapper for segmented control styling/behavior.
Code preview integration
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx, .../pureCodePreviewPanel.tsx
Added optional blame prop; conditional fetch of blame data; header UI updates (file stats, toggle, legend); wires blameGutterExtension into editor and navigation handlers for commit preview and reblame.
Page routing & URL utils
packages/web/src/app/(app)/browse/[...path]/page.tsx, packages/web/src/app/(app)/browse/hooks/utils.ts
Added blame=true search param support, BLAME_QUERY_PARAM constant, and blame?: boolean on blob BrowseProps; getBrowsePath appends blame query when set.
Blame parsing
packages/web/src/features/git/getFileBlameApi.ts
parsePorcelainBlame now merges contiguous ranges with the same commit hash to simplify ranges returned.
Blame age UI
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameAgeColors.ts, .../blameAgeLegend.tsx
New 10-step age color ramp with bucket computation and a legend component rendering the color scale.
Navigation state
packages/web/src/ee/features/codeNav/components/symbolHoverPopup/index.tsx
Now includes activeBottomPanelTab: 'explore' in navigation state when opening Explore for definitions/references.

Sequence Diagram

sequenceDiagram
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • msukkari
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main feature: adding a file blame view to the code browser, which aligns with the PR's primary objective of implementing GitHub-style blame functionality.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/file-blame-ui

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.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@github-actions

github-actionsBot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

License Audit

⚠️Status: PASS

MetricCount
Total packages2068
Resolved (non-standard)12
Unresolved0
Strong copyleft0
Weak copyleft39

Weak Copyleft Packages (informational)

PackageVersionLicense
@img/sharp-libvips-darwin-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.0.5LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-ppc641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-riscv641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.2.4LGPL-3.0-or-later
@img/sharp-wasm320.33.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-wasm320.34.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-win32-arm640.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.34.5Apache-2.0 AND LGPL-3.0-or-later
axe-core4.10.3MPL-2.0
dompurify3.4.0(MPL-2.0 OR Apache-2.0)
lightningcss1.32.0MPL-2.0
lightningcss-android-arm641.32.0MPL-2.0
lightningcss-darwin-arm641.32.0MPL-2.0
lightningcss-darwin-x641.32.0MPL-2.0
lightningcss-freebsd-x641.32.0MPL-2.0
lightningcss-linux-arm-gnueabihf1.32.0MPL-2.0
lightningcss-linux-arm64-gnu1.32.0MPL-2.0
lightningcss-linux-arm64-musl1.32.0MPL-2.0
lightningcss-linux-x64-gnu1.32.0MPL-2.0
lightningcss-linux-x64-musl1.32.0MPL-2.0
lightningcss-win32-arm64-msvc1.32.0MPL-2.0
lightningcss-win32-x64-msvc1.32.0MPL-2.0
Resolved Packages (12)
PackageVersionOriginalResolvedSource
@react-grab/cli0.1.23UNKNOWNMITGitHub repo aidenybai/react-grab (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
@react-grab/cli0.1.29UNKNOWNMITGitHub repo aidenybai/react-grab (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
@react-grab/mcp0.1.29UNKNOWNMITGitHub repo aidenybai/react-grab (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
codemirror-lang-elixir4.0.0UNKNOWNApache-2.0GitHub repo livebook-dev/codemirror-lang-elixir (GitHub license API)
element-source0.0.3UNKNOWNMITGitHub repo aidenybai/element-source (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
lezer-elixir1.1.2UNKNOWNApache-2.0GitHub repo livebook-dev/lezer-elixir (GitHub license API)
map-stream0.1.0UNKNOWNMITGitHub repo dominictarr/map-stream (GitHub license API)
memorystream0.3.1UNKNOWNMITnpm registry licenses array: [{"type":"MIT","url":"http://github.com/JSBizon/node-memorystream/raw/master/LICENSE"}]
obug2.1.1UNKNOWNMITnpm registry license field
pause-stream0.0.11["MIT","Apache2"]MIT AND Apache-2.0GitHub repo dominictarr/pause-stream LICENSE file (dual-licensed MIT and Apache 2)
posthog-js1.369.0SEE LICENSE IN LICENSEApache-2.0GitHub repo PostHog/posthog-js LICENSE file (Apache License, Version 2.0)
valid-url1.0.9UNKNOWNMITGitHub repo ogt/valid-url LICENSE file

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Suppress blame mode when a preview ref is active.

searchParams.blame still reaches CodePreviewPanel when previewRef is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6faeb6d and 8e719ed.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (11)
  • CHANGELOG.md
  • packages/web/package.json
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameGutterExtension.ts
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameViewToggle.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/pureCodePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/page.tsx
  • packages/web/src/app/(app)/browse/hooks/utils.ts
  • packages/web/src/components/ui/toggle-group.tsx
  • packages/web/src/ee/features/codeNav/components/symbolHoverPopup/index.tsx
  • packages/web/src/features/git/getFileBlameApi.ts

@brendan-kellam
brendan-kellam merged commit 38d2274 into mainApr 30, 2026
8 of 9 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/file-blame-ui branch April 30, 2026 01:05
@github-actionsgithub-actionsBot mentioned this pull request Apr 30, 2026
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(web): add file blame view to code browser - #1160

Merged
brendan-kellam merged 5 commits into
mainfrom
brendan/file-blame-ui
Apr 30, 2026
Merged

feat(web): add file blame view to code browser#1160
brendan-kellam merged 5 commits into
mainfrom
brendan/file-blame-ui

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Apr 30, 2026

Copy link
Copy Markdown
Contributor
imageimage

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

  • Code / Blame segmented toggle next to the path header. URL-driven via the new ?blame=true query 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).
  • File stats next to the toggle: 1,246 lines · 42.6 KB. Line count from newlines in the source; byte size from Buffer.byteLength on the already-fetched content (no extra git call).
  • Blame gutter rendered to the left of line numbers when blame is enabled. Each region's first line shows: relative date, author avatar (via the /api/avatar resolver — 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.
  • Cursor-driven peer highlighting (GitLens-style): when the cursor is on a line attributed to commit X, every other line attributed to X gets a subtle bg-accent highlight, in both the source and gutter columns.
  • Region grouping: porcelain emits a fresh group whenever source-line numbering is discontinuous in a commit's snapshot. The API now coalesces adjacent same-commit ranges so consumers see one range per visual region.

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 in parsePorcelainBlame. 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 hit flushSync lifecycle errors and async-render flicker). Uses Prec.high to register before lineNumbers() from basicSetup. A StateField derives the active commit from the cursor's main selection and provides Decoration.line decorations + a gutterLineClassRangeSet for the peer highlight.
  • pureCodePreviewPanel.tsx: when blame is on, disables foldGutter, highlightActiveLine, and highlightActiveLineGutter in basicSetup (they collide with the blame gutter and the cursor-driven peer highlight). Wires router-driven onCommitClick and onReblameClick callbacks 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

  • Includes one unrelated commit (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

  • New Features
    • Added a Code/Blame toggle to switch views in the file preview.
    • Cursor-driven highlighting that marks all lines from the selected blame commit.
    • "Reblame" action to navigate to prior commits for a line range.
    • Blame UI shows commit metadata (author, message, relative date, optional avatar).
    • Visual age legend with color ramp for commit ages; header now shows line count and file size.
    • Improved handling when blame data fails to load.

brendan-kellamand others added 3 commits April 29, 2026 16:46
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>
@coderabbitai

coderabbitaiBot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

Pull request was closed or merged during review

Walkthrough

Adds 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

Cohort / File(s)Summary
Changelog & package
CHANGELOG.md, packages/web/package.json
Added changelog bullet; bumped @radix-ui/react-toggle and added @radix-ui/react-toggle-group.
Blame gutter extension
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameGutterExtension.ts
New CodeMirror Extension that builds per-line blame index from FileBlameResponse, renders a left blame gutter with commit info/avatars, reblame button, and decorations to highlight all lines for the active commit.
Blame UI & toggle
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameViewToggle.tsx, packages/web/src/components/ui/toggle-group.tsx
New BlameViewToggle component and a Radix-backed ToggleGroup wrapper for segmented control styling/behavior.
Code preview integration
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx, .../pureCodePreviewPanel.tsx
Added optional blame prop; conditional fetch of blame data; header UI updates (file stats, toggle, legend); wires blameGutterExtension into editor and navigation handlers for commit preview and reblame.
Page routing & URL utils
packages/web/src/app/(app)/browse/[...path]/page.tsx, packages/web/src/app/(app)/browse/hooks/utils.ts
Added blame=true search param support, BLAME_QUERY_PARAM constant, and blame?: boolean on blob BrowseProps; getBrowsePath appends blame query when set.
Blame parsing
packages/web/src/features/git/getFileBlameApi.ts
parsePorcelainBlame now merges contiguous ranges with the same commit hash to simplify ranges returned.
Blame age UI
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameAgeColors.ts, .../blameAgeLegend.tsx
New 10-step age color ramp with bucket computation and a legend component rendering the color scale.
Navigation state
packages/web/src/ee/features/codeNav/components/symbolHoverPopup/index.tsx
Now includes activeBottomPanelTab: 'explore' in navigation state when opening Explore for definitions/references.

Sequence Diagram

sequenceDiagram
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • msukkari
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main feature: adding a file blame view to the code browser, which aligns with the PR's primary objective of implementing GitHub-style blame functionality.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/file-blame-ui

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.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@github-actions

github-actionsBot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

License Audit

⚠️Status: PASS

MetricCount
Total packages2068
Resolved (non-standard)12
Unresolved0
Strong copyleft0
Weak copyleft39

Weak Copyleft Packages (informational)

PackageVersionLicense
@img/sharp-libvips-darwin-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.0.5LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-ppc641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-riscv641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.2.4LGPL-3.0-or-later
@img/sharp-wasm320.33.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-wasm320.34.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-win32-arm640.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.34.5Apache-2.0 AND LGPL-3.0-or-later
axe-core4.10.3MPL-2.0
dompurify3.4.0(MPL-2.0 OR Apache-2.0)
lightningcss1.32.0MPL-2.0
lightningcss-android-arm641.32.0MPL-2.0
lightningcss-darwin-arm641.32.0MPL-2.0
lightningcss-darwin-x641.32.0MPL-2.0
lightningcss-freebsd-x641.32.0MPL-2.0
lightningcss-linux-arm-gnueabihf1.32.0MPL-2.0
lightningcss-linux-arm64-gnu1.32.0MPL-2.0
lightningcss-linux-arm64-musl1.32.0MPL-2.0
lightningcss-linux-x64-gnu1.32.0MPL-2.0
lightningcss-linux-x64-musl1.32.0MPL-2.0
lightningcss-win32-arm64-msvc1.32.0MPL-2.0
lightningcss-win32-x64-msvc1.32.0MPL-2.0
Resolved Packages (12)
PackageVersionOriginalResolvedSource
@react-grab/cli0.1.23UNKNOWNMITGitHub repo aidenybai/react-grab (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
@react-grab/cli0.1.29UNKNOWNMITGitHub repo aidenybai/react-grab (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
@react-grab/mcp0.1.29UNKNOWNMITGitHub repo aidenybai/react-grab (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
codemirror-lang-elixir4.0.0UNKNOWNApache-2.0GitHub repo livebook-dev/codemirror-lang-elixir (GitHub license API)
element-source0.0.3UNKNOWNMITGitHub repo aidenybai/element-source (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
lezer-elixir1.1.2UNKNOWNApache-2.0GitHub repo livebook-dev/lezer-elixir (GitHub license API)
map-stream0.1.0UNKNOWNMITGitHub repo dominictarr/map-stream (GitHub license API)
memorystream0.3.1UNKNOWNMITnpm registry licenses array: [{"type":"MIT","url":"http://github.com/JSBizon/node-memorystream/raw/master/LICENSE"}]
obug2.1.1UNKNOWNMITnpm registry license field
pause-stream0.0.11["MIT","Apache2"]MIT AND Apache-2.0GitHub repo dominictarr/pause-stream LICENSE file (dual-licensed MIT and Apache 2)
posthog-js1.369.0SEE LICENSE IN LICENSEApache-2.0GitHub repo PostHog/posthog-js LICENSE file (Apache License, Version 2.0)
valid-url1.0.9UNKNOWNMITGitHub repo ogt/valid-url LICENSE file

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Suppress blame mode when a preview ref is active.

searchParams.blame still reaches CodePreviewPanel when previewRef is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6faeb6d and 8e719ed.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (11)
  • CHANGELOG.md
  • packages/web/package.json
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameGutterExtension.ts
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameViewToggle.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/pureCodePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/page.tsx
  • packages/web/src/app/(app)/browse/hooks/utils.ts
  • packages/web/src/components/ui/toggle-group.tsx
  • packages/web/src/ee/features/codeNav/components/symbolHoverPopup/index.tsx
  • packages/web/src/features/git/getFileBlameApi.ts

@brendan-kellam
brendan-kellam merged commit 38d2274 into mainApr 30, 2026
8 of 9 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/file-blame-ui branch April 30, 2026 01:05
@github-actionsgithub-actionsBot mentioned this pull request Apr 30, 2026
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(web): add file blame view to code browser - #1160

Merged
brendan-kellam merged 5 commits into
mainfrom
brendan/file-blame-ui
Apr 30, 2026
Merged

feat(web): add file blame view to code browser#1160
brendan-kellam merged 5 commits into
mainfrom
brendan/file-blame-ui

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Apr 30, 2026

Copy link
Copy Markdown
Contributor
imageimage

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

  • Code / Blame segmented toggle next to the path header. URL-driven via the new ?blame=true query 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).
  • File stats next to the toggle: 1,246 lines · 42.6 KB. Line count from newlines in the source; byte size from Buffer.byteLength on the already-fetched content (no extra git call).
  • Blame gutter rendered to the left of line numbers when blame is enabled. Each region's first line shows: relative date, author avatar (via the /api/avatar resolver — 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.
  • Cursor-driven peer highlighting (GitLens-style): when the cursor is on a line attributed to commit X, every other line attributed to X gets a subtle bg-accent highlight, in both the source and gutter columns.
  • Region grouping: porcelain emits a fresh group whenever source-line numbering is discontinuous in a commit's snapshot. The API now coalesces adjacent same-commit ranges so consumers see one range per visual region.

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 in parsePorcelainBlame. 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 hit flushSync lifecycle errors and async-render flicker). Uses Prec.high to register before lineNumbers() from basicSetup. A StateField derives the active commit from the cursor's main selection and provides Decoration.line decorations + a gutterLineClassRangeSet for the peer highlight.
  • pureCodePreviewPanel.tsx: when blame is on, disables foldGutter, highlightActiveLine, and highlightActiveLineGutter in basicSetup (they collide with the blame gutter and the cursor-driven peer highlight). Wires router-driven onCommitClick and onReblameClick callbacks 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

  • Includes one unrelated commit (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

  • New Features
    • Added a Code/Blame toggle to switch views in the file preview.
    • Cursor-driven highlighting that marks all lines from the selected blame commit.
    • "Reblame" action to navigate to prior commits for a line range.
    • Blame UI shows commit metadata (author, message, relative date, optional avatar).
    • Visual age legend with color ramp for commit ages; header now shows line count and file size.
    • Improved handling when blame data fails to load.

brendan-kellamand others added 3 commits April 29, 2026 16:46
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>
@coderabbitai

coderabbitaiBot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

Pull request was closed or merged during review

Walkthrough

Adds 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

Cohort / File(s)Summary
Changelog & package
CHANGELOG.md, packages/web/package.json
Added changelog bullet; bumped @radix-ui/react-toggle and added @radix-ui/react-toggle-group.
Blame gutter extension
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameGutterExtension.ts
New CodeMirror Extension that builds per-line blame index from FileBlameResponse, renders a left blame gutter with commit info/avatars, reblame button, and decorations to highlight all lines for the active commit.
Blame UI & toggle
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameViewToggle.tsx, packages/web/src/components/ui/toggle-group.tsx
New BlameViewToggle component and a Radix-backed ToggleGroup wrapper for segmented control styling/behavior.
Code preview integration
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx, .../pureCodePreviewPanel.tsx
Added optional blame prop; conditional fetch of blame data; header UI updates (file stats, toggle, legend); wires blameGutterExtension into editor and navigation handlers for commit preview and reblame.
Page routing & URL utils
packages/web/src/app/(app)/browse/[...path]/page.tsx, packages/web/src/app/(app)/browse/hooks/utils.ts
Added blame=true search param support, BLAME_QUERY_PARAM constant, and blame?: boolean on blob BrowseProps; getBrowsePath appends blame query when set.
Blame parsing
packages/web/src/features/git/getFileBlameApi.ts
parsePorcelainBlame now merges contiguous ranges with the same commit hash to simplify ranges returned.
Blame age UI
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameAgeColors.ts, .../blameAgeLegend.tsx
New 10-step age color ramp with bucket computation and a legend component rendering the color scale.
Navigation state
packages/web/src/ee/features/codeNav/components/symbolHoverPopup/index.tsx
Now includes activeBottomPanelTab: 'explore' in navigation state when opening Explore for definitions/references.

Sequence Diagram

sequenceDiagram
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • msukkari
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main feature: adding a file blame view to the code browser, which aligns with the PR's primary objective of implementing GitHub-style blame functionality.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/file-blame-ui

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.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@github-actions

github-actionsBot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

License Audit

⚠️Status: PASS

MetricCount
Total packages2068
Resolved (non-standard)12
Unresolved0
Strong copyleft0
Weak copyleft39

Weak Copyleft Packages (informational)

PackageVersionLicense
@img/sharp-libvips-darwin-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.0.5LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-ppc641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-riscv641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.2.4LGPL-3.0-or-later
@img/sharp-wasm320.33.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-wasm320.34.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-win32-arm640.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.34.5Apache-2.0 AND LGPL-3.0-or-later
axe-core4.10.3MPL-2.0
dompurify3.4.0(MPL-2.0 OR Apache-2.0)
lightningcss1.32.0MPL-2.0
lightningcss-android-arm641.32.0MPL-2.0
lightningcss-darwin-arm641.32.0MPL-2.0
lightningcss-darwin-x641.32.0MPL-2.0
lightningcss-freebsd-x641.32.0MPL-2.0
lightningcss-linux-arm-gnueabihf1.32.0MPL-2.0
lightningcss-linux-arm64-gnu1.32.0MPL-2.0
lightningcss-linux-arm64-musl1.32.0MPL-2.0
lightningcss-linux-x64-gnu1.32.0MPL-2.0
lightningcss-linux-x64-musl1.32.0MPL-2.0
lightningcss-win32-arm64-msvc1.32.0MPL-2.0
lightningcss-win32-x64-msvc1.32.0MPL-2.0
Resolved Packages (12)
PackageVersionOriginalResolvedSource
@react-grab/cli0.1.23UNKNOWNMITGitHub repo aidenybai/react-grab (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
@react-grab/cli0.1.29UNKNOWNMITGitHub repo aidenybai/react-grab (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
@react-grab/mcp0.1.29UNKNOWNMITGitHub repo aidenybai/react-grab (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
codemirror-lang-elixir4.0.0UNKNOWNApache-2.0GitHub repo livebook-dev/codemirror-lang-elixir (GitHub license API)
element-source0.0.3UNKNOWNMITGitHub repo aidenybai/element-source (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
lezer-elixir1.1.2UNKNOWNApache-2.0GitHub repo livebook-dev/lezer-elixir (GitHub license API)
map-stream0.1.0UNKNOWNMITGitHub repo dominictarr/map-stream (GitHub license API)
memorystream0.3.1UNKNOWNMITnpm registry licenses array: [{"type":"MIT","url":"http://github.com/JSBizon/node-memorystream/raw/master/LICENSE"}]
obug2.1.1UNKNOWNMITnpm registry license field
pause-stream0.0.11["MIT","Apache2"]MIT AND Apache-2.0GitHub repo dominictarr/pause-stream LICENSE file (dual-licensed MIT and Apache 2)
posthog-js1.369.0SEE LICENSE IN LICENSEApache-2.0GitHub repo PostHog/posthog-js LICENSE file (Apache License, Version 2.0)
valid-url1.0.9UNKNOWNMITGitHub repo ogt/valid-url LICENSE file

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Suppress blame mode when a preview ref is active.

searchParams.blame still reaches CodePreviewPanel when previewRef is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6faeb6d and 8e719ed.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (11)
  • CHANGELOG.md
  • packages/web/package.json
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameGutterExtension.ts
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameViewToggle.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/pureCodePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/page.tsx
  • packages/web/src/app/(app)/browse/hooks/utils.ts
  • packages/web/src/components/ui/toggle-group.tsx
  • packages/web/src/ee/features/codeNav/components/symbolHoverPopup/index.tsx
  • packages/web/src/features/git/getFileBlameApi.ts

@brendan-kellam
brendan-kellam merged commit 38d2274 into mainApr 30, 2026
8 of 9 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/file-blame-ui branch April 30, 2026 01:05
@github-actionsgithub-actionsBot mentioned this pull request Apr 30, 2026
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(web): add file blame view to code browser - #1160

Merged
brendan-kellam merged 5 commits into
mainfrom
brendan/file-blame-ui
Apr 30, 2026
Merged

feat(web): add file blame view to code browser#1160
brendan-kellam merged 5 commits into
mainfrom
brendan/file-blame-ui

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Apr 30, 2026

Copy link
Copy Markdown
Contributor
imageimage

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

  • Code / Blame segmented toggle next to the path header. URL-driven via the new ?blame=true query 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).
  • File stats next to the toggle: 1,246 lines · 42.6 KB. Line count from newlines in the source; byte size from Buffer.byteLength on the already-fetched content (no extra git call).
  • Blame gutter rendered to the left of line numbers when blame is enabled. Each region's first line shows: relative date, author avatar (via the /api/avatar resolver — 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.
  • Cursor-driven peer highlighting (GitLens-style): when the cursor is on a line attributed to commit X, every other line attributed to X gets a subtle bg-accent highlight, in both the source and gutter columns.
  • Region grouping: porcelain emits a fresh group whenever source-line numbering is discontinuous in a commit's snapshot. The API now coalesces adjacent same-commit ranges so consumers see one range per visual region.

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 in parsePorcelainBlame. 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 hit flushSync lifecycle errors and async-render flicker). Uses Prec.high to register before lineNumbers() from basicSetup. A StateField derives the active commit from the cursor's main selection and provides Decoration.line decorations + a gutterLineClassRangeSet for the peer highlight.
  • pureCodePreviewPanel.tsx: when blame is on, disables foldGutter, highlightActiveLine, and highlightActiveLineGutter in basicSetup (they collide with the blame gutter and the cursor-driven peer highlight). Wires router-driven onCommitClick and onReblameClick callbacks 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

  • Includes one unrelated commit (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

  • New Features
    • Added a Code/Blame toggle to switch views in the file preview.
    • Cursor-driven highlighting that marks all lines from the selected blame commit.
    • "Reblame" action to navigate to prior commits for a line range.
    • Blame UI shows commit metadata (author, message, relative date, optional avatar).
    • Visual age legend with color ramp for commit ages; header now shows line count and file size.
    • Improved handling when blame data fails to load.

brendan-kellamand others added 3 commits April 29, 2026 16:46
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>
@coderabbitai

coderabbitaiBot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

Pull request was closed or merged during review

Walkthrough

Adds 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

Cohort / File(s)Summary
Changelog & package
CHANGELOG.md, packages/web/package.json
Added changelog bullet; bumped @radix-ui/react-toggle and added @radix-ui/react-toggle-group.
Blame gutter extension
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameGutterExtension.ts
New CodeMirror Extension that builds per-line blame index from FileBlameResponse, renders a left blame gutter with commit info/avatars, reblame button, and decorations to highlight all lines for the active commit.
Blame UI & toggle
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameViewToggle.tsx, packages/web/src/components/ui/toggle-group.tsx
New BlameViewToggle component and a Radix-backed ToggleGroup wrapper for segmented control styling/behavior.
Code preview integration
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx, .../pureCodePreviewPanel.tsx
Added optional blame prop; conditional fetch of blame data; header UI updates (file stats, toggle, legend); wires blameGutterExtension into editor and navigation handlers for commit preview and reblame.
Page routing & URL utils
packages/web/src/app/(app)/browse/[...path]/page.tsx, packages/web/src/app/(app)/browse/hooks/utils.ts
Added blame=true search param support, BLAME_QUERY_PARAM constant, and blame?: boolean on blob BrowseProps; getBrowsePath appends blame query when set.
Blame parsing
packages/web/src/features/git/getFileBlameApi.ts
parsePorcelainBlame now merges contiguous ranges with the same commit hash to simplify ranges returned.
Blame age UI
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameAgeColors.ts, .../blameAgeLegend.tsx
New 10-step age color ramp with bucket computation and a legend component rendering the color scale.
Navigation state
packages/web/src/ee/features/codeNav/components/symbolHoverPopup/index.tsx
Now includes activeBottomPanelTab: 'explore' in navigation state when opening Explore for definitions/references.

Sequence Diagram

sequenceDiagram
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • msukkari
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main feature: adding a file blame view to the code browser, which aligns with the PR's primary objective of implementing GitHub-style blame functionality.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/file-blame-ui

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.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@github-actions

github-actionsBot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

License Audit

⚠️Status: PASS

MetricCount
Total packages2068
Resolved (non-standard)12
Unresolved0
Strong copyleft0
Weak copyleft39

Weak Copyleft Packages (informational)

PackageVersionLicense
@img/sharp-libvips-darwin-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.0.5LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-ppc641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-riscv641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.2.4LGPL-3.0-or-later
@img/sharp-wasm320.33.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-wasm320.34.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-win32-arm640.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.34.5Apache-2.0 AND LGPL-3.0-or-later
axe-core4.10.3MPL-2.0
dompurify3.4.0(MPL-2.0 OR Apache-2.0)
lightningcss1.32.0MPL-2.0
lightningcss-android-arm641.32.0MPL-2.0
lightningcss-darwin-arm641.32.0MPL-2.0
lightningcss-darwin-x641.32.0MPL-2.0
lightningcss-freebsd-x641.32.0MPL-2.0
lightningcss-linux-arm-gnueabihf1.32.0MPL-2.0
lightningcss-linux-arm64-gnu1.32.0MPL-2.0
lightningcss-linux-arm64-musl1.32.0MPL-2.0
lightningcss-linux-x64-gnu1.32.0MPL-2.0
lightningcss-linux-x64-musl1.32.0MPL-2.0
lightningcss-win32-arm64-msvc1.32.0MPL-2.0
lightningcss-win32-x64-msvc1.32.0MPL-2.0
Resolved Packages (12)
PackageVersionOriginalResolvedSource
@react-grab/cli0.1.23UNKNOWNMITGitHub repo aidenybai/react-grab (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
@react-grab/cli0.1.29UNKNOWNMITGitHub repo aidenybai/react-grab (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
@react-grab/mcp0.1.29UNKNOWNMITGitHub repo aidenybai/react-grab (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
codemirror-lang-elixir4.0.0UNKNOWNApache-2.0GitHub repo livebook-dev/codemirror-lang-elixir (GitHub license API)
element-source0.0.3UNKNOWNMITGitHub repo aidenybai/element-source (same maintainer aiden.bai05@gmail.com, MIT license confirmed)
lezer-elixir1.1.2UNKNOWNApache-2.0GitHub repo livebook-dev/lezer-elixir (GitHub license API)
map-stream0.1.0UNKNOWNMITGitHub repo dominictarr/map-stream (GitHub license API)
memorystream0.3.1UNKNOWNMITnpm registry licenses array: [{"type":"MIT","url":"http://github.com/JSBizon/node-memorystream/raw/master/LICENSE"}]
obug2.1.1UNKNOWNMITnpm registry license field
pause-stream0.0.11["MIT","Apache2"]MIT AND Apache-2.0GitHub repo dominictarr/pause-stream LICENSE file (dual-licensed MIT and Apache 2)
posthog-js1.369.0SEE LICENSE IN LICENSEApache-2.0GitHub repo PostHog/posthog-js LICENSE file (Apache License, Version 2.0)
valid-url1.0.9UNKNOWNMITGitHub repo ogt/valid-url LICENSE file

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Suppress blame mode when a preview ref is active.

searchParams.blame still reaches CodePreviewPanel when previewRef is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6faeb6d and 8e719ed.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (11)
  • CHANGELOG.md
  • packages/web/package.json
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameGutterExtension.ts
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/blameViewToggle.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/pureCodePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/page.tsx
  • packages/web/src/app/(app)/browse/hooks/utils.ts
  • packages/web/src/components/ui/toggle-group.tsx
  • packages/web/src/ee/features/codeNav/components/symbolHoverPopup/index.tsx
  • packages/web/src/features/git/getFileBlameApi.ts

@brendan-kellam
brendan-kellam merged commit 38d2274 into mainApr 30, 2026
8 of 9 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/file-blame-ui branch April 30, 2026 01:05
@github-actionsgithub-actionsBot mentioned this pull request Apr 30, 2026
Sign up for freeto 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

@brendan-kellam