Skip to content

feat(web): add git history view#1150

Merged
brendan-kellam merged 12 commits intomainfrom
bkellam/git-history-SOU-131
Apr 25, 2026
Merged

feat(web): add git history view#1150
brendan-kellam merged 12 commits intomainfrom
bkellam/git-history-SOU-131

Conversation

@brendan-kellam
Copy link
Copy Markdown
Contributor

@brendan-kellam brendan-kellam commented Apr 24, 2026

Fixes SOU-131

Screenshots:

Commit header (single author):
image

Commit header (multi-author):
image

Commit header w/ commit body expanded:
image

Commit history view (file):
image

Commit history view (repo root):
image
note: we aren't explicitly linking to this anywhere. A good place to surface this would probably be if we built a "repository landing page" that render's the root README.md.

Filter commit history by user:
image

Filter commit history by date range:
image

Bottom bar (collapsed):
image

Bottom bar (expanded on history):
image

Summary

Adds a GitHub-style git history experience to the code browser:

  • Commit header above open files — shows the most recent commit that touched the file (avatar group, author names, message, body toggle, short SHA, relative date, and a History button)
  • New commits pathType/browse/<repo>@<rev>/-/commits[/<path>] renders a paginated commit history view, grouped by date with sticky section headers
  • Per-row actions — copy SHA, view code at this commit, view repository at this commit
  • Author filter — popover with top 100 authors (sorted by commit count), search, checkmark for selected, and a "Filter on author X" escape valve for authors outside the top 100
  • Date range filter — shadcn range Calendar with two-click range selection (even for single-day), future dates disabled, inclusive end date
  • Prev/Next pagination — URL-driven, preserves all active filters

The author and date filters are threaded through a shareable URL (?author=&since=&until=&page=), and a new public API endpoint GET /api/commits/authors is exposed under the Git tag.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Browse repository commit history with per-file latest-commit header, paginated commits, and “View full history.”
    • Filter commits by author (selectable list) and by date range via an inline calendar picker.
    • Commit rows now show avatars, expanded details, copy-SHA, and links to view code or repo at a specific commit.
    • Bottom panel gains History/Explore tabs.
  • Documentation

    • Public API: added GET /api/commits/authors with schemas and clarified commit-query regex semantics.

brendan-kellam and others added 7 commits April 24, 2026 12:01
Adds a GitHub-style commit row below the path header on the file browse
view, showing the author, message, short SHA, and relative date for the
most recent commit that touched the file. A no-op History button is
included as a placeholder for the upcoming file-history view.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Parse Co-authored-by trailers from the commit body and display an
AvatarGroup with the combined author list, falling back to an overflow
count when more than two. Add a toggle button next to the commit
message to reveal the full commit body inline.

Upgrades the Avatar ui component to include AvatarGroup, AvatarGroupCount,
and AvatarBadge primitives.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extend the browse URL scheme to support /-/commits/<path> as a third
pathType alongside blob and tree. Empty paths are permitted so the same
route can later serve repo-level history.

The History button in the commit header now links to this route via
getBrowsePath. The page renders a placeholder for the commits pathType;
the actual commit list panel will follow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Render a paginated commit history list when pathType is commits. The
view reuses PathHeader for the "History for <repo> / <path>" subheader,
groups commits by local date with sticky section headers, and shows
Previous/Next links driven by a page query param.

Each row renders the commit message, co-authors, short SHA, copy action,
view-code-at-commit, and view-repo-at-commit links. When on the last
page the list ends with an "End of commit history" marker.

Refactor the author parsing, avatar group, body toggle, and body panel
out of commitHeader into commitAuthors.ts and commitParts.tsx so the
new CommitRow can reuse them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
List unique commit authors scoped to a ref and optional path, sorted by
commit count descending. Backed by git shortlog -sne for a native walk
that emits one line per author rather than per commit, which keeps the
response small even for files with long histories.

The route is exposed in the public API under the Git tag.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The history subheader gets an "All users" dropdown that filters commits
by author. The top 100 authors (by commit count) are fetched via
listCommitAuthors and shown in a Popover + cmdk Command list with a
search input, checkmark on the selected author, and a "View commits for
all users" footer to clear. A "Filter on author <input>" row appears at
the top whenever search is non-empty, acting as an escape valve for
authors outside the top 100.

The filter survives pagination by threading the author query param
through CommitsPagination. Duplicate entries from the same email (git
shortlog groups by full author string, so name-variant spellings split
into multiple rows) are collapsed client-side with the name variant
having the most commits winning as canonical.

Document listCommits's --author and --grep as POSIX BRE regex and move
the literal-escape responsibility onto the caller; CommitsPanel escapes
the selected author via a BRE-safe helper before passing to git.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a date range dropdown next to the author filter using shadcn's
range Calendar in a Popover. URL state is `?since=YYYY-MM-DD&until=YYYY-MM-DD`
so ranges are shareable.

Two clicks are required to form a range even when one is already
selected — the component tracks an in-progress draft locally and
intercepts react-day-picker v9's "adjust" behavior that would otherwise
commit a new range in a single click. Single-day ranges require two
clicks of the same date.

The upper bound is made inclusive by appending end-of-day time before
passing to git log. `since` also gets explicit midnight time to sidestep
git's approxidate parser, which silently mishandles some bare
YYYY-MM-DD forms. Future dates are disabled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mintlify
Copy link
Copy Markdown

mintlify Bot commented Apr 24, 2026

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
sourcebot 🟢 Ready View Preview Apr 24, 2026, 11:44 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@github-actions

This comment has been minimized.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: be6f5f16-b8c8-4e69-ba74-1b335c125570

📥 Commits

Reviewing files that changed from the base of the PR and between aa58cd3 and 76464e4.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • packages/web/src/app/(app)/browse/[...path]/components/commitRow.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitsPanel.tsx
  • packages/web/src/app/(app)/browse/components/bottomPanel.tsx

Walkthrough

Adds a commit history feature to the code browser (per-file/latest commit header, paginated history, author and date filters) and a new public endpoint GET /api/commits/authors, plus supporting UI components, client/server APIs, schemas, OpenAPI docs, and tests.

Changes

Cohort / File(s) Summary
API endpoint & server route
packages/web/src/app/api/(server)/commits/authors/route.ts
New Next.js route implementing GET /api/commits/authors: validates query, calls service, returns JSON with X-Total-Count and RFC-8288 Link header, maps service errors.
Public OpenAPI & docs
packages/web/src/openapi/publicApiDocument.ts, packages/web/src/openapi/publicApiSchemas.ts, docs/api-reference/sourcebot-public.openapi.json, docs/docs.json
Adds OpenAPI entries and schemas for listing commit authors and updates docs navigation to include the new endpoint.
Git service APIs & schemas
packages/web/src/features/git/listCommitAuthorsApi.ts, packages/web/src/features/git/schemas.ts, packages/web/src/features/git/index.ts, packages/web/src/features/git/getPathTypeApi.ts, packages/web/src/features/git/listCommitsApi.ts
New listCommitAuthors implementation (parses git shortlog -sne, paging), new Zod schemas for author listing, POSIX BRE docs for author/query, and new getPathType helper; exports updated.
Client API
packages/web/src/app/api/(client)/client.ts
Adds client helper listCommits to call /api/commits, parse JSON and X-Total-Count, and return typed response or ServiceError.
Browse routing & page plumbing
packages/web/src/app/(app)/browse/hooks/utils.ts, packages/web/src/app/(app)/browse/[...path]/page.tsx, packages/web/src/app/(app)/browse/browseStateProvider.tsx
Adds commits pathType and normalization, extends page props to accept search params (page, author, since, until), and adds activeBottomPanelTab state.
Commit-history UI & panels
packages/web/src/app/(app)/browse/[...path]/components/commitsPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/commitRow.tsx, packages/web/src/app/(app)/browse/[...path]/components/commitsPagination.tsx, packages/web/src/app/(app)/browse/components/historyPanel.tsx, packages/web/src/app/(app)/browse/components/historyRow.tsx, packages/web/src/app/(app)/browse/components/latestCommitInfo.tsx, packages/web/src/app/(app)/browse/components/bottomPanel.tsx
Adds server/client panels and rows for commits/history, grouping by day, copy-SHA action, pagination and infinite-scroll history panel, bottom-panel tabbing (History/Explore) and latest-commit header.
Filters & date UI
packages/web/src/app/(app)/browse/[...path]/components/authorFilter.tsx, packages/web/src/app/(app)/browse/[...path]/components/dateFilter.tsx
Author picker with searchable popover and URL-sync, and a timezone-aware date-range picker using react-day-picker with popover, draft-range handling, and URL-sync.
Commit metadata & UI primitives
packages/web/src/app/(app)/browse/components/commitAuthors.ts, packages/web/src/app/(app)/browse/components/commitParts.tsx, packages/web/src/app/(app)/browse/components/commitParts.tsx
Co-author parsing, deduplication/aggregation, BRE escaping helper, author formatting, avatar group, commit body toggle/display, and action link components.
UI component updates
packages/web/src/components/ui/avatar.tsx, packages/web/src/components/ui/calendar.tsx, packages/web/src/components/ui/tab-switcher.tsx
Refactors Avatar API (size variants, group/badge/count), adds Calendar wrapper for react-day-picker, and allows LowProfileTabsTrigger to accept className.
Tests & package changes
packages/web/src/features/git/listCommitsApi.test.ts, packages/web/package.json, CHANGELOG.md
Adds test ensuring author-escaping forwarded to git, bumps @radix-ui/react-slot, adds react-day-picker dependency, and updates changelog.

Sequence Diagram

sequenceDiagram
    actor User
    participant BrowseUI as Browse Page<br/>(Commits view)
    participant AuthorAPI as /api/commits/authors
    participant CommitsAPI as /api/commits
    participant GitService as Git Service<br/>(listCommitAuthors / listCommits)

    User->>BrowseUI: Open history with repo/ref/path + filters
    BrowseUI->>AuthorAPI: GET ?repo=X&ref=Y&path=Z&page=1&perPage=...
    AuthorAPI->>GitService: listCommitAuthors(repo, ref, path, maxCount, skip)
    GitService-->>AuthorAPI: authors[] + totalCount
    AuthorAPI-->>BrowseUI: 200 + authors + X-Total-Count + Link

    BrowseUI->>CommitsAPI: GET ?repo=X&author=A&since=S&until=U&page=N&perPage=M
    CommitsAPI->>GitService: listCommits(query params)
    GitService-->>CommitsAPI: commits[] + totalCount
    CommitsAPI-->>BrowseUI: 200 + commits + X-Total-Count

    BrowseUI->>BrowseUI: Group by day, render rows, show pagination
    User->>BrowseUI: Select author/date or paginate
    BrowseUI->>BrowseUI: Update URL params, refetch
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

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

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring 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 name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(web): add git history view' accurately and concisely describes the main change in the pull request, which adds a GitHub-style git history experience to the code browser.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check 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 bkellam/git-history-SOU-131

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

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
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 24, 2026

License Audit

⚠️ Status: PASS

Metric Count
Total packages 2064
Resolved (non-standard) 10
Unresolved 0
Strong copyleft 0
Weak copyleft 39

Weak Copyleft Packages (informational)

Package Version License
@img/sharp-libvips-darwin-arm64 1.0.4 LGPL-3.0-or-later
@img/sharp-libvips-darwin-arm64 1.2.4 LGPL-3.0-or-later
@img/sharp-libvips-darwin-x64 1.0.4 LGPL-3.0-or-later
@img/sharp-libvips-darwin-x64 1.2.4 LGPL-3.0-or-later
@img/sharp-libvips-linux-arm 1.0.5 LGPL-3.0-or-later
@img/sharp-libvips-linux-arm 1.2.4 LGPL-3.0-or-later
@img/sharp-libvips-linux-arm64 1.0.4 LGPL-3.0-or-later
@img/sharp-libvips-linux-arm64 1.2.4 LGPL-3.0-or-later
@img/sharp-libvips-linux-ppc64 1.2.4 LGPL-3.0-or-later
@img/sharp-libvips-linux-riscv64 1.2.4 LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x 1.0.4 LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x 1.2.4 LGPL-3.0-or-later
@img/sharp-libvips-linux-x64 1.0.4 LGPL-3.0-or-later
@img/sharp-libvips-linux-x64 1.2.4 LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm64 1.0.4 LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm64 1.2.4 LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x64 1.0.4 LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x64 1.2.4 LGPL-3.0-or-later
@img/sharp-wasm32 0.33.5 Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-wasm32 0.34.5 Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-win32-arm64 0.34.5 Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia32 0.33.5 Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia32 0.34.5 Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x64 0.33.5 Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x64 0.34.5 Apache-2.0 AND LGPL-3.0-or-later
axe-core 4.10.3 MPL-2.0
dompurify 3.4.0 (MPL-2.0 OR Apache-2.0)
lightningcss 1.32.0 MPL-2.0
lightningcss-android-arm64 1.32.0 MPL-2.0
lightningcss-darwin-arm64 1.32.0 MPL-2.0
lightningcss-darwin-x64 1.32.0 MPL-2.0
lightningcss-freebsd-x64 1.32.0 MPL-2.0
lightningcss-linux-arm-gnueabihf 1.32.0 MPL-2.0
lightningcss-linux-arm64-gnu 1.32.0 MPL-2.0
lightningcss-linux-arm64-musl 1.32.0 MPL-2.0
lightningcss-linux-x64-gnu 1.32.0 MPL-2.0
lightningcss-linux-x64-musl 1.32.0 MPL-2.0
lightningcss-win32-arm64-msvc 1.32.0 MPL-2.0
lightningcss-win32-x64-msvc 1.32.0 MPL-2.0
Resolved Packages (10)
Package Version Original Resolved Source
@react-grab/cli 0.1.23 UNKNOWN MIT GitHub repo aidenybai/react-grab (monorepo containing packages/cli) — MIT License
@react-grab/cli 0.1.29 UNKNOWN MIT GitHub repo aidenybai/react-grab (monorepo containing packages/cli) — MIT License
@react-grab/mcp 0.1.29 UNKNOWN MIT GitHub repo aidenybai/react-grab (monorepo containing packages/mcp) — MIT License
codemirror-lang-elixir 4.0.0 UNKNOWN Apache-2.0 GitHub repo livebook-dev/codemirror-lang-elixir — Apache License Version 2.0
element-source 0.0.3 UNKNOWN MIT GitHub repo aidenybai/element-source — MIT License
lezer-elixir 1.1.2 UNKNOWN Apache-2.0 GitHub repo livebook-dev/lezer-elixir — Apache License Version 2.0
map-stream 0.1.0 UNKNOWN MIT GitHub repo dominictarr/map-stream — MIT License (LICENCE file)
memorystream 0.3.1 UNKNOWN MIT npm registry — licenses array object {"type": "MIT", "url": "http://github.com/JSBizon/node-memorystream/raw/master/LICENSE"}
posthog-js 1.369.0 SEE LICENSE IN LICENSE Apache-2.0 GitHub repo PostHog/posthog-js — Apache License Version 2.0
valid-url 1.0.9 UNKNOWN MIT GitHub repo ogt/valid-url — MIT License

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (7)
packages/web/src/openapi/publicApiSchemas.ts (1)

1-58: LGTM: OpenAPI schema exports for commit authors are consistent.

The added public schemas are well-structured:

  • query schema (for params),
  • author object schema,
  • response as an array of authors.

Please just make sure the route handler at GET /api/commits/authors wires these exact schemas when generating/validating OpenAPI responses/requests (so docs and runtime stay aligned).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/openapi/publicApiSchemas.ts` around lines 1 - 58, Ensure the
GET /api/commits/authors route handler uses the exported OpenAPI schemas: wire
publicListCommitAuthorsQuerySchema for request query validation,
publicCommitAuthorSchema for individual author objects, and
publicListCommitAuthorsResponseSchema for the response body/schema in the
OpenAPI generation and any runtime validation middleware (look for the route
handler referencing GET /api/commits/authors and update the OpenAPI/validator
config there to reference these three symbols).
packages/web/src/components/ui/avatar.tsx (2)

8-26: Potential ref regression: wrapper drops/changes ref behavior.

Avatar is now a plain function wrapper around AvatarPrimitive.Root with no forwardRef. If any call sites rely on passing ref to <Avatar /> (common for focus/scroll measurement), that ref may no longer work as before.

Please verify (1) whether <Avatar /> is used with ref={...} anywhere, and (2) whether React 19 in this repo actually treats ref as a normal prop for function components with the current TS setup.

If you don’t intend to support refs, consider changing the prop type to React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> to prevent accidental ref usage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/components/ui/avatar.tsx` around lines 8 - 26, The Avatar
wrapper drops ref forwarding and may break callers relying on ref; update Avatar
to either forward refs to AvatarPrimitive.Root using React.forwardRef
(preserving ref behavior for focus/measurement) or change the prop type to
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> to explicitly
disallow refs; locate the Avatar function and modify its signature and export to
use React.forwardRef (forward the ref into AvatarPrimitive.Root) or adjust the
props type as suggested, and ensure the data-size/data-slot/className spread
remain unchanged when applying the fix.

57-70: Badge sizing depends on group data on the ancestor.

AvatarBadge uses group-data-[size=...] /avatar:* selectors and assumes it’s rendered under an element with data-size and class="group/avatar". This is likely correct if call sites nest <AvatarBadge /> inside <Avatar />, but it’s worth ensuring the intended structure is documented/consistent.

If any call sites render AvatarBadge outside Avatar, badge sizing could break silently.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/components/ui/avatar.tsx` around lines 57 - 70, AvatarBadge
relies on ancestor group data selectors (group-data-[size=...]/avatar and
data-size on an element with class "group/avatar") so badge sizing will break if
it's rendered outside Avatar; update AvatarBadge to either accept an explicit
size prop (e.g., size: "sm" | "default" | "lg") and apply the corresponding size
classes when provided, or add a runtime check in AvatarBadge (useEffect) that
warns when no ancestor with data-size and class "group/avatar" is found so
callers know to nest it inside Avatar; also add a short doc comment on
AvatarBadge and update Avatar to pass size down if it renders AvatarBadge.
packages/web/src/features/git/listCommitsApi.ts (1)

78-107: Check correctness of --author/--grep option ordering and escaping assumptions.

The implementation correctly documents that --author and --grep are interpreted as git regex (POSIX BRE + GNU extensions) and that callers must escape metacharacters for literal matching.

One concern to double-check: you currently build sharedOptions into Object.entries(...), and while JS preserves insertion order, the presence/absence of author vs query can alter the relative order of --author/--grep vs --regexp-ignore-case. If ordering ever matters for parsing, it could be brittle. Tests cover some ordering, but not all combinations.

At minimum, ensure there’s test coverage for “author only”, “query only”, and “author+query+since/until” with exact argv order expectations where it matters.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/features/git/listCommitsApi.ts` around lines 78 - 107, The
ordering of --author/--grep vs their --regexp-ignore-case flag is brittle
because sharedOptions relies on Object.entries insertion order; change the arg
construction in listCommitsApi (the sharedOptions/logArgs logic) to explicitly
append flags in a stable sequence (e.g., if author present push '--author=...'
then push '--regexp-ignore-case', likewise for grep) rather than iterating
Object.entries, and add unit tests for the three scenarios ("author only",
"query only", and "author+query+since/until") asserting the exact logArgs/argv
order produced by the function so regressions are caught.
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel.tsx (1)

16-30: Performance risk: extra git log call per code preview render.

codePreviewPanel now issues an additional listCommits(..., maxCount: 1) request for the current path and revisionName. Depending on how many CodePreviewPanel instances render per page, this could become a significant load on git operations.

If this component is rendered frequently (e.g., multiple open files, client transitions, pagination), consider:

  • caching the “latest commit for this (repo, path, ref)” at the API or request layer, or
  • computing it in a higher-level data fetch and passing it down.

Not necessarily blocker, but worth watching.

🤖 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]/components/codePreviewPanel.tsx
around lines 16 - 30, CodePreviewPanel currently issues an additional
listCommits(...) call per render (using path and revisionName) which can be
heavy; to fix, remove or memoize that call by either (1) moving the
latest-commit lookup out of CodePreviewPanel into a higher-level loader or
parent component and pass the commit data down, or (2) add a short-lived cache
in the request layer/shared fetch helper so repeated listCommits(repo, path,
ref, maxCount:1) requests reuse results; update CodePreviewPanel to use the
already-fetched commit data or the cached fetch together with the existing
getFileSource and getRepoInfoByName calls to avoid per-instance git log calls.
packages/web/src/app/(app)/browse/[...path]/components/commitsPagination.tsx (1)

43-59: Prefer <button disabled> over <span aria-disabled="true"> for disabled prev/next.

A native disabled <button> is keyboard-skipped automatically and is consistently announced as disabled by screen readers; the current <span aria-disabled="true"> still receives focus traversal in some flows and relies entirely on visual styling. Minor a11y polish.

♻️ Suggested refactor
-            ) : (
-                <span className={cn(disabledClass)} aria-disabled="true">
-                    <ChevronLeft className="h-4 w-4" />
-                    Previous
-                </span>
-            )}
+            ) : (
+                <button type="button" disabled className={cn(disabledClass)}>
+                    <ChevronLeft className="h-4 w-4" />
+                    Previous
+                </button>
+            )}
@@
-            ) : (
-                <span className={cn(disabledClass)} aria-disabled="true">
-                    Next
-                    <ChevronRight className="h-4 w-4" />
-                </span>
-            )}
+            ) : (
+                <button type="button" disabled className={cn(disabledClass)}>
+                    Next
+                    <ChevronRight className="h-4 w-4" />
+                </button>
+            )}
🤖 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]/components/commitsPagination.tsx
around lines 43 - 59, Replace the non-interactive disabled spans used for
Prev/Next with native disabled buttons so they are skipped by keyboard and
announced by assistive tech: where the code currently renders <span
className={cn(disabledClass)} aria-disabled="true"> for the previous and next
controls, change those branches to render <button type="button" disabled
className={cn(disabledClass)}> with the same children (ChevronLeft/ChevronRight
and text). Keep the enabled branches using Link (Link href={buildHref(page + 1,
extraParams)} className={linkClass}) and preserve linkClass, disabledClass,
hasNext/hasPrev (and page/extraParams) usage. Ensure no href is added to the
disabled button and retain the icon order/structure.
packages/web/src/app/(app)/browse/[...path]/page.tsx (1)

78-99: Consider whether to add searchParams validation, but note validation already exists at the API boundary.

In Next.js 16 App Router, searchParams values are string | string[] | undefined. The current type narrowing to string doesn't account for repeated query parameters (e.g., ?page=1&page=2 arrives as an array), which would be silently coerced by parseInt or template strings. However, similar page components in the codebase (signup, redeem, onboard) follow the same direct passthrough pattern. Since CommitsPanel ultimately calls the API route at packages/web/src/app/api/(server)/commits/route.ts, which validates via listCommitsQueryParamsSchema, the validation already occurs at the appropriate boundary. Adding Zod validation here would be stricter but would diverge from the established pattern where validation is centralized at API routes.

🤖 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 78 - 99,
The searchParams values in BrowsePage (BrowsePageProps -> searchParams) can be
string|string[]|undefined; update the coercion logic so you handle string[]
cases before parsing: for each param (searchParams.page, .author, .since,
.until) normalize to a single string by taking the first element when
Array.isArray(...) (or otherwise converting to string) and then parse page with
parseInt and assign author/since/until as undefined when empty; keep validation
at the API boundary but ensure BrowsePage doesn't silently coerce arrays (refer
to BrowsePage, searchParams, page, author, since, until, and
getBrowseParamsFromPathParam).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CHANGELOG.md`:
- Around line 8-12: The Unreleased entry under "## [Unreleased]" -> "### Added"
is not a single sentence; edit the single bullet (the line starting "Added a
GitHub-style git history view to the code browser: ...") so it becomes one
sentence (for example by joining the clause "Also exposes GET
/api/commits/authors in the public API." to the previous sentence with
appropriate punctuation or conjunction), ensuring the entire bullet is a single
grammatically correct sentence.

In `@packages/web/src/app/`(app)/browse/[...path]/components/commitsPanel.tsx:
- Around line 37-56: The author list is being fetched without date bounds while
commits are filtered by since/until; update the listCommitAuthors call (the call
named listCommitAuthors) to include the same since/until parameters used for
listCommits (sinceForGit and untilForGit) so the "top authors" reflect the
current date window for the given repo/path/ref (path, revisionName), or
explicitly document/guard the current behavior if you intentionally want authors
across the entire ref.

In `@packages/web/src/app/`(app)/browse/[...path]/components/dateFilter.tsx:
- Around line 22-29: parseLocalDate currently accepts structurally-valid but
semantically-invalid dates (e.g., "2024-13-45") which new Date(...) silently
rolls into a different date; to fix it, after extracting year/month/day in
parseLocalDate validate by constructing the Date and then verifying
date.getFullYear() === year, date.getMonth() === month - 1 and date.getDate()
=== day, returning undefined if any check fails so only true calendar-valid
dates are accepted.
- Around line 66-91: The popover's month state ("month"/setMonth) is only
initialized from fromDate and never updated when fromDate changes externally, so
the calendar can open on the wrong month; update month whenever the popover is
opened or when fromDate changes by adding an effect that runs on [isOpen,
fromDate] (or separate effects) and calls setMonth(fromDate or
selectedRange.from) to resync the visible month with the URL-derived
fromDate/draftRange; reference the existing month/setMonth, fromDate, isOpen,
selectedRange and draftRange to locate where to add this sync.

In `@packages/web/src/features/git/listCommitAuthorsApi.ts`:
- Around line 85-107: The catch block in listCommitAuthorsApi currently maps git
ref errors to unexpectedError or throws, causing 500s for client mistakes;
update it to import and return the existing service errors (invalidGitRef and
unresolvedGitRef) instead of unexpectedError/throwing for ref-related messages:
detect messages like 'ambiguous argument', 'unknown revision', 'bad revision',
'invalid object name' (and any other git ref resolution strings) and return
invalidGitRef(ref) or unresolvedGitRef(ref) as appropriate; keep the existing
handling for 'not a git repository' but ensure other non-ref errors still
surface as unexpectedError, and remove the final throw so the function
consistently returns a service error type.
- Around line 56-84: The code parses shortlog into `all` then slices it into
`authors` and returns `totalCount = all.length`, which lets name-variants for
the same email skew paging; instead, dedupe authors by lowercased email on the
backend before computing totals and slicing: build a map keyed by
email.toLowerCase() (merge commit counts and choose a canonical name—e.g.,
first-seen), produce a deduped array sorted by commitCount desc, set
`totalCount` to deduped.length, then slice that array with `skip`/`maxCount` and
return those results; update the logic around `all`, `totalCount`, and `authors`
in the function that calls `git.raw` (the code block using `lineRegex` and
pushing into `all`) so the returned page matches UI dedupe behavior.

---

Nitpick comments:
In `@packages/web/src/app/`(app)/browse/[...path]/components/codePreviewPanel.tsx:
- Around line 16-30: CodePreviewPanel currently issues an additional
listCommits(...) call per render (using path and revisionName) which can be
heavy; to fix, remove or memoize that call by either (1) moving the
latest-commit lookup out of CodePreviewPanel into a higher-level loader or
parent component and pass the commit data down, or (2) add a short-lived cache
in the request layer/shared fetch helper so repeated listCommits(repo, path,
ref, maxCount:1) requests reuse results; update CodePreviewPanel to use the
already-fetched commit data or the cached fetch together with the existing
getFileSource and getRepoInfoByName calls to avoid per-instance git log calls.

In
`@packages/web/src/app/`(app)/browse/[...path]/components/commitsPagination.tsx:
- Around line 43-59: Replace the non-interactive disabled spans used for
Prev/Next with native disabled buttons so they are skipped by keyboard and
announced by assistive tech: where the code currently renders <span
className={cn(disabledClass)} aria-disabled="true"> for the previous and next
controls, change those branches to render <button type="button" disabled
className={cn(disabledClass)}> with the same children (ChevronLeft/ChevronRight
and text). Keep the enabled branches using Link (Link href={buildHref(page + 1,
extraParams)} className={linkClass}) and preserve linkClass, disabledClass,
hasNext/hasPrev (and page/extraParams) usage. Ensure no href is added to the
disabled button and retain the icon order/structure.

In `@packages/web/src/app/`(app)/browse/[...path]/page.tsx:
- Around line 78-99: The searchParams values in BrowsePage (BrowsePageProps ->
searchParams) can be string|string[]|undefined; update the coercion logic so you
handle string[] cases before parsing: for each param (searchParams.page,
.author, .since, .until) normalize to a single string by taking the first
element when Array.isArray(...) (or otherwise converting to string) and then
parse page with parseInt and assign author/since/until as undefined when empty;
keep validation at the API boundary but ensure BrowsePage doesn't silently
coerce arrays (refer to BrowsePage, searchParams, page, author, since, until,
and getBrowseParamsFromPathParam).

In `@packages/web/src/components/ui/avatar.tsx`:
- Around line 8-26: The Avatar wrapper drops ref forwarding and may break
callers relying on ref; update Avatar to either forward refs to
AvatarPrimitive.Root using React.forwardRef (preserving ref behavior for
focus/measurement) or change the prop type to
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> to explicitly
disallow refs; locate the Avatar function and modify its signature and export to
use React.forwardRef (forward the ref into AvatarPrimitive.Root) or adjust the
props type as suggested, and ensure the data-size/data-slot/className spread
remain unchanged when applying the fix.
- Around line 57-70: AvatarBadge relies on ancestor group data selectors
(group-data-[size=...]/avatar and data-size on an element with class
"group/avatar") so badge sizing will break if it's rendered outside Avatar;
update AvatarBadge to either accept an explicit size prop (e.g., size: "sm" |
"default" | "lg") and apply the corresponding size classes when provided, or add
a runtime check in AvatarBadge (useEffect) that warns when no ancestor with
data-size and class "group/avatar" is found so callers know to nest it inside
Avatar; also add a short doc comment on AvatarBadge and update Avatar to pass
size down if it renders AvatarBadge.

In `@packages/web/src/features/git/listCommitsApi.ts`:
- Around line 78-107: The ordering of --author/--grep vs their
--regexp-ignore-case flag is brittle because sharedOptions relies on
Object.entries insertion order; change the arg construction in listCommitsApi
(the sharedOptions/logArgs logic) to explicitly append flags in a stable
sequence (e.g., if author present push '--author=...' then push
'--regexp-ignore-case', likewise for grep) rather than iterating Object.entries,
and add unit tests for the three scenarios ("author only", "query only", and
"author+query+since/until") asserting the exact logArgs/argv order produced by
the function so regressions are caught.

In `@packages/web/src/openapi/publicApiSchemas.ts`:
- Around line 1-58: Ensure the GET /api/commits/authors route handler uses the
exported OpenAPI schemas: wire publicListCommitAuthorsQuerySchema for request
query validation, publicCommitAuthorSchema for individual author objects, and
publicListCommitAuthorsResponseSchema for the response body/schema in the
OpenAPI generation and any runtime validation middleware (look for the route
handler referencing GET /api/commits/authors and update the OpenAPI/validator
config there to reference these three symbols).
🪄 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: cde158a4-5333-48c3-89d9-d5b38c3a62c9

📥 Commits

Reviewing files that changed from the base of the PR and between 3ab245a and eafa01f.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (25)
  • CHANGELOG.md
  • docs/api-reference/sourcebot-public.openapi.json
  • docs/docs.json
  • packages/web/package.json
  • packages/web/src/app/(app)/browse/[...path]/components/authorFilter.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitAuthors.ts
  • packages/web/src/app/(app)/browse/[...path]/components/commitHeader.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitParts.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitRow.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitsPagination.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitsPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/dateFilter.tsx
  • packages/web/src/app/(app)/browse/[...path]/page.tsx
  • packages/web/src/app/(app)/browse/hooks/utils.ts
  • packages/web/src/app/api/(server)/commits/authors/route.ts
  • packages/web/src/components/ui/avatar.tsx
  • packages/web/src/components/ui/calendar.tsx
  • packages/web/src/features/git/index.ts
  • packages/web/src/features/git/listCommitAuthorsApi.ts
  • packages/web/src/features/git/listCommitsApi.test.ts
  • packages/web/src/features/git/listCommitsApi.ts
  • packages/web/src/features/git/schemas.ts
  • packages/web/src/openapi/publicApiDocument.ts
  • packages/web/src/openapi/publicApiSchemas.ts

Comment thread CHANGELOG.md
Comment thread packages/web/src/app/(app)/browse/[...path]/components/commitsPanel.tsx Outdated
Comment thread packages/web/src/features/git/listCommitAuthorsApi.ts
Comment thread packages/web/src/features/git/listCommitAuthorsApi.ts
Adds a new History tab to the bottom panel alongside Explore. The tab is
toggled via shift+mod+h with the existing collapse-on-same/switch-on-other
semantics, and the active tab is indicated with an underline using
LowProfileTabsTrigger (now accepts an optional className).

The history panel itself is a client-side infinite-scroll list (react-query
useInfiniteQuery + IntersectionObserver) that hits a new client-side
listCommits wrapper. Each row reuses AuthorsAvatarGroup, the co-author
parsing, and the new shared CommitActionLink primitive for the
view-code-at-commit / view-repo-at-commit actions. CommitRow was refactored
to use the same primitive.

The previous CommitHeader rendered above each open file is removed.
A compact summary of the latest commit now appears in the bottom panel
header (right side) whenever the panel is collapsed, so the file/folder's
last commit stays glanceable without consuming a full row above the code.
When the panel is expanded the right side hosts the View-full-history
and Hide buttons.

Shared commit utilities (commitAuthors.ts, commitParts.tsx) moved up
from browse/[...path]/components/ to browse/components/ so both the
existing commits view and the new bottom-panel components import them
without crossing route boundaries.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (3)
packages/web/src/app/(app)/browse/components/bottomPanel.tsx (1)

47-52: Minor: memoize fullHistoryHref.

Recomputed on every render; cheap but easy to stabilize:

♻️ Proposed tweak
-    const { repoName, revisionName, path } = useBrowseParams();
-    const fullHistoryHref = getBrowsePath({
-        repoName,
-        revisionName,
-        path,
-        pathType: 'commits',
-    });
+    const { repoName, revisionName, path } = useBrowseParams();
+    const fullHistoryHref = useMemo(
+        () => getBrowsePath({ repoName, revisionName, path, pathType: 'commits' }),
+        [repoName, revisionName, path],
+    );

(Add useMemo to the existing react import.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/app/`(app)/browse/components/bottomPanel.tsx around lines 47
- 52, fullHistoryHref is recomputed on every render; wrap the call to
getBrowsePath in a useMemo to stabilize the value. Import useMemo from React
(add to the existing react import) and replace the direct assignment of
fullHistoryHref with a useMemo(() => getBrowsePath({ repoName, revisionName,
path, pathType: 'commits' }), [repoName, revisionName, path]); so the memo
depends on repoName, revisionName, and path and uses the existing getBrowsePath
reference in bottomPanel.tsx.
packages/web/src/app/(app)/browse/components/commitAuthors.ts (1)

5-13: Co-author trailers with leading whitespace will be missed.

Some commit conventions (and tools) emit indented trailers, e.g. when a commit body is wrapped/quoted. The regex anchors ^ directly to co-authored-by: and won't match Co-authored-by: …. If you want to be lenient, allow optional leading whitespace:

♻️ Proposed tweak
-    const regex = /^co-authored-by:\s*(.+?)\s*<(.+?)>\s*$/gim;
+    const regex = /^[ \t]*co-authored-by:\s*(.+?)\s*<(.+?)>\s*$/gim;

Not a blocker — standard git commit -s/--trailer-emitted trailers will already match.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/app/`(app)/browse/components/commitAuthors.ts around lines 5
- 13, The parseCoAuthors function's regex currently anchors to the line start
and misses indented trailers; update the regex used in parseCoAuthors to allow
optional leading whitespace (e.g. prefix with \s* after ^) so lines like " 
Co-authored-by: Name <email>" are matched; keep the existing case-insensitive
and global flags and ensure the rest of the capture groups (name/email) remain
unchanged so coAuthors.push({ name: match[1].trim(), email: match[2].trim() })
still works.
packages/web/src/app/(app)/browse/components/historyPanel.tsx (1)

51-66: Observer is reattached on every isFetchingNextPage flip.

Including isFetchingNextPage in the effect deps means the observer is destroyed and recreated each time a page starts/finishes fetching. It works, but you can drop it from the deps and read the latest value via a ref (or just data?.pages.length-style guard inside the callback) to avoid the churn. Optional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/app/`(app)/browse/components/historyPanel.tsx around lines
51 - 66, The effect re-creates the IntersectionObserver whenever
isFetchingNextPage flips; remove isFetchingNextPage from the dependency array
and read its latest value via a ref instead. Create a ref like
isFetchingNextPageRef, update isFetchingNextPageRef.current whenever
isFetchingNextPage changes, then in the IntersectionObserver callback check
entries[0].isIntersecting && !isFetchingNextPageRef.current before calling
fetchNextPage; keep hasNextPage and fetchNextPage (and sentinelRef) in the
effect deps so the observer is only recreated when those truly change.
🤖 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/commitRow.tsx:
- Around line 52-56: The onCopySha handler calls
navigator.clipboard.writeText(commit.hash) but doesn't await it, so the success
toast is shown even if the promise rejects; update the onCopySha function to
await the writeText Promise inside a try/catch (or use .then/.catch) so you only
call toast({ description: "✅ Copied commit SHA to clipboard" }) on success and
show an error toast on failure, and also guard for missing API (typeof
navigator.clipboard === "undefined") to surface a sensible error message; touch
the onCopySha function, navigator.clipboard.writeText, commit.hash and toast
references when implementing this.

In `@packages/web/src/app/`(app)/browse/[...path]/components/commitsPanel.tsx:
- Around line 70-128: The UI shows only "End of commit history" when filters
return zero commits; update the render logic in commitsPanel.tsx to detect
commits.length === 0 (or totalCount === 0) and render an explicit empty state
above the current End message instead of mapping groups: show a friendly message
(e.g., "No commits match these filters"), optional actionable guidance to clear
filters, and a control to reset AuthorFilter/DateFilter (or a callback prop) so
users can clear filter state; keep existing groups Map/isLastPage logic and
CommitRow rendering for the non-empty path.
- Around line 30-35: The sinceForGit and untilForGit timestamps are built
without timezone info so git interprets them in server-local time causing
day-shift bugs; update the code that constructs sinceForGit/untilForGit (the
values derived from parseLocalDate) to produce timezone-qualified ISO timestamps
(either convert the parsed local date to UTC and append 'Z' or include the local
offset) before sending to git so --since/--until are interpreted consistently
across client and server; ensure the transformation happens where sinceForGit
and untilForGit are defined and used.

In `@packages/web/src/app/`(app)/browse/components/bottomPanel.tsx:
- Around line 132-138: The ExternalLink icon next to the in-app route
fullHistoryHref is misleading; update the UI in bottomPanel.tsx so the button
reflects internal navigation by replacing ExternalLink with a semantic internal
icon (e.g., History or ListOrdered) and update the corresponding import, or if
you intended to open the link in a new tab, keep ExternalLink but add
target="_blank" rel="noopener" to the Link; ensure the change touches the Button
asChild / Link element and the import statement for the icon (ExternalLink →
History/ListOrdered) so the icon matches the chosen behavior.

In `@packages/web/src/app/`(app)/browse/components/historyPanel.tsx:
- Around line 91-98: The HistoryPanel is not passing pathType into each
HistoryRow, so per-row file actions can't know if the current path is a blob or
tree; update the map that renders HistoryRow (where allCommits is iterated) to
include the pathType prop (alongside repoName and path) so HistoryRow can use it
to build the correct “View code at this commit” link; locate the rendering block
that calls <HistoryRow key={commit.hash} commit={commit} repoName={repoName}
path={path} /> and add pathType={pathType} (getting pathType from the same
useBrowseParams context you already use elsewhere).

In `@packages/web/src/app/`(app)/browse/components/historyRow.tsx:
- Around line 20-37: The "View code at this commit" action can generate blob
URLs for directory paths because hasFilePath only checks path !== '' && path !==
'/' and viewCodeHref always uses pathType: 'blob'; update HistoryRow so it only
shows the file action when the row's pathType is 'blob' (either by threading
pathType from useBrowseParams through HistoryPanel -> HistoryRow, or by
computing a per-row pathType before building viewCodeHref), and ensure
viewCodeHref uses that pathType (instead of always 'blob'); keep viewRepoHref as
tree. Adjust rendering to hide the FileCode icon/label when pathType !== 'blob'
to avoid invalid /-/blob/... URLs.

In `@packages/web/src/app/`(app)/browse/components/latestCommitInfo.tsx:
- Around line 15-39: The component currently hides the header when the query is
loading or errors because it only checks commit; update the useQuery call in
latestCommitInfo.tsx to destructure isLoading and isError (e.g., const { data:
commit, isLoading, isError } = useQuery(...)) and then render appropriate
fallbacks instead of returning null: show a small muted placeholder like
"Loading latest commit…" when isLoading, and "Couldn't load latest commit"
(styled muted) when isError; keep returning null only when commit === null (no
commits). Ensure any existing logic that computes authors via
getCommitAuthors(commit) remains guarded by commit and the useMemo dependency.

---

Nitpick comments:
In `@packages/web/src/app/`(app)/browse/components/bottomPanel.tsx:
- Around line 47-52: fullHistoryHref is recomputed on every render; wrap the
call to getBrowsePath in a useMemo to stabilize the value. Import useMemo from
React (add to the existing react import) and replace the direct assignment of
fullHistoryHref with a useMemo(() => getBrowsePath({ repoName, revisionName,
path, pathType: 'commits' }), [repoName, revisionName, path]); so the memo
depends on repoName, revisionName, and path and uses the existing getBrowsePath
reference in bottomPanel.tsx.

In `@packages/web/src/app/`(app)/browse/components/commitAuthors.ts:
- Around line 5-13: The parseCoAuthors function's regex currently anchors to the
line start and misses indented trailers; update the regex used in parseCoAuthors
to allow optional leading whitespace (e.g. prefix with \s* after ^) so lines
like "  Co-authored-by: Name <email>" are matched; keep the existing
case-insensitive and global flags and ensure the rest of the capture groups
(name/email) remain unchanged so coAuthors.push({ name: match[1].trim(), email:
match[2].trim() }) still works.

In `@packages/web/src/app/`(app)/browse/components/historyPanel.tsx:
- Around line 51-66: The effect re-creates the IntersectionObserver whenever
isFetchingNextPage flips; remove isFetchingNextPage from the dependency array
and read its latest value via a ref instead. Create a ref like
isFetchingNextPageRef, update isFetchingNextPageRef.current whenever
isFetchingNextPage changes, then in the IntersectionObserver callback check
entries[0].isIntersecting && !isFetchingNextPageRef.current before calling
fetchNextPage; keep hasNextPage and fetchNextPage (and sentinelRef) in the
effect deps so the observer is only recreated when those truly change.
🪄 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: 1eceb3fc-ea9a-4bda-904a-e6f0bae59728

📥 Commits

Reviewing files that changed from the base of the PR and between eafa01f and f40c691.

📒 Files selected for processing (12)
  • packages/web/src/app/(app)/browse/[...path]/components/commitRow.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitsPanel.tsx
  • packages/web/src/app/(app)/browse/browseStateProvider.tsx
  • packages/web/src/app/(app)/browse/components/bottomPanel.tsx
  • packages/web/src/app/(app)/browse/components/commitAuthors.ts
  • packages/web/src/app/(app)/browse/components/commitParts.tsx
  • packages/web/src/app/(app)/browse/components/historyPanel.tsx
  • packages/web/src/app/(app)/browse/components/historyRow.tsx
  • packages/web/src/app/(app)/browse/components/latestCommitInfo.tsx
  • packages/web/src/app/api/(client)/client.ts
  • packages/web/src/components/ui/tab-switcher.tsx
  • packages/web/src/features/git/listCommitsApi.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/web/src/app/(app)/browse/browseStateProvider.tsx
  • packages/web/src/features/git/listCommitsApi.ts

Comment thread packages/web/src/app/(app)/browse/[...path]/components/commitRow.tsx Outdated
Comment thread packages/web/src/app/(app)/browse/components/bottomPanel.tsx
Comment thread packages/web/src/app/(app)/browse/components/historyPanel.tsx
Comment thread packages/web/src/app/(app)/browse/components/historyRow.tsx Outdated
Comment thread packages/web/src/app/(app)/browse/components/latestCommitInfo.tsx
…dling

The commits view URL pattern (/-/commits/<path>) doesn't carry whether
the path is a file or a folder, so PathHeader was rendering folder
paths with the file-icon last-segment treatment. Add a small
getPathType helper backed by `git cat-file -t <ref>:<path>` and use it
to pick the correct PathHeader behaviour. CommitRow and HistoryRow now
gate the "view code at this commit" action on the same blob check so
the file-only link doesn't appear on folder rows.

Also fix a related path-handling bug: PathHeader's repo-name link
called getBrowsePath with `path: '/'`, which encoded as `%2F` and
parsed back as `path: '/'` — that leaked through as `git log -- /`,
which git correctly rejects with "outside repository". Strip leading
slashes both when generating and when parsing browse URLs so any path
that comes through as a literal `/` resolves to the repo root.

When clicking "View full history" the bottom panel now collapses
on the same click, so the new full-view page renders without the
panel still hovering on top of it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/web/src/features/git/getPathTypeApi.ts (1)

75-82: Preserve original error via cause when re-throwing.

The wrapping Error discards the original stack trace, which makes diagnosing unexpected simple-git failures harder in production logs. Pass the original error through cause.

♻️ Proposed refactor
-            if (error instanceof Error) {
-                throw new Error(
-                    `Failed to resolve path type for ${repoName}:${path}: ${error.message}`,
-                );
-            }
-            throw new Error(
-                `Failed to resolve path type for ${repoName}:${path}: ${errorMessage}`,
-            );
+            throw new Error(
+                `Failed to resolve path type for ${repoName}:${path}: ${errorMessage}`,
+                error instanceof Error ? { cause: error } : undefined,
+            );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/features/git/getPathTypeApi.ts` around lines 75 - 82, The
thrown wrapper Errors in the path resolution logic lose the original error
stack; update both throw sites in getPathTypeApi (the branch checking error
instanceof Error and the fallback throwing with errorMessage) to include the
original error as the cause (use the Error constructor's { cause: ... } option),
so the original exception/stack is preserved when re-throwing from the function
that resolves path type (the two throw new Error(...) lines).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/web/src/features/git/getPathTypeApi.ts`:
- Around line 75-82: The thrown wrapper Errors in the path resolution logic lose
the original error stack; update both throw sites in getPathTypeApi (the branch
checking error instanceof Error and the fallback throwing with errorMessage) to
include the original error as the cause (use the Error constructor's { cause:
... } option), so the original exception/stack is preserved when re-throwing
from the function that resolves path type (the two throw new Error(...) lines).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cf80b38b-52f4-4735-a2ec-50009ae93c1c

📥 Commits

Reviewing files that changed from the base of the PR and between f40c691 and aa58cd3.

📒 Files selected for processing (8)
  • packages/web/src/app/(app)/browse/[...path]/components/commitRow.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitsPanel.tsx
  • packages/web/src/app/(app)/browse/components/bottomPanel.tsx
  • packages/web/src/app/(app)/browse/components/historyPanel.tsx
  • packages/web/src/app/(app)/browse/components/historyRow.tsx
  • packages/web/src/app/(app)/browse/hooks/utils.ts
  • packages/web/src/features/git/getPathTypeApi.ts
  • packages/web/src/features/git/index.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/web/src/app/(app)/browse/components/historyRow.tsx
  • packages/web/src/app/(app)/browse/components/bottomPanel.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/web/src/features/git/index.ts
  • packages/web/src/app/(app)/browse/components/historyPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitsPanel.tsx

@brendan-kellam brendan-kellam merged commit 105ddf4 into main Apr 25, 2026
7 of 9 checks passed
@brendan-kellam brendan-kellam deleted the bkellam/git-history-SOU-131 branch April 25, 2026 05:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant