feat(web): fetch /browse data client-side and disallow crawlers via robots.txt - #1426

Merged
brendan-kellam merged 5 commits into
mainfrom
brendan/browse-csr
Jul 8, 2026
Merged

feat(web): fetch /browse data client-side and disallow crawlers via robots.txt#1426
brendan-kellam merged 5 commits into
mainfrom
brendan/browse-csr

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Problem

app.sourcebot.dev was repeatedly killed by its liveness probe (6 restarts in 24h). Investigation traced the cause to /browse pages: blob/tree/commit panels fetched git data (file sources, folder contents, full commit diffs) in server components and passed it as props across the client-component boundary. React flight-encodes those props into the document on the main thread — a single large commit page measured 38MB of RSC payload (~8s of encoding), and a binary blob produced a 62MB document. Crawler traffic (GPTBot, Applebot, meta-externalagent, AhrefsBot — ~97% of all server processing time) walked the unbounded @<sha> URL space, stacking these renders until the event loop starved and health probes timed out.

Changes

  1. robots.txt (app/robots.ts): disallow all crawling, with an allowlist for link-preview fetchers (Slackbot, Twitterbot, LinkedInBot, Discordbot, facebookexternalhit, TelegramBot, WhatsApp) so shared links — e.g. chat pages — keep their OpenGraph unfurls. Applebot/GPTBot/meta-externalagent are deliberately not allowlisted (search/AI-training crawlers, not preview fetchers).

  2. Client-side data fetching for /browse panels: CodePreviewPanel, TreePreviewPanel, FullCommitDiffPanel, and FocusedCommitDiffPanel now render a shell and fetch via JSON API routes with react-query (per repo convention). Server components only fetch repoInfo for the path header.

    • New /api/folder_contents route (internal, not registered in the public API) wrapping the existing getFolderContents.
    • New client fetchers: getCommit, getDiff, getFolderContents, getFileBlame.
    • Shared useCommitDiff hook preserving the initial-commit empty-tree fallback.
    • CommitsPanel left server-side intentionally (bounded at 35 commits/page).

Documents drop to the ~70KB app shell regardless of content size, JSON serialization replaces flight encoding (~50-100x less main-thread time for large payloads), and JS-less crawlers never trigger data fetches at all.

Verification

Exercised in an isolated dev environment against an indexed repo: blob, tree, commit, blame-mode, and focused-diff pages all render correctly via client fetching; blob documents contain zero file content and commit documents zero hunk payloads (previously embedded); all API routes return 200; robots.txt serves the intended rules. Typecheck clean for all touched files.

Follow-ups (not in this PR)

  • Payload caps on the JSON APIs themselves (/api/diff on a mega-commit still returns MBs of JSON).
  • Liveness probe timeout bump + event-loop load shedding (infra/chart).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Browse pages now fetch file sources, folder contents, diffs, and blame in the browser via API routes for a smoother experience.
    • Commit and tree preview panels were updated to render client-side loading/error states and interactive UI.
  • Bug Fixes
    • Improved diff handling for initial commits (files show as added by default).
    • More reliable matching for file renames and deletions when viewing diffs.
  • Chores
    • Added a robots.txt route that blocks crawlers by default while allowing common link-preview bots to keep OpenGraph previews working.

brendan-kellamand others added 2 commits July 7, 2026 18:49
…w bots
Sourcebot exposes an unbounded URL space (every file x revision x commit)
and crawler traffic (GPTBot, Applebot, meta-externalagent, AhrefsBot)
generates heavy SSR load. Deny all crawling while keeping unfurl bots
(Slack, X, LinkedIn, etc.) allowed so shared links keep their OpenGraph
previews.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…C props
Blob, tree, and commit-diff panels previously fetched git data (file
source, folder contents, full commit diffs) in server components and
passed it as props across the client boundary. React flight-encodes
those props into the document at seconds of main-thread time for large
payloads (a large commit page measured 38MB / ~8s of encoding), which
starves the event loop and health probes under crawler load.
The panels now render a shell and fetch via existing JSON API routes
with react-query (per repo convention), plus a new /api/folder_contents
route. Documents drop to the ~70KB shell regardless of content size,
and JS-less crawlers never trigger the data fetch at all.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@coderabbitai

coderabbitaiBot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5786f4f6-5cd0-4eb5-a948-d2d82fccae7d

📥 Commits

Reviewing files that changed from the base of the PR and between 98aa8a7 and badd362.

📒 Files selected for processing (1)
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanelClient.tsx

Walkthrough

Browse preview and commit-diff panels now fetch only repository metadata on the server and load file, folder, commit, diff, and blame data in client components. New client API helpers, a folder contents API route, changelog notes, and a robots policy accompany the refactor.

Changes

Browse panel client-side refactor

Layer / File(s)Summary
Client API wrappers and folder_contents route
packages/web/src/app/api/(client)/client.ts, packages/web/src/app/api/(server)/folder_contents/route.ts
Adds getCommit, getDiff, getFolderContents, and getFileBlame client helpers, plus a validated GET route for folder contents.
Code preview panel client migration
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanelClient.tsx
CodePreviewPanel now fetches only repo info and delegates to CodePreviewPanelClient, which fetches file source and blame, computes file metadata, and renders preview and header controls.
Tree preview panel client migration
packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanelClient.tsx
TreePreviewPanel now fetches only repo info and delegates to TreePreviewPanelClient, which fetches folder contents and renders the tree header and items.
Commit diff panels client migration
packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx, packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/fullCommitDiffPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/useCommitDiff.ts
FocusedCommitDiffPanel and FullCommitDiffPanel now delegate commit and diff loading to useCommitDiff, and FocusedCommitDiffPanelClient renders the matching file diff, metadata, and viewer.
Changelog updates
CHANGELOG.md
Adds entries for the browse-page client-side fetch refactor and the robots route.

Estimated code review effort: 4 (Complex) | ~60 minutes

Robots.txt Crawler Policy

Layer / File(s)Summary
Robots configuration
packages/web/src/app/robots.ts
Introduces robots() returning MetadataRoute.Robots with an allowlist for preview bots and a catch-all disallow rule.

Sequence Diagram(s)

sequenceDiagram
participant CodePreviewPanel
participant CodePreviewPanelClient
participant ClientAPI
CodePreviewPanel->>ClientAPI: getRepoInfoByName(repoName)
CodePreviewPanel->>CodePreviewPanelClient: render with repo, path, revisionName
CodePreviewPanelClient->>ClientAPI: getFileSource(...)
CodePreviewPanelClient->>ClientAPI: getFileBlame(...)
ClientAPI-->>CodePreviewPanelClient: source, blame data
CodePreviewPanelClient-->>CodePreviewPanelClient: render PureCodePreviewPanel
Loading
sequenceDiagram
participant FocusedCommitDiffPanel
participant FocusedCommitDiffPanelClient
participant useCommitDiff
participant ClientAPI
FocusedCommitDiffPanel->>ClientAPI: getRepoInfoByName(repoName)
FocusedCommitDiffPanel->>FocusedCommitDiffPanelClient: render with repo, commitSha, path
FocusedCommitDiffPanelClient->>useCommitDiff: useCommitDiff(repoName, commitSha, path)
useCommitDiff->>ClientAPI: getCommit(...)
useCommitDiff->>ClientAPI: getDiff(...)
ClientAPI-->>useCommitDiff: commit, diff
useCommitDiff-->>FocusedCommitDiffPanelClient: commit, diff
FocusedCommitDiffPanelClient-->>FocusedCommitDiffPanelClient: render file header and LightweightDiffViewer
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly captures the two main changes: client-side /browse data fetching and crawler blocking via robots.txt.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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/browse-csr

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.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@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.

🧹 Nitpick comments (2)
packages/web/src/app/api/(client)/client.ts (1)

173-240: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Optional: Extract a shared fetch helper to reduce duplication.

The four new functions (getCommit, getDiff, getFolderContents, getFileBlame) share the same structure: URL construction, param iteration, fetch with the X-Sourcebot-Client-Source header, JSON parsing, and type casting. A small shared helper would centralize the pattern and make future additions or header changes a one-line edit.

♻️ Suggested shared helper
+async function fetchJson<T>(endpoint: string, queryParams: Record<string, string | undefined>): Promise<T | ServiceError> {+ const url = new URL(endpoint, window.location.origin);+ for (const [key, value] of Object.entries(queryParams)) {+ if (value !== undefined) {+ url.searchParams.set(key, value);+ }+ }+ const result = await fetch(url, {+ method: "GET",+ headers: {+ "X-Sourcebot-Client-Source": "sourcebot-web-client",+ },+ }).then(response => response.json());+ return result as T | ServiceError;+}+-export const getCommit = async (queryParams: { repo: string; ref: string }): Promise<CommitDetail | ServiceError> => {- const url = new URL("/api/commit", window.location.origin);- for (const [key, value] of Object.entries(queryParams)) {- url.searchParams.set(key, value);- }-- const result = await fetch(url, {- method: "GET",- headers: {- "X-Sourcebot-Client-Source": "sourcebot-web-client",- },- }).then(response => response.json());-- return result as CommitDetail | ServiceError;-}+export const getCommit = (queryParams: { repo: string; ref: string }) =>+ fetchJson<CommitDetail>("/api/commit", queryParams);

The same simplification applies to getDiff, getFolderContents, and getFileBlame. The undefined filter in the helper covers both required and optional param types uniformly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/api/`(client)/client.ts around lines 173 - 240, The four
API client functions repeat the same URL-building and fetch/json/header logic,
so extract that common flow into a shared helper in client.ts and have
getCommit, getDiff, getFolderContents, and getFileBlame delegate to it. Keep the
helper responsible for appending query params, setting the
X-Sourcebot-Client-Source header, performing fetch, and returning parsed JSON so
future header or request changes only need one update.
packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx (1)

42-57: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: extract the shared pending/error UI.

These pending and error blocks are duplicated verbatim in fullCommitDiffPanel.tsx (lines 25-40). Consider a small shared wrapper (e.g. <CommitDiffState isPending error>) to avoid divergence as the panels evolve.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/web/src/app/`(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx
around lines 42 - 57, The pending and error UI in focusedCommitDiffPanelClient
is duplicated in fullCommitDiffPanel, so extract the shared loading/error
rendering into a reusable component or wrapper (for example, a CommitDiffState
in the shared commit diff panel area) and use it from both panels. Keep the
existing behavior of the isPending and error/data checks, but centralize the
Loader2/loading text and error message markup so both
focusedCommitDiffPanelClient and fullCommitDiffPanel stay in sync as they
evolve.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In
`@packages/web/src/app/`(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx:
- Around line 42-57: The pending and error UI in focusedCommitDiffPanelClient is
duplicated in fullCommitDiffPanel, so extract the shared loading/error rendering
into a reusable component or wrapper (for example, a CommitDiffState in the
shared commit diff panel area) and use it from both panels. Keep the existing
behavior of the isPending and error/data checks, but centralize the
Loader2/loading text and error message markup so both
focusedCommitDiffPanelClient and fullCommitDiffPanel stay in sync as they
evolve.
In `@packages/web/src/app/api/`(client)/client.ts:
- Around line 173-240: The four API client functions repeat the same
URL-building and fetch/json/header logic, so extract that common flow into a
shared helper in client.ts and have getCommit, getDiff, getFolderContents, and
getFileBlame delegate to it. Keep the helper responsible for appending query
params, setting the X-Sourcebot-Client-Source header, performing fetch, and
returning parsed JSON so future header or request changes only need one update.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c02f0a2b-b454-4983-964c-cb35fa6d5583

📥 Commits

Reviewing files that changed from the base of the PR and between 94b2af6 and 80ee7fa.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanelClient.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/fullCommitDiffPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/useCommitDiff.ts
  • packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanelClient.tsx
  • packages/web/src/app/api/(client)/client.ts
  • packages/web/src/app/api/(server)/folder_contents/route.ts
  • packages/web/src/app/robots.ts

brendan-kellamand others added 2 commits July 7, 2026 20:00
Render the path header, separator, and file toolbar (blame toggle + age
legend) immediately from props, and scope the loading spinner / error to
the body below. The line-count/size stat shows a skeleton while the file
source is pending, then the real value once loaded.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@brendan-kellam
brendan-kellam merged commit 2afc265 into mainJul 8, 2026
10 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/browse-csr branch July 8, 2026 03:53
@github-actionsgithub-actionsBot mentioned this pull request Jul 8, 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): fetch /browse data client-side and disallow crawlers via robots.txt - #1426

Merged
brendan-kellam merged 5 commits into
mainfrom
brendan/browse-csr
Jul 8, 2026
Merged

feat(web): fetch /browse data client-side and disallow crawlers via robots.txt#1426
brendan-kellam merged 5 commits into
mainfrom
brendan/browse-csr

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Problem

app.sourcebot.dev was repeatedly killed by its liveness probe (6 restarts in 24h). Investigation traced the cause to /browse pages: blob/tree/commit panels fetched git data (file sources, folder contents, full commit diffs) in server components and passed it as props across the client-component boundary. React flight-encodes those props into the document on the main thread — a single large commit page measured 38MB of RSC payload (~8s of encoding), and a binary blob produced a 62MB document. Crawler traffic (GPTBot, Applebot, meta-externalagent, AhrefsBot — ~97% of all server processing time) walked the unbounded @<sha> URL space, stacking these renders until the event loop starved and health probes timed out.

Changes

  1. robots.txt (app/robots.ts): disallow all crawling, with an allowlist for link-preview fetchers (Slackbot, Twitterbot, LinkedInBot, Discordbot, facebookexternalhit, TelegramBot, WhatsApp) so shared links — e.g. chat pages — keep their OpenGraph unfurls. Applebot/GPTBot/meta-externalagent are deliberately not allowlisted (search/AI-training crawlers, not preview fetchers).

  2. Client-side data fetching for /browse panels: CodePreviewPanel, TreePreviewPanel, FullCommitDiffPanel, and FocusedCommitDiffPanel now render a shell and fetch via JSON API routes with react-query (per repo convention). Server components only fetch repoInfo for the path header.

    • New /api/folder_contents route (internal, not registered in the public API) wrapping the existing getFolderContents.
    • New client fetchers: getCommit, getDiff, getFolderContents, getFileBlame.
    • Shared useCommitDiff hook preserving the initial-commit empty-tree fallback.
    • CommitsPanel left server-side intentionally (bounded at 35 commits/page).

Documents drop to the ~70KB app shell regardless of content size, JSON serialization replaces flight encoding (~50-100x less main-thread time for large payloads), and JS-less crawlers never trigger data fetches at all.

Verification

Exercised in an isolated dev environment against an indexed repo: blob, tree, commit, blame-mode, and focused-diff pages all render correctly via client fetching; blob documents contain zero file content and commit documents zero hunk payloads (previously embedded); all API routes return 200; robots.txt serves the intended rules. Typecheck clean for all touched files.

Follow-ups (not in this PR)

  • Payload caps on the JSON APIs themselves (/api/diff on a mega-commit still returns MBs of JSON).
  • Liveness probe timeout bump + event-loop load shedding (infra/chart).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Browse pages now fetch file sources, folder contents, diffs, and blame in the browser via API routes for a smoother experience.
    • Commit and tree preview panels were updated to render client-side loading/error states and interactive UI.
  • Bug Fixes
    • Improved diff handling for initial commits (files show as added by default).
    • More reliable matching for file renames and deletions when viewing diffs.
  • Chores
    • Added a robots.txt route that blocks crawlers by default while allowing common link-preview bots to keep OpenGraph previews working.

brendan-kellamand others added 2 commits July 7, 2026 18:49
…w bots
Sourcebot exposes an unbounded URL space (every file x revision x commit)
and crawler traffic (GPTBot, Applebot, meta-externalagent, AhrefsBot)
generates heavy SSR load. Deny all crawling while keeping unfurl bots
(Slack, X, LinkedIn, etc.) allowed so shared links keep their OpenGraph
previews.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…C props
Blob, tree, and commit-diff panels previously fetched git data (file
source, folder contents, full commit diffs) in server components and
passed it as props across the client boundary. React flight-encodes
those props into the document at seconds of main-thread time for large
payloads (a large commit page measured 38MB / ~8s of encoding), which
starves the event loop and health probes under crawler load.
The panels now render a shell and fetch via existing JSON API routes
with react-query (per repo convention), plus a new /api/folder_contents
route. Documents drop to the ~70KB shell regardless of content size,
and JS-less crawlers never trigger the data fetch at all.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@coderabbitai

coderabbitaiBot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5786f4f6-5cd0-4eb5-a948-d2d82fccae7d

📥 Commits

Reviewing files that changed from the base of the PR and between 98aa8a7 and badd362.

📒 Files selected for processing (1)
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanelClient.tsx

Walkthrough

Browse preview and commit-diff panels now fetch only repository metadata on the server and load file, folder, commit, diff, and blame data in client components. New client API helpers, a folder contents API route, changelog notes, and a robots policy accompany the refactor.

Changes

Browse panel client-side refactor

Layer / File(s)Summary
Client API wrappers and folder_contents route
packages/web/src/app/api/(client)/client.ts, packages/web/src/app/api/(server)/folder_contents/route.ts
Adds getCommit, getDiff, getFolderContents, and getFileBlame client helpers, plus a validated GET route for folder contents.
Code preview panel client migration
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanelClient.tsx
CodePreviewPanel now fetches only repo info and delegates to CodePreviewPanelClient, which fetches file source and blame, computes file metadata, and renders preview and header controls.
Tree preview panel client migration
packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanelClient.tsx
TreePreviewPanel now fetches only repo info and delegates to TreePreviewPanelClient, which fetches folder contents and renders the tree header and items.
Commit diff panels client migration
packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx, packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/fullCommitDiffPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/useCommitDiff.ts
FocusedCommitDiffPanel and FullCommitDiffPanel now delegate commit and diff loading to useCommitDiff, and FocusedCommitDiffPanelClient renders the matching file diff, metadata, and viewer.
Changelog updates
CHANGELOG.md
Adds entries for the browse-page client-side fetch refactor and the robots route.

Estimated code review effort: 4 (Complex) | ~60 minutes

Robots.txt Crawler Policy

Layer / File(s)Summary
Robots configuration
packages/web/src/app/robots.ts
Introduces robots() returning MetadataRoute.Robots with an allowlist for preview bots and a catch-all disallow rule.

Sequence Diagram(s)

sequenceDiagram
participant CodePreviewPanel
participant CodePreviewPanelClient
participant ClientAPI
CodePreviewPanel->>ClientAPI: getRepoInfoByName(repoName)
CodePreviewPanel->>CodePreviewPanelClient: render with repo, path, revisionName
CodePreviewPanelClient->>ClientAPI: getFileSource(...)
CodePreviewPanelClient->>ClientAPI: getFileBlame(...)
ClientAPI-->>CodePreviewPanelClient: source, blame data
CodePreviewPanelClient-->>CodePreviewPanelClient: render PureCodePreviewPanel
Loading
sequenceDiagram
participant FocusedCommitDiffPanel
participant FocusedCommitDiffPanelClient
participant useCommitDiff
participant ClientAPI
FocusedCommitDiffPanel->>ClientAPI: getRepoInfoByName(repoName)
FocusedCommitDiffPanel->>FocusedCommitDiffPanelClient: render with repo, commitSha, path
FocusedCommitDiffPanelClient->>useCommitDiff: useCommitDiff(repoName, commitSha, path)
useCommitDiff->>ClientAPI: getCommit(...)
useCommitDiff->>ClientAPI: getDiff(...)
ClientAPI-->>useCommitDiff: commit, diff
useCommitDiff-->>FocusedCommitDiffPanelClient: commit, diff
FocusedCommitDiffPanelClient-->>FocusedCommitDiffPanelClient: render file header and LightweightDiffViewer
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly captures the two main changes: client-side /browse data fetching and crawler blocking via robots.txt.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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/browse-csr

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.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@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.

🧹 Nitpick comments (2)
packages/web/src/app/api/(client)/client.ts (1)

173-240: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Optional: Extract a shared fetch helper to reduce duplication.

The four new functions (getCommit, getDiff, getFolderContents, getFileBlame) share the same structure: URL construction, param iteration, fetch with the X-Sourcebot-Client-Source header, JSON parsing, and type casting. A small shared helper would centralize the pattern and make future additions or header changes a one-line edit.

♻️ Suggested shared helper
+async function fetchJson<T>(endpoint: string, queryParams: Record<string, string | undefined>): Promise<T | ServiceError> {+ const url = new URL(endpoint, window.location.origin);+ for (const [key, value] of Object.entries(queryParams)) {+ if (value !== undefined) {+ url.searchParams.set(key, value);+ }+ }+ const result = await fetch(url, {+ method: "GET",+ headers: {+ "X-Sourcebot-Client-Source": "sourcebot-web-client",+ },+ }).then(response => response.json());+ return result as T | ServiceError;+}+-export const getCommit = async (queryParams: { repo: string; ref: string }): Promise<CommitDetail | ServiceError> => {- const url = new URL("/api/commit", window.location.origin);- for (const [key, value] of Object.entries(queryParams)) {- url.searchParams.set(key, value);- }-- const result = await fetch(url, {- method: "GET",- headers: {- "X-Sourcebot-Client-Source": "sourcebot-web-client",- },- }).then(response => response.json());-- return result as CommitDetail | ServiceError;-}+export const getCommit = (queryParams: { repo: string; ref: string }) =>+ fetchJson<CommitDetail>("/api/commit", queryParams);

The same simplification applies to getDiff, getFolderContents, and getFileBlame. The undefined filter in the helper covers both required and optional param types uniformly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/api/`(client)/client.ts around lines 173 - 240, The four
API client functions repeat the same URL-building and fetch/json/header logic,
so extract that common flow into a shared helper in client.ts and have
getCommit, getDiff, getFolderContents, and getFileBlame delegate to it. Keep the
helper responsible for appending query params, setting the
X-Sourcebot-Client-Source header, performing fetch, and returning parsed JSON so
future header or request changes only need one update.
packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx (1)

42-57: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: extract the shared pending/error UI.

These pending and error blocks are duplicated verbatim in fullCommitDiffPanel.tsx (lines 25-40). Consider a small shared wrapper (e.g. <CommitDiffState isPending error>) to avoid divergence as the panels evolve.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/web/src/app/`(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx
around lines 42 - 57, The pending and error UI in focusedCommitDiffPanelClient
is duplicated in fullCommitDiffPanel, so extract the shared loading/error
rendering into a reusable component or wrapper (for example, a CommitDiffState
in the shared commit diff panel area) and use it from both panels. Keep the
existing behavior of the isPending and error/data checks, but centralize the
Loader2/loading text and error message markup so both
focusedCommitDiffPanelClient and fullCommitDiffPanel stay in sync as they
evolve.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In
`@packages/web/src/app/`(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx:
- Around line 42-57: The pending and error UI in focusedCommitDiffPanelClient is
duplicated in fullCommitDiffPanel, so extract the shared loading/error rendering
into a reusable component or wrapper (for example, a CommitDiffState in the
shared commit diff panel area) and use it from both panels. Keep the existing
behavior of the isPending and error/data checks, but centralize the
Loader2/loading text and error message markup so both
focusedCommitDiffPanelClient and fullCommitDiffPanel stay in sync as they
evolve.
In `@packages/web/src/app/api/`(client)/client.ts:
- Around line 173-240: The four API client functions repeat the same
URL-building and fetch/json/header logic, so extract that common flow into a
shared helper in client.ts and have getCommit, getDiff, getFolderContents, and
getFileBlame delegate to it. Keep the helper responsible for appending query
params, setting the X-Sourcebot-Client-Source header, performing fetch, and
returning parsed JSON so future header or request changes only need one update.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c02f0a2b-b454-4983-964c-cb35fa6d5583

📥 Commits

Reviewing files that changed from the base of the PR and between 94b2af6 and 80ee7fa.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanelClient.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/fullCommitDiffPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/useCommitDiff.ts
  • packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanelClient.tsx
  • packages/web/src/app/api/(client)/client.ts
  • packages/web/src/app/api/(server)/folder_contents/route.ts
  • packages/web/src/app/robots.ts

brendan-kellamand others added 2 commits July 7, 2026 20:00
Render the path header, separator, and file toolbar (blame toggle + age
legend) immediately from props, and scope the loading spinner / error to
the body below. The line-count/size stat shows a skeleton while the file
source is pending, then the real value once loaded.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@brendan-kellam
brendan-kellam merged commit 2afc265 into mainJul 8, 2026
10 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/browse-csr branch July 8, 2026 03:53
@github-actionsgithub-actionsBot mentioned this pull request Jul 8, 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): fetch /browse data client-side and disallow crawlers via robots.txt - #1426

Merged
brendan-kellam merged 5 commits into
mainfrom
brendan/browse-csr
Jul 8, 2026
Merged

feat(web): fetch /browse data client-side and disallow crawlers via robots.txt#1426
brendan-kellam merged 5 commits into
mainfrom
brendan/browse-csr

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Problem

app.sourcebot.dev was repeatedly killed by its liveness probe (6 restarts in 24h). Investigation traced the cause to /browse pages: blob/tree/commit panels fetched git data (file sources, folder contents, full commit diffs) in server components and passed it as props across the client-component boundary. React flight-encodes those props into the document on the main thread — a single large commit page measured 38MB of RSC payload (~8s of encoding), and a binary blob produced a 62MB document. Crawler traffic (GPTBot, Applebot, meta-externalagent, AhrefsBot — ~97% of all server processing time) walked the unbounded @<sha> URL space, stacking these renders until the event loop starved and health probes timed out.

Changes

  1. robots.txt (app/robots.ts): disallow all crawling, with an allowlist for link-preview fetchers (Slackbot, Twitterbot, LinkedInBot, Discordbot, facebookexternalhit, TelegramBot, WhatsApp) so shared links — e.g. chat pages — keep their OpenGraph unfurls. Applebot/GPTBot/meta-externalagent are deliberately not allowlisted (search/AI-training crawlers, not preview fetchers).

  2. Client-side data fetching for /browse panels: CodePreviewPanel, TreePreviewPanel, FullCommitDiffPanel, and FocusedCommitDiffPanel now render a shell and fetch via JSON API routes with react-query (per repo convention). Server components only fetch repoInfo for the path header.

    • New /api/folder_contents route (internal, not registered in the public API) wrapping the existing getFolderContents.
    • New client fetchers: getCommit, getDiff, getFolderContents, getFileBlame.
    • Shared useCommitDiff hook preserving the initial-commit empty-tree fallback.
    • CommitsPanel left server-side intentionally (bounded at 35 commits/page).

Documents drop to the ~70KB app shell regardless of content size, JSON serialization replaces flight encoding (~50-100x less main-thread time for large payloads), and JS-less crawlers never trigger data fetches at all.

Verification

Exercised in an isolated dev environment against an indexed repo: blob, tree, commit, blame-mode, and focused-diff pages all render correctly via client fetching; blob documents contain zero file content and commit documents zero hunk payloads (previously embedded); all API routes return 200; robots.txt serves the intended rules. Typecheck clean for all touched files.

Follow-ups (not in this PR)

  • Payload caps on the JSON APIs themselves (/api/diff on a mega-commit still returns MBs of JSON).
  • Liveness probe timeout bump + event-loop load shedding (infra/chart).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Browse pages now fetch file sources, folder contents, diffs, and blame in the browser via API routes for a smoother experience.
    • Commit and tree preview panels were updated to render client-side loading/error states and interactive UI.
  • Bug Fixes
    • Improved diff handling for initial commits (files show as added by default).
    • More reliable matching for file renames and deletions when viewing diffs.
  • Chores
    • Added a robots.txt route that blocks crawlers by default while allowing common link-preview bots to keep OpenGraph previews working.

brendan-kellamand others added 2 commits July 7, 2026 18:49
…w bots
Sourcebot exposes an unbounded URL space (every file x revision x commit)
and crawler traffic (GPTBot, Applebot, meta-externalagent, AhrefsBot)
generates heavy SSR load. Deny all crawling while keeping unfurl bots
(Slack, X, LinkedIn, etc.) allowed so shared links keep their OpenGraph
previews.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…C props
Blob, tree, and commit-diff panels previously fetched git data (file
source, folder contents, full commit diffs) in server components and
passed it as props across the client boundary. React flight-encodes
those props into the document at seconds of main-thread time for large
payloads (a large commit page measured 38MB / ~8s of encoding), which
starves the event loop and health probes under crawler load.
The panels now render a shell and fetch via existing JSON API routes
with react-query (per repo convention), plus a new /api/folder_contents
route. Documents drop to the ~70KB shell regardless of content size,
and JS-less crawlers never trigger the data fetch at all.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@coderabbitai

coderabbitaiBot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5786f4f6-5cd0-4eb5-a948-d2d82fccae7d

📥 Commits

Reviewing files that changed from the base of the PR and between 98aa8a7 and badd362.

📒 Files selected for processing (1)
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanelClient.tsx

Walkthrough

Browse preview and commit-diff panels now fetch only repository metadata on the server and load file, folder, commit, diff, and blame data in client components. New client API helpers, a folder contents API route, changelog notes, and a robots policy accompany the refactor.

Changes

Browse panel client-side refactor

Layer / File(s)Summary
Client API wrappers and folder_contents route
packages/web/src/app/api/(client)/client.ts, packages/web/src/app/api/(server)/folder_contents/route.ts
Adds getCommit, getDiff, getFolderContents, and getFileBlame client helpers, plus a validated GET route for folder contents.
Code preview panel client migration
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanelClient.tsx
CodePreviewPanel now fetches only repo info and delegates to CodePreviewPanelClient, which fetches file source and blame, computes file metadata, and renders preview and header controls.
Tree preview panel client migration
packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanelClient.tsx
TreePreviewPanel now fetches only repo info and delegates to TreePreviewPanelClient, which fetches folder contents and renders the tree header and items.
Commit diff panels client migration
packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx, packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/fullCommitDiffPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/useCommitDiff.ts
FocusedCommitDiffPanel and FullCommitDiffPanel now delegate commit and diff loading to useCommitDiff, and FocusedCommitDiffPanelClient renders the matching file diff, metadata, and viewer.
Changelog updates
CHANGELOG.md
Adds entries for the browse-page client-side fetch refactor and the robots route.

Estimated code review effort: 4 (Complex) | ~60 minutes

Robots.txt Crawler Policy

Layer / File(s)Summary
Robots configuration
packages/web/src/app/robots.ts
Introduces robots() returning MetadataRoute.Robots with an allowlist for preview bots and a catch-all disallow rule.

Sequence Diagram(s)

sequenceDiagram
participant CodePreviewPanel
participant CodePreviewPanelClient
participant ClientAPI
CodePreviewPanel->>ClientAPI: getRepoInfoByName(repoName)
CodePreviewPanel->>CodePreviewPanelClient: render with repo, path, revisionName
CodePreviewPanelClient->>ClientAPI: getFileSource(...)
CodePreviewPanelClient->>ClientAPI: getFileBlame(...)
ClientAPI-->>CodePreviewPanelClient: source, blame data
CodePreviewPanelClient-->>CodePreviewPanelClient: render PureCodePreviewPanel
Loading
sequenceDiagram
participant FocusedCommitDiffPanel
participant FocusedCommitDiffPanelClient
participant useCommitDiff
participant ClientAPI
FocusedCommitDiffPanel->>ClientAPI: getRepoInfoByName(repoName)
FocusedCommitDiffPanel->>FocusedCommitDiffPanelClient: render with repo, commitSha, path
FocusedCommitDiffPanelClient->>useCommitDiff: useCommitDiff(repoName, commitSha, path)
useCommitDiff->>ClientAPI: getCommit(...)
useCommitDiff->>ClientAPI: getDiff(...)
ClientAPI-->>useCommitDiff: commit, diff
useCommitDiff-->>FocusedCommitDiffPanelClient: commit, diff
FocusedCommitDiffPanelClient-->>FocusedCommitDiffPanelClient: render file header and LightweightDiffViewer
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly captures the two main changes: client-side /browse data fetching and crawler blocking via robots.txt.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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/browse-csr

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.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@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.

🧹 Nitpick comments (2)
packages/web/src/app/api/(client)/client.ts (1)

173-240: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Optional: Extract a shared fetch helper to reduce duplication.

The four new functions (getCommit, getDiff, getFolderContents, getFileBlame) share the same structure: URL construction, param iteration, fetch with the X-Sourcebot-Client-Source header, JSON parsing, and type casting. A small shared helper would centralize the pattern and make future additions or header changes a one-line edit.

♻️ Suggested shared helper
+async function fetchJson<T>(endpoint: string, queryParams: Record<string, string | undefined>): Promise<T | ServiceError> {+ const url = new URL(endpoint, window.location.origin);+ for (const [key, value] of Object.entries(queryParams)) {+ if (value !== undefined) {+ url.searchParams.set(key, value);+ }+ }+ const result = await fetch(url, {+ method: "GET",+ headers: {+ "X-Sourcebot-Client-Source": "sourcebot-web-client",+ },+ }).then(response => response.json());+ return result as T | ServiceError;+}+-export const getCommit = async (queryParams: { repo: string; ref: string }): Promise<CommitDetail | ServiceError> => {- const url = new URL("/api/commit", window.location.origin);- for (const [key, value] of Object.entries(queryParams)) {- url.searchParams.set(key, value);- }-- const result = await fetch(url, {- method: "GET",- headers: {- "X-Sourcebot-Client-Source": "sourcebot-web-client",- },- }).then(response => response.json());-- return result as CommitDetail | ServiceError;-}+export const getCommit = (queryParams: { repo: string; ref: string }) =>+ fetchJson<CommitDetail>("/api/commit", queryParams);

The same simplification applies to getDiff, getFolderContents, and getFileBlame. The undefined filter in the helper covers both required and optional param types uniformly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/api/`(client)/client.ts around lines 173 - 240, The four
API client functions repeat the same URL-building and fetch/json/header logic,
so extract that common flow into a shared helper in client.ts and have
getCommit, getDiff, getFolderContents, and getFileBlame delegate to it. Keep the
helper responsible for appending query params, setting the
X-Sourcebot-Client-Source header, performing fetch, and returning parsed JSON so
future header or request changes only need one update.
packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx (1)

42-57: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: extract the shared pending/error UI.

These pending and error blocks are duplicated verbatim in fullCommitDiffPanel.tsx (lines 25-40). Consider a small shared wrapper (e.g. <CommitDiffState isPending error>) to avoid divergence as the panels evolve.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/web/src/app/`(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx
around lines 42 - 57, The pending and error UI in focusedCommitDiffPanelClient
is duplicated in fullCommitDiffPanel, so extract the shared loading/error
rendering into a reusable component or wrapper (for example, a CommitDiffState
in the shared commit diff panel area) and use it from both panels. Keep the
existing behavior of the isPending and error/data checks, but centralize the
Loader2/loading text and error message markup so both
focusedCommitDiffPanelClient and fullCommitDiffPanel stay in sync as they
evolve.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In
`@packages/web/src/app/`(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx:
- Around line 42-57: The pending and error UI in focusedCommitDiffPanelClient is
duplicated in fullCommitDiffPanel, so extract the shared loading/error rendering
into a reusable component or wrapper (for example, a CommitDiffState in the
shared commit diff panel area) and use it from both panels. Keep the existing
behavior of the isPending and error/data checks, but centralize the
Loader2/loading text and error message markup so both
focusedCommitDiffPanelClient and fullCommitDiffPanel stay in sync as they
evolve.
In `@packages/web/src/app/api/`(client)/client.ts:
- Around line 173-240: The four API client functions repeat the same
URL-building and fetch/json/header logic, so extract that common flow into a
shared helper in client.ts and have getCommit, getDiff, getFolderContents, and
getFileBlame delegate to it. Keep the helper responsible for appending query
params, setting the X-Sourcebot-Client-Source header, performing fetch, and
returning parsed JSON so future header or request changes only need one update.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c02f0a2b-b454-4983-964c-cb35fa6d5583

📥 Commits

Reviewing files that changed from the base of the PR and between 94b2af6 and 80ee7fa.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanelClient.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/fullCommitDiffPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/useCommitDiff.ts
  • packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanelClient.tsx
  • packages/web/src/app/api/(client)/client.ts
  • packages/web/src/app/api/(server)/folder_contents/route.ts
  • packages/web/src/app/robots.ts

brendan-kellamand others added 2 commits July 7, 2026 20:00
Render the path header, separator, and file toolbar (blame toggle + age
legend) immediately from props, and scope the loading spinner / error to
the body below. The line-count/size stat shows a skeleton while the file
source is pending, then the real value once loaded.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@brendan-kellam
brendan-kellam merged commit 2afc265 into mainJul 8, 2026
10 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/browse-csr branch July 8, 2026 03:53
@github-actionsgithub-actionsBot mentioned this pull request Jul 8, 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): fetch /browse data client-side and disallow crawlers via robots.txt - #1426

Merged
brendan-kellam merged 5 commits into
mainfrom
brendan/browse-csr
Jul 8, 2026
Merged

feat(web): fetch /browse data client-side and disallow crawlers via robots.txt#1426
brendan-kellam merged 5 commits into
mainfrom
brendan/browse-csr

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Problem

app.sourcebot.dev was repeatedly killed by its liveness probe (6 restarts in 24h). Investigation traced the cause to /browse pages: blob/tree/commit panels fetched git data (file sources, folder contents, full commit diffs) in server components and passed it as props across the client-component boundary. React flight-encodes those props into the document on the main thread — a single large commit page measured 38MB of RSC payload (~8s of encoding), and a binary blob produced a 62MB document. Crawler traffic (GPTBot, Applebot, meta-externalagent, AhrefsBot — ~97% of all server processing time) walked the unbounded @<sha> URL space, stacking these renders until the event loop starved and health probes timed out.

Changes

  1. robots.txt (app/robots.ts): disallow all crawling, with an allowlist for link-preview fetchers (Slackbot, Twitterbot, LinkedInBot, Discordbot, facebookexternalhit, TelegramBot, WhatsApp) so shared links — e.g. chat pages — keep their OpenGraph unfurls. Applebot/GPTBot/meta-externalagent are deliberately not allowlisted (search/AI-training crawlers, not preview fetchers).

  2. Client-side data fetching for /browse panels: CodePreviewPanel, TreePreviewPanel, FullCommitDiffPanel, and FocusedCommitDiffPanel now render a shell and fetch via JSON API routes with react-query (per repo convention). Server components only fetch repoInfo for the path header.

    • New /api/folder_contents route (internal, not registered in the public API) wrapping the existing getFolderContents.
    • New client fetchers: getCommit, getDiff, getFolderContents, getFileBlame.
    • Shared useCommitDiff hook preserving the initial-commit empty-tree fallback.
    • CommitsPanel left server-side intentionally (bounded at 35 commits/page).

Documents drop to the ~70KB app shell regardless of content size, JSON serialization replaces flight encoding (~50-100x less main-thread time for large payloads), and JS-less crawlers never trigger data fetches at all.

Verification

Exercised in an isolated dev environment against an indexed repo: blob, tree, commit, blame-mode, and focused-diff pages all render correctly via client fetching; blob documents contain zero file content and commit documents zero hunk payloads (previously embedded); all API routes return 200; robots.txt serves the intended rules. Typecheck clean for all touched files.

Follow-ups (not in this PR)

  • Payload caps on the JSON APIs themselves (/api/diff on a mega-commit still returns MBs of JSON).
  • Liveness probe timeout bump + event-loop load shedding (infra/chart).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Browse pages now fetch file sources, folder contents, diffs, and blame in the browser via API routes for a smoother experience.
    • Commit and tree preview panels were updated to render client-side loading/error states and interactive UI.
  • Bug Fixes
    • Improved diff handling for initial commits (files show as added by default).
    • More reliable matching for file renames and deletions when viewing diffs.
  • Chores
    • Added a robots.txt route that blocks crawlers by default while allowing common link-preview bots to keep OpenGraph previews working.

brendan-kellamand others added 2 commits July 7, 2026 18:49
…w bots
Sourcebot exposes an unbounded URL space (every file x revision x commit)
and crawler traffic (GPTBot, Applebot, meta-externalagent, AhrefsBot)
generates heavy SSR load. Deny all crawling while keeping unfurl bots
(Slack, X, LinkedIn, etc.) allowed so shared links keep their OpenGraph
previews.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…C props
Blob, tree, and commit-diff panels previously fetched git data (file
source, folder contents, full commit diffs) in server components and
passed it as props across the client boundary. React flight-encodes
those props into the document at seconds of main-thread time for large
payloads (a large commit page measured 38MB / ~8s of encoding), which
starves the event loop and health probes under crawler load.
The panels now render a shell and fetch via existing JSON API routes
with react-query (per repo convention), plus a new /api/folder_contents
route. Documents drop to the ~70KB shell regardless of content size,
and JS-less crawlers never trigger the data fetch at all.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@coderabbitai

coderabbitaiBot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5786f4f6-5cd0-4eb5-a948-d2d82fccae7d

📥 Commits

Reviewing files that changed from the base of the PR and between 98aa8a7 and badd362.

📒 Files selected for processing (1)
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanelClient.tsx

Walkthrough

Browse preview and commit-diff panels now fetch only repository metadata on the server and load file, folder, commit, diff, and blame data in client components. New client API helpers, a folder contents API route, changelog notes, and a robots policy accompany the refactor.

Changes

Browse panel client-side refactor

Layer / File(s)Summary
Client API wrappers and folder_contents route
packages/web/src/app/api/(client)/client.ts, packages/web/src/app/api/(server)/folder_contents/route.ts
Adds getCommit, getDiff, getFolderContents, and getFileBlame client helpers, plus a validated GET route for folder contents.
Code preview panel client migration
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanelClient.tsx
CodePreviewPanel now fetches only repo info and delegates to CodePreviewPanelClient, which fetches file source and blame, computes file metadata, and renders preview and header controls.
Tree preview panel client migration
packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanelClient.tsx
TreePreviewPanel now fetches only repo info and delegates to TreePreviewPanelClient, which fetches folder contents and renders the tree header and items.
Commit diff panels client migration
packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx, packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/fullCommitDiffPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/useCommitDiff.ts
FocusedCommitDiffPanel and FullCommitDiffPanel now delegate commit and diff loading to useCommitDiff, and FocusedCommitDiffPanelClient renders the matching file diff, metadata, and viewer.
Changelog updates
CHANGELOG.md
Adds entries for the browse-page client-side fetch refactor and the robots route.

Estimated code review effort: 4 (Complex) | ~60 minutes

Robots.txt Crawler Policy

Layer / File(s)Summary
Robots configuration
packages/web/src/app/robots.ts
Introduces robots() returning MetadataRoute.Robots with an allowlist for preview bots and a catch-all disallow rule.

Sequence Diagram(s)

sequenceDiagram
participant CodePreviewPanel
participant CodePreviewPanelClient
participant ClientAPI
CodePreviewPanel->>ClientAPI: getRepoInfoByName(repoName)
CodePreviewPanel->>CodePreviewPanelClient: render with repo, path, revisionName
CodePreviewPanelClient->>ClientAPI: getFileSource(...)
CodePreviewPanelClient->>ClientAPI: getFileBlame(...)
ClientAPI-->>CodePreviewPanelClient: source, blame data
CodePreviewPanelClient-->>CodePreviewPanelClient: render PureCodePreviewPanel
Loading
sequenceDiagram
participant FocusedCommitDiffPanel
participant FocusedCommitDiffPanelClient
participant useCommitDiff
participant ClientAPI
FocusedCommitDiffPanel->>ClientAPI: getRepoInfoByName(repoName)
FocusedCommitDiffPanel->>FocusedCommitDiffPanelClient: render with repo, commitSha, path
FocusedCommitDiffPanelClient->>useCommitDiff: useCommitDiff(repoName, commitSha, path)
useCommitDiff->>ClientAPI: getCommit(...)
useCommitDiff->>ClientAPI: getDiff(...)
ClientAPI-->>useCommitDiff: commit, diff
useCommitDiff-->>FocusedCommitDiffPanelClient: commit, diff
FocusedCommitDiffPanelClient-->>FocusedCommitDiffPanelClient: render file header and LightweightDiffViewer
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly captures the two main changes: client-side /browse data fetching and crawler blocking via robots.txt.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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/browse-csr

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.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@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.

🧹 Nitpick comments (2)
packages/web/src/app/api/(client)/client.ts (1)

173-240: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Optional: Extract a shared fetch helper to reduce duplication.

The four new functions (getCommit, getDiff, getFolderContents, getFileBlame) share the same structure: URL construction, param iteration, fetch with the X-Sourcebot-Client-Source header, JSON parsing, and type casting. A small shared helper would centralize the pattern and make future additions or header changes a one-line edit.

♻️ Suggested shared helper
+async function fetchJson<T>(endpoint: string, queryParams: Record<string, string | undefined>): Promise<T | ServiceError> {+ const url = new URL(endpoint, window.location.origin);+ for (const [key, value] of Object.entries(queryParams)) {+ if (value !== undefined) {+ url.searchParams.set(key, value);+ }+ }+ const result = await fetch(url, {+ method: "GET",+ headers: {+ "X-Sourcebot-Client-Source": "sourcebot-web-client",+ },+ }).then(response => response.json());+ return result as T | ServiceError;+}+-export const getCommit = async (queryParams: { repo: string; ref: string }): Promise<CommitDetail | ServiceError> => {- const url = new URL("/api/commit", window.location.origin);- for (const [key, value] of Object.entries(queryParams)) {- url.searchParams.set(key, value);- }-- const result = await fetch(url, {- method: "GET",- headers: {- "X-Sourcebot-Client-Source": "sourcebot-web-client",- },- }).then(response => response.json());-- return result as CommitDetail | ServiceError;-}+export const getCommit = (queryParams: { repo: string; ref: string }) =>+ fetchJson<CommitDetail>("/api/commit", queryParams);

The same simplification applies to getDiff, getFolderContents, and getFileBlame. The undefined filter in the helper covers both required and optional param types uniformly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/api/`(client)/client.ts around lines 173 - 240, The four
API client functions repeat the same URL-building and fetch/json/header logic,
so extract that common flow into a shared helper in client.ts and have
getCommit, getDiff, getFolderContents, and getFileBlame delegate to it. Keep the
helper responsible for appending query params, setting the
X-Sourcebot-Client-Source header, performing fetch, and returning parsed JSON so
future header or request changes only need one update.
packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx (1)

42-57: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: extract the shared pending/error UI.

These pending and error blocks are duplicated verbatim in fullCommitDiffPanel.tsx (lines 25-40). Consider a small shared wrapper (e.g. <CommitDiffState isPending error>) to avoid divergence as the panels evolve.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/web/src/app/`(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx
around lines 42 - 57, The pending and error UI in focusedCommitDiffPanelClient
is duplicated in fullCommitDiffPanel, so extract the shared loading/error
rendering into a reusable component or wrapper (for example, a CommitDiffState
in the shared commit diff panel area) and use it from both panels. Keep the
existing behavior of the isPending and error/data checks, but centralize the
Loader2/loading text and error message markup so both
focusedCommitDiffPanelClient and fullCommitDiffPanel stay in sync as they
evolve.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In
`@packages/web/src/app/`(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx:
- Around line 42-57: The pending and error UI in focusedCommitDiffPanelClient is
duplicated in fullCommitDiffPanel, so extract the shared loading/error rendering
into a reusable component or wrapper (for example, a CommitDiffState in the
shared commit diff panel area) and use it from both panels. Keep the existing
behavior of the isPending and error/data checks, but centralize the
Loader2/loading text and error message markup so both
focusedCommitDiffPanelClient and fullCommitDiffPanel stay in sync as they
evolve.
In `@packages/web/src/app/api/`(client)/client.ts:
- Around line 173-240: The four API client functions repeat the same
URL-building and fetch/json/header logic, so extract that common flow into a
shared helper in client.ts and have getCommit, getDiff, getFolderContents, and
getFileBlame delegate to it. Keep the helper responsible for appending query
params, setting the X-Sourcebot-Client-Source header, performing fetch, and
returning parsed JSON so future header or request changes only need one update.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c02f0a2b-b454-4983-964c-cb35fa6d5583

📥 Commits

Reviewing files that changed from the base of the PR and between 94b2af6 and 80ee7fa.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanelClient.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/fullCommitDiffPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/useCommitDiff.ts
  • packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanelClient.tsx
  • packages/web/src/app/api/(client)/client.ts
  • packages/web/src/app/api/(server)/folder_contents/route.ts
  • packages/web/src/app/robots.ts

brendan-kellamand others added 2 commits July 7, 2026 20:00
Render the path header, separator, and file toolbar (blame toggle + age
legend) immediately from props, and scope the loading spinner / error to
the body below. The line-count/size stat shows a skeleton while the file
source is pending, then the real value once loaded.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@brendan-kellam
brendan-kellam merged commit 2afc265 into mainJul 8, 2026
10 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/browse-csr branch July 8, 2026 03:53
@github-actionsgithub-actionsBot mentioned this pull request Jul 8, 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): fetch /browse data client-side and disallow crawlers via robots.txt - #1426

Merged
brendan-kellam merged 5 commits into
mainfrom
brendan/browse-csr
Jul 8, 2026
Merged

feat(web): fetch /browse data client-side and disallow crawlers via robots.txt#1426
brendan-kellam merged 5 commits into
mainfrom
brendan/browse-csr

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Problem

app.sourcebot.dev was repeatedly killed by its liveness probe (6 restarts in 24h). Investigation traced the cause to /browse pages: blob/tree/commit panels fetched git data (file sources, folder contents, full commit diffs) in server components and passed it as props across the client-component boundary. React flight-encodes those props into the document on the main thread — a single large commit page measured 38MB of RSC payload (~8s of encoding), and a binary blob produced a 62MB document. Crawler traffic (GPTBot, Applebot, meta-externalagent, AhrefsBot — ~97% of all server processing time) walked the unbounded @<sha> URL space, stacking these renders until the event loop starved and health probes timed out.

Changes

  1. robots.txt (app/robots.ts): disallow all crawling, with an allowlist for link-preview fetchers (Slackbot, Twitterbot, LinkedInBot, Discordbot, facebookexternalhit, TelegramBot, WhatsApp) so shared links — e.g. chat pages — keep their OpenGraph unfurls. Applebot/GPTBot/meta-externalagent are deliberately not allowlisted (search/AI-training crawlers, not preview fetchers).

  2. Client-side data fetching for /browse panels: CodePreviewPanel, TreePreviewPanel, FullCommitDiffPanel, and FocusedCommitDiffPanel now render a shell and fetch via JSON API routes with react-query (per repo convention). Server components only fetch repoInfo for the path header.

    • New /api/folder_contents route (internal, not registered in the public API) wrapping the existing getFolderContents.
    • New client fetchers: getCommit, getDiff, getFolderContents, getFileBlame.
    • Shared useCommitDiff hook preserving the initial-commit empty-tree fallback.
    • CommitsPanel left server-side intentionally (bounded at 35 commits/page).

Documents drop to the ~70KB app shell regardless of content size, JSON serialization replaces flight encoding (~50-100x less main-thread time for large payloads), and JS-less crawlers never trigger data fetches at all.

Verification

Exercised in an isolated dev environment against an indexed repo: blob, tree, commit, blame-mode, and focused-diff pages all render correctly via client fetching; blob documents contain zero file content and commit documents zero hunk payloads (previously embedded); all API routes return 200; robots.txt serves the intended rules. Typecheck clean for all touched files.

Follow-ups (not in this PR)

  • Payload caps on the JSON APIs themselves (/api/diff on a mega-commit still returns MBs of JSON).
  • Liveness probe timeout bump + event-loop load shedding (infra/chart).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Browse pages now fetch file sources, folder contents, diffs, and blame in the browser via API routes for a smoother experience.
    • Commit and tree preview panels were updated to render client-side loading/error states and interactive UI.
  • Bug Fixes
    • Improved diff handling for initial commits (files show as added by default).
    • More reliable matching for file renames and deletions when viewing diffs.
  • Chores
    • Added a robots.txt route that blocks crawlers by default while allowing common link-preview bots to keep OpenGraph previews working.

brendan-kellamand others added 2 commits July 7, 2026 18:49
…w bots
Sourcebot exposes an unbounded URL space (every file x revision x commit)
and crawler traffic (GPTBot, Applebot, meta-externalagent, AhrefsBot)
generates heavy SSR load. Deny all crawling while keeping unfurl bots
(Slack, X, LinkedIn, etc.) allowed so shared links keep their OpenGraph
previews.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…C props
Blob, tree, and commit-diff panels previously fetched git data (file
source, folder contents, full commit diffs) in server components and
passed it as props across the client boundary. React flight-encodes
those props into the document at seconds of main-thread time for large
payloads (a large commit page measured 38MB / ~8s of encoding), which
starves the event loop and health probes under crawler load.
The panels now render a shell and fetch via existing JSON API routes
with react-query (per repo convention), plus a new /api/folder_contents
route. Documents drop to the ~70KB shell regardless of content size,
and JS-less crawlers never trigger the data fetch at all.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@coderabbitai

coderabbitaiBot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5786f4f6-5cd0-4eb5-a948-d2d82fccae7d

📥 Commits

Reviewing files that changed from the base of the PR and between 98aa8a7 and badd362.

📒 Files selected for processing (1)
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanelClient.tsx

Walkthrough

Browse preview and commit-diff panels now fetch only repository metadata on the server and load file, folder, commit, diff, and blame data in client components. New client API helpers, a folder contents API route, changelog notes, and a robots policy accompany the refactor.

Changes

Browse panel client-side refactor

Layer / File(s)Summary
Client API wrappers and folder_contents route
packages/web/src/app/api/(client)/client.ts, packages/web/src/app/api/(server)/folder_contents/route.ts
Adds getCommit, getDiff, getFolderContents, and getFileBlame client helpers, plus a validated GET route for folder contents.
Code preview panel client migration
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanelClient.tsx
CodePreviewPanel now fetches only repo info and delegates to CodePreviewPanelClient, which fetches file source and blame, computes file metadata, and renders preview and header controls.
Tree preview panel client migration
packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanelClient.tsx
TreePreviewPanel now fetches only repo info and delegates to TreePreviewPanelClient, which fetches folder contents and renders the tree header and items.
Commit diff panels client migration
packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx, packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/fullCommitDiffPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/useCommitDiff.ts
FocusedCommitDiffPanel and FullCommitDiffPanel now delegate commit and diff loading to useCommitDiff, and FocusedCommitDiffPanelClient renders the matching file diff, metadata, and viewer.
Changelog updates
CHANGELOG.md
Adds entries for the browse-page client-side fetch refactor and the robots route.

Estimated code review effort: 4 (Complex) | ~60 minutes

Robots.txt Crawler Policy

Layer / File(s)Summary
Robots configuration
packages/web/src/app/robots.ts
Introduces robots() returning MetadataRoute.Robots with an allowlist for preview bots and a catch-all disallow rule.

Sequence Diagram(s)

sequenceDiagram
participant CodePreviewPanel
participant CodePreviewPanelClient
participant ClientAPI
CodePreviewPanel->>ClientAPI: getRepoInfoByName(repoName)
CodePreviewPanel->>CodePreviewPanelClient: render with repo, path, revisionName
CodePreviewPanelClient->>ClientAPI: getFileSource(...)
CodePreviewPanelClient->>ClientAPI: getFileBlame(...)
ClientAPI-->>CodePreviewPanelClient: source, blame data
CodePreviewPanelClient-->>CodePreviewPanelClient: render PureCodePreviewPanel
Loading
sequenceDiagram
participant FocusedCommitDiffPanel
participant FocusedCommitDiffPanelClient
participant useCommitDiff
participant ClientAPI
FocusedCommitDiffPanel->>ClientAPI: getRepoInfoByName(repoName)
FocusedCommitDiffPanel->>FocusedCommitDiffPanelClient: render with repo, commitSha, path
FocusedCommitDiffPanelClient->>useCommitDiff: useCommitDiff(repoName, commitSha, path)
useCommitDiff->>ClientAPI: getCommit(...)
useCommitDiff->>ClientAPI: getDiff(...)
ClientAPI-->>useCommitDiff: commit, diff
useCommitDiff-->>FocusedCommitDiffPanelClient: commit, diff
FocusedCommitDiffPanelClient-->>FocusedCommitDiffPanelClient: render file header and LightweightDiffViewer
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly captures the two main changes: client-side /browse data fetching and crawler blocking via robots.txt.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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/browse-csr

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.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@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.

🧹 Nitpick comments (2)
packages/web/src/app/api/(client)/client.ts (1)

173-240: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Optional: Extract a shared fetch helper to reduce duplication.

The four new functions (getCommit, getDiff, getFolderContents, getFileBlame) share the same structure: URL construction, param iteration, fetch with the X-Sourcebot-Client-Source header, JSON parsing, and type casting. A small shared helper would centralize the pattern and make future additions or header changes a one-line edit.

♻️ Suggested shared helper
+async function fetchJson<T>(endpoint: string, queryParams: Record<string, string | undefined>): Promise<T | ServiceError> {+ const url = new URL(endpoint, window.location.origin);+ for (const [key, value] of Object.entries(queryParams)) {+ if (value !== undefined) {+ url.searchParams.set(key, value);+ }+ }+ const result = await fetch(url, {+ method: "GET",+ headers: {+ "X-Sourcebot-Client-Source": "sourcebot-web-client",+ },+ }).then(response => response.json());+ return result as T | ServiceError;+}+-export const getCommit = async (queryParams: { repo: string; ref: string }): Promise<CommitDetail | ServiceError> => {- const url = new URL("/api/commit", window.location.origin);- for (const [key, value] of Object.entries(queryParams)) {- url.searchParams.set(key, value);- }-- const result = await fetch(url, {- method: "GET",- headers: {- "X-Sourcebot-Client-Source": "sourcebot-web-client",- },- }).then(response => response.json());-- return result as CommitDetail | ServiceError;-}+export const getCommit = (queryParams: { repo: string; ref: string }) =>+ fetchJson<CommitDetail>("/api/commit", queryParams);

The same simplification applies to getDiff, getFolderContents, and getFileBlame. The undefined filter in the helper covers both required and optional param types uniformly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/api/`(client)/client.ts around lines 173 - 240, The four
API client functions repeat the same URL-building and fetch/json/header logic,
so extract that common flow into a shared helper in client.ts and have
getCommit, getDiff, getFolderContents, and getFileBlame delegate to it. Keep the
helper responsible for appending query params, setting the
X-Sourcebot-Client-Source header, performing fetch, and returning parsed JSON so
future header or request changes only need one update.
packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx (1)

42-57: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: extract the shared pending/error UI.

These pending and error blocks are duplicated verbatim in fullCommitDiffPanel.tsx (lines 25-40). Consider a small shared wrapper (e.g. <CommitDiffState isPending error>) to avoid divergence as the panels evolve.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/web/src/app/`(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx
around lines 42 - 57, The pending and error UI in focusedCommitDiffPanelClient
is duplicated in fullCommitDiffPanel, so extract the shared loading/error
rendering into a reusable component or wrapper (for example, a CommitDiffState
in the shared commit diff panel area) and use it from both panels. Keep the
existing behavior of the isPending and error/data checks, but centralize the
Loader2/loading text and error message markup so both
focusedCommitDiffPanelClient and fullCommitDiffPanel stay in sync as they
evolve.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In
`@packages/web/src/app/`(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx:
- Around line 42-57: The pending and error UI in focusedCommitDiffPanelClient is
duplicated in fullCommitDiffPanel, so extract the shared loading/error rendering
into a reusable component or wrapper (for example, a CommitDiffState in the
shared commit diff panel area) and use it from both panels. Keep the existing
behavior of the isPending and error/data checks, but centralize the
Loader2/loading text and error message markup so both
focusedCommitDiffPanelClient and fullCommitDiffPanel stay in sync as they
evolve.
In `@packages/web/src/app/api/`(client)/client.ts:
- Around line 173-240: The four API client functions repeat the same
URL-building and fetch/json/header logic, so extract that common flow into a
shared helper in client.ts and have getCommit, getDiff, getFolderContents, and
getFileBlame delegate to it. Keep the helper responsible for appending query
params, setting the X-Sourcebot-Client-Source header, performing fetch, and
returning parsed JSON so future header or request changes only need one update.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c02f0a2b-b454-4983-964c-cb35fa6d5583

📥 Commits

Reviewing files that changed from the base of the PR and between 94b2af6 and 80ee7fa.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanelClient.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/fullCommitDiffPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/useCommitDiff.ts
  • packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanelClient.tsx
  • packages/web/src/app/api/(client)/client.ts
  • packages/web/src/app/api/(server)/folder_contents/route.ts
  • packages/web/src/app/robots.ts

brendan-kellamand others added 2 commits July 7, 2026 20:00
Render the path header, separator, and file toolbar (blame toggle + age
legend) immediately from props, and scope the loading spinner / error to
the body below. The line-count/size stat shows a skeleton while the file
source is pending, then the real value once loaded.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@brendan-kellam
brendan-kellam merged commit 2afc265 into mainJul 8, 2026
10 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/browse-csr branch July 8, 2026 03:53
@github-actionsgithub-actionsBot mentioned this pull request Jul 8, 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): fetch /browse data client-side and disallow crawlers via robots.txt - #1426

Merged
brendan-kellam merged 5 commits into
mainfrom
brendan/browse-csr
Jul 8, 2026
Merged

feat(web): fetch /browse data client-side and disallow crawlers via robots.txt#1426
brendan-kellam merged 5 commits into
mainfrom
brendan/browse-csr

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Problem

app.sourcebot.dev was repeatedly killed by its liveness probe (6 restarts in 24h). Investigation traced the cause to /browse pages: blob/tree/commit panels fetched git data (file sources, folder contents, full commit diffs) in server components and passed it as props across the client-component boundary. React flight-encodes those props into the document on the main thread — a single large commit page measured 38MB of RSC payload (~8s of encoding), and a binary blob produced a 62MB document. Crawler traffic (GPTBot, Applebot, meta-externalagent, AhrefsBot — ~97% of all server processing time) walked the unbounded @<sha> URL space, stacking these renders until the event loop starved and health probes timed out.

Changes

  1. robots.txt (app/robots.ts): disallow all crawling, with an allowlist for link-preview fetchers (Slackbot, Twitterbot, LinkedInBot, Discordbot, facebookexternalhit, TelegramBot, WhatsApp) so shared links — e.g. chat pages — keep their OpenGraph unfurls. Applebot/GPTBot/meta-externalagent are deliberately not allowlisted (search/AI-training crawlers, not preview fetchers).

  2. Client-side data fetching for /browse panels: CodePreviewPanel, TreePreviewPanel, FullCommitDiffPanel, and FocusedCommitDiffPanel now render a shell and fetch via JSON API routes with react-query (per repo convention). Server components only fetch repoInfo for the path header.

    • New /api/folder_contents route (internal, not registered in the public API) wrapping the existing getFolderContents.
    • New client fetchers: getCommit, getDiff, getFolderContents, getFileBlame.
    • Shared useCommitDiff hook preserving the initial-commit empty-tree fallback.
    • CommitsPanel left server-side intentionally (bounded at 35 commits/page).

Documents drop to the ~70KB app shell regardless of content size, JSON serialization replaces flight encoding (~50-100x less main-thread time for large payloads), and JS-less crawlers never trigger data fetches at all.

Verification

Exercised in an isolated dev environment against an indexed repo: blob, tree, commit, blame-mode, and focused-diff pages all render correctly via client fetching; blob documents contain zero file content and commit documents zero hunk payloads (previously embedded); all API routes return 200; robots.txt serves the intended rules. Typecheck clean for all touched files.

Follow-ups (not in this PR)

  • Payload caps on the JSON APIs themselves (/api/diff on a mega-commit still returns MBs of JSON).
  • Liveness probe timeout bump + event-loop load shedding (infra/chart).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Browse pages now fetch file sources, folder contents, diffs, and blame in the browser via API routes for a smoother experience.
    • Commit and tree preview panels were updated to render client-side loading/error states and interactive UI.
  • Bug Fixes
    • Improved diff handling for initial commits (files show as added by default).
    • More reliable matching for file renames and deletions when viewing diffs.
  • Chores
    • Added a robots.txt route that blocks crawlers by default while allowing common link-preview bots to keep OpenGraph previews working.

brendan-kellamand others added 2 commits July 7, 2026 18:49
…w bots
Sourcebot exposes an unbounded URL space (every file x revision x commit)
and crawler traffic (GPTBot, Applebot, meta-externalagent, AhrefsBot)
generates heavy SSR load. Deny all crawling while keeping unfurl bots
(Slack, X, LinkedIn, etc.) allowed so shared links keep their OpenGraph
previews.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…C props
Blob, tree, and commit-diff panels previously fetched git data (file
source, folder contents, full commit diffs) in server components and
passed it as props across the client boundary. React flight-encodes
those props into the document at seconds of main-thread time for large
payloads (a large commit page measured 38MB / ~8s of encoding), which
starves the event loop and health probes under crawler load.
The panels now render a shell and fetch via existing JSON API routes
with react-query (per repo convention), plus a new /api/folder_contents
route. Documents drop to the ~70KB shell regardless of content size,
and JS-less crawlers never trigger the data fetch at all.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@coderabbitai

coderabbitaiBot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5786f4f6-5cd0-4eb5-a948-d2d82fccae7d

📥 Commits

Reviewing files that changed from the base of the PR and between 98aa8a7 and badd362.

📒 Files selected for processing (1)
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanelClient.tsx

Walkthrough

Browse preview and commit-diff panels now fetch only repository metadata on the server and load file, folder, commit, diff, and blame data in client components. New client API helpers, a folder contents API route, changelog notes, and a robots policy accompany the refactor.

Changes

Browse panel client-side refactor

Layer / File(s)Summary
Client API wrappers and folder_contents route
packages/web/src/app/api/(client)/client.ts, packages/web/src/app/api/(server)/folder_contents/route.ts
Adds getCommit, getDiff, getFolderContents, and getFileBlame client helpers, plus a validated GET route for folder contents.
Code preview panel client migration
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanelClient.tsx
CodePreviewPanel now fetches only repo info and delegates to CodePreviewPanelClient, which fetches file source and blame, computes file metadata, and renders preview and header controls.
Tree preview panel client migration
packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanelClient.tsx
TreePreviewPanel now fetches only repo info and delegates to TreePreviewPanelClient, which fetches folder contents and renders the tree header and items.
Commit diff panels client migration
packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx, packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/fullCommitDiffPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/useCommitDiff.ts
FocusedCommitDiffPanel and FullCommitDiffPanel now delegate commit and diff loading to useCommitDiff, and FocusedCommitDiffPanelClient renders the matching file diff, metadata, and viewer.
Changelog updates
CHANGELOG.md
Adds entries for the browse-page client-side fetch refactor and the robots route.

Estimated code review effort: 4 (Complex) | ~60 minutes

Robots.txt Crawler Policy

Layer / File(s)Summary
Robots configuration
packages/web/src/app/robots.ts
Introduces robots() returning MetadataRoute.Robots with an allowlist for preview bots and a catch-all disallow rule.

Sequence Diagram(s)

sequenceDiagram
participant CodePreviewPanel
participant CodePreviewPanelClient
participant ClientAPI
CodePreviewPanel->>ClientAPI: getRepoInfoByName(repoName)
CodePreviewPanel->>CodePreviewPanelClient: render with repo, path, revisionName
CodePreviewPanelClient->>ClientAPI: getFileSource(...)
CodePreviewPanelClient->>ClientAPI: getFileBlame(...)
ClientAPI-->>CodePreviewPanelClient: source, blame data
CodePreviewPanelClient-->>CodePreviewPanelClient: render PureCodePreviewPanel
Loading
sequenceDiagram
participant FocusedCommitDiffPanel
participant FocusedCommitDiffPanelClient
participant useCommitDiff
participant ClientAPI
FocusedCommitDiffPanel->>ClientAPI: getRepoInfoByName(repoName)
FocusedCommitDiffPanel->>FocusedCommitDiffPanelClient: render with repo, commitSha, path
FocusedCommitDiffPanelClient->>useCommitDiff: useCommitDiff(repoName, commitSha, path)
useCommitDiff->>ClientAPI: getCommit(...)
useCommitDiff->>ClientAPI: getDiff(...)
ClientAPI-->>useCommitDiff: commit, diff
useCommitDiff-->>FocusedCommitDiffPanelClient: commit, diff
FocusedCommitDiffPanelClient-->>FocusedCommitDiffPanelClient: render file header and LightweightDiffViewer
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly captures the two main changes: client-side /browse data fetching and crawler blocking via robots.txt.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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/browse-csr

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.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@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.

🧹 Nitpick comments (2)
packages/web/src/app/api/(client)/client.ts (1)

173-240: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Optional: Extract a shared fetch helper to reduce duplication.

The four new functions (getCommit, getDiff, getFolderContents, getFileBlame) share the same structure: URL construction, param iteration, fetch with the X-Sourcebot-Client-Source header, JSON parsing, and type casting. A small shared helper would centralize the pattern and make future additions or header changes a one-line edit.

♻️ Suggested shared helper
+async function fetchJson<T>(endpoint: string, queryParams: Record<string, string | undefined>): Promise<T | ServiceError> {+ const url = new URL(endpoint, window.location.origin);+ for (const [key, value] of Object.entries(queryParams)) {+ if (value !== undefined) {+ url.searchParams.set(key, value);+ }+ }+ const result = await fetch(url, {+ method: "GET",+ headers: {+ "X-Sourcebot-Client-Source": "sourcebot-web-client",+ },+ }).then(response => response.json());+ return result as T | ServiceError;+}+-export const getCommit = async (queryParams: { repo: string; ref: string }): Promise<CommitDetail | ServiceError> => {- const url = new URL("/api/commit", window.location.origin);- for (const [key, value] of Object.entries(queryParams)) {- url.searchParams.set(key, value);- }-- const result = await fetch(url, {- method: "GET",- headers: {- "X-Sourcebot-Client-Source": "sourcebot-web-client",- },- }).then(response => response.json());-- return result as CommitDetail | ServiceError;-}+export const getCommit = (queryParams: { repo: string; ref: string }) =>+ fetchJson<CommitDetail>("/api/commit", queryParams);

The same simplification applies to getDiff, getFolderContents, and getFileBlame. The undefined filter in the helper covers both required and optional param types uniformly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/api/`(client)/client.ts around lines 173 - 240, The four
API client functions repeat the same URL-building and fetch/json/header logic,
so extract that common flow into a shared helper in client.ts and have
getCommit, getDiff, getFolderContents, and getFileBlame delegate to it. Keep the
helper responsible for appending query params, setting the
X-Sourcebot-Client-Source header, performing fetch, and returning parsed JSON so
future header or request changes only need one update.
packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx (1)

42-57: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: extract the shared pending/error UI.

These pending and error blocks are duplicated verbatim in fullCommitDiffPanel.tsx (lines 25-40). Consider a small shared wrapper (e.g. <CommitDiffState isPending error>) to avoid divergence as the panels evolve.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/web/src/app/`(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx
around lines 42 - 57, The pending and error UI in focusedCommitDiffPanelClient
is duplicated in fullCommitDiffPanel, so extract the shared loading/error
rendering into a reusable component or wrapper (for example, a CommitDiffState
in the shared commit diff panel area) and use it from both panels. Keep the
existing behavior of the isPending and error/data checks, but centralize the
Loader2/loading text and error message markup so both
focusedCommitDiffPanelClient and fullCommitDiffPanel stay in sync as they
evolve.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In
`@packages/web/src/app/`(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx:
- Around line 42-57: The pending and error UI in focusedCommitDiffPanelClient is
duplicated in fullCommitDiffPanel, so extract the shared loading/error rendering
into a reusable component or wrapper (for example, a CommitDiffState in the
shared commit diff panel area) and use it from both panels. Keep the existing
behavior of the isPending and error/data checks, but centralize the
Loader2/loading text and error message markup so both
focusedCommitDiffPanelClient and fullCommitDiffPanel stay in sync as they
evolve.
In `@packages/web/src/app/api/`(client)/client.ts:
- Around line 173-240: The four API client functions repeat the same
URL-building and fetch/json/header logic, so extract that common flow into a
shared helper in client.ts and have getCommit, getDiff, getFolderContents, and
getFileBlame delegate to it. Keep the helper responsible for appending query
params, setting the X-Sourcebot-Client-Source header, performing fetch, and
returning parsed JSON so future header or request changes only need one update.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c02f0a2b-b454-4983-964c-cb35fa6d5583

📥 Commits

Reviewing files that changed from the base of the PR and between 94b2af6 and 80ee7fa.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanelClient.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/fullCommitDiffPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/useCommitDiff.ts
  • packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanelClient.tsx
  • packages/web/src/app/api/(client)/client.ts
  • packages/web/src/app/api/(server)/folder_contents/route.ts
  • packages/web/src/app/robots.ts

brendan-kellamand others added 2 commits July 7, 2026 20:00
Render the path header, separator, and file toolbar (blame toggle + age
legend) immediately from props, and scope the loading spinner / error to
the body below. The line-count/size stat shows a skeleton while the file
source is pending, then the real value once loaded.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@brendan-kellam
brendan-kellam merged commit 2afc265 into mainJul 8, 2026
10 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/browse-csr branch July 8, 2026 03:53
@github-actionsgithub-actionsBot mentioned this pull request Jul 8, 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): fetch /browse data client-side and disallow crawlers via robots.txt - #1426

Merged
brendan-kellam merged 5 commits into
mainfrom
brendan/browse-csr
Jul 8, 2026
Merged

feat(web): fetch /browse data client-side and disallow crawlers via robots.txt#1426
brendan-kellam merged 5 commits into
mainfrom
brendan/browse-csr

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Problem

app.sourcebot.dev was repeatedly killed by its liveness probe (6 restarts in 24h). Investigation traced the cause to /browse pages: blob/tree/commit panels fetched git data (file sources, folder contents, full commit diffs) in server components and passed it as props across the client-component boundary. React flight-encodes those props into the document on the main thread — a single large commit page measured 38MB of RSC payload (~8s of encoding), and a binary blob produced a 62MB document. Crawler traffic (GPTBot, Applebot, meta-externalagent, AhrefsBot — ~97% of all server processing time) walked the unbounded @<sha> URL space, stacking these renders until the event loop starved and health probes timed out.

Changes

  1. robots.txt (app/robots.ts): disallow all crawling, with an allowlist for link-preview fetchers (Slackbot, Twitterbot, LinkedInBot, Discordbot, facebookexternalhit, TelegramBot, WhatsApp) so shared links — e.g. chat pages — keep their OpenGraph unfurls. Applebot/GPTBot/meta-externalagent are deliberately not allowlisted (search/AI-training crawlers, not preview fetchers).

  2. Client-side data fetching for /browse panels: CodePreviewPanel, TreePreviewPanel, FullCommitDiffPanel, and FocusedCommitDiffPanel now render a shell and fetch via JSON API routes with react-query (per repo convention). Server components only fetch repoInfo for the path header.

    • New /api/folder_contents route (internal, not registered in the public API) wrapping the existing getFolderContents.
    • New client fetchers: getCommit, getDiff, getFolderContents, getFileBlame.
    • Shared useCommitDiff hook preserving the initial-commit empty-tree fallback.
    • CommitsPanel left server-side intentionally (bounded at 35 commits/page).

Documents drop to the ~70KB app shell regardless of content size, JSON serialization replaces flight encoding (~50-100x less main-thread time for large payloads), and JS-less crawlers never trigger data fetches at all.

Verification

Exercised in an isolated dev environment against an indexed repo: blob, tree, commit, blame-mode, and focused-diff pages all render correctly via client fetching; blob documents contain zero file content and commit documents zero hunk payloads (previously embedded); all API routes return 200; robots.txt serves the intended rules. Typecheck clean for all touched files.

Follow-ups (not in this PR)

  • Payload caps on the JSON APIs themselves (/api/diff on a mega-commit still returns MBs of JSON).
  • Liveness probe timeout bump + event-loop load shedding (infra/chart).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Browse pages now fetch file sources, folder contents, diffs, and blame in the browser via API routes for a smoother experience.
    • Commit and tree preview panels were updated to render client-side loading/error states and interactive UI.
  • Bug Fixes
    • Improved diff handling for initial commits (files show as added by default).
    • More reliable matching for file renames and deletions when viewing diffs.
  • Chores
    • Added a robots.txt route that blocks crawlers by default while allowing common link-preview bots to keep OpenGraph previews working.

brendan-kellamand others added 2 commits July 7, 2026 18:49
…w bots
Sourcebot exposes an unbounded URL space (every file x revision x commit)
and crawler traffic (GPTBot, Applebot, meta-externalagent, AhrefsBot)
generates heavy SSR load. Deny all crawling while keeping unfurl bots
(Slack, X, LinkedIn, etc.) allowed so shared links keep their OpenGraph
previews.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…C props
Blob, tree, and commit-diff panels previously fetched git data (file
source, folder contents, full commit diffs) in server components and
passed it as props across the client boundary. React flight-encodes
those props into the document at seconds of main-thread time for large
payloads (a large commit page measured 38MB / ~8s of encoding), which
starves the event loop and health probes under crawler load.
The panels now render a shell and fetch via existing JSON API routes
with react-query (per repo convention), plus a new /api/folder_contents
route. Documents drop to the ~70KB shell regardless of content size,
and JS-less crawlers never trigger the data fetch at all.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@coderabbitai

coderabbitaiBot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5786f4f6-5cd0-4eb5-a948-d2d82fccae7d

📥 Commits

Reviewing files that changed from the base of the PR and between 98aa8a7 and badd362.

📒 Files selected for processing (1)
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanelClient.tsx

Walkthrough

Browse preview and commit-diff panels now fetch only repository metadata on the server and load file, folder, commit, diff, and blame data in client components. New client API helpers, a folder contents API route, changelog notes, and a robots policy accompany the refactor.

Changes

Browse panel client-side refactor

Layer / File(s)Summary
Client API wrappers and folder_contents route
packages/web/src/app/api/(client)/client.ts, packages/web/src/app/api/(server)/folder_contents/route.ts
Adds getCommit, getDiff, getFolderContents, and getFileBlame client helpers, plus a validated GET route for folder contents.
Code preview panel client migration
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanelClient.tsx
CodePreviewPanel now fetches only repo info and delegates to CodePreviewPanelClient, which fetches file source and blame, computes file metadata, and renders preview and header controls.
Tree preview panel client migration
packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanelClient.tsx
TreePreviewPanel now fetches only repo info and delegates to TreePreviewPanelClient, which fetches folder contents and renders the tree header and items.
Commit diff panels client migration
packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx, packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/fullCommitDiffPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/useCommitDiff.ts
FocusedCommitDiffPanel and FullCommitDiffPanel now delegate commit and diff loading to useCommitDiff, and FocusedCommitDiffPanelClient renders the matching file diff, metadata, and viewer.
Changelog updates
CHANGELOG.md
Adds entries for the browse-page client-side fetch refactor and the robots route.

Estimated code review effort: 4 (Complex) | ~60 minutes

Robots.txt Crawler Policy

Layer / File(s)Summary
Robots configuration
packages/web/src/app/robots.ts
Introduces robots() returning MetadataRoute.Robots with an allowlist for preview bots and a catch-all disallow rule.

Sequence Diagram(s)

sequenceDiagram
participant CodePreviewPanel
participant CodePreviewPanelClient
participant ClientAPI
CodePreviewPanel->>ClientAPI: getRepoInfoByName(repoName)
CodePreviewPanel->>CodePreviewPanelClient: render with repo, path, revisionName
CodePreviewPanelClient->>ClientAPI: getFileSource(...)
CodePreviewPanelClient->>ClientAPI: getFileBlame(...)
ClientAPI-->>CodePreviewPanelClient: source, blame data
CodePreviewPanelClient-->>CodePreviewPanelClient: render PureCodePreviewPanel
Loading
sequenceDiagram
participant FocusedCommitDiffPanel
participant FocusedCommitDiffPanelClient
participant useCommitDiff
participant ClientAPI
FocusedCommitDiffPanel->>ClientAPI: getRepoInfoByName(repoName)
FocusedCommitDiffPanel->>FocusedCommitDiffPanelClient: render with repo, commitSha, path
FocusedCommitDiffPanelClient->>useCommitDiff: useCommitDiff(repoName, commitSha, path)
useCommitDiff->>ClientAPI: getCommit(...)
useCommitDiff->>ClientAPI: getDiff(...)
ClientAPI-->>useCommitDiff: commit, diff
useCommitDiff-->>FocusedCommitDiffPanelClient: commit, diff
FocusedCommitDiffPanelClient-->>FocusedCommitDiffPanelClient: render file header and LightweightDiffViewer
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly captures the two main changes: client-side /browse data fetching and crawler blocking via robots.txt.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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/browse-csr

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.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@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.

🧹 Nitpick comments (2)
packages/web/src/app/api/(client)/client.ts (1)

173-240: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Optional: Extract a shared fetch helper to reduce duplication.

The four new functions (getCommit, getDiff, getFolderContents, getFileBlame) share the same structure: URL construction, param iteration, fetch with the X-Sourcebot-Client-Source header, JSON parsing, and type casting. A small shared helper would centralize the pattern and make future additions or header changes a one-line edit.

♻️ Suggested shared helper
+async function fetchJson<T>(endpoint: string, queryParams: Record<string, string | undefined>): Promise<T | ServiceError> {+ const url = new URL(endpoint, window.location.origin);+ for (const [key, value] of Object.entries(queryParams)) {+ if (value !== undefined) {+ url.searchParams.set(key, value);+ }+ }+ const result = await fetch(url, {+ method: "GET",+ headers: {+ "X-Sourcebot-Client-Source": "sourcebot-web-client",+ },+ }).then(response => response.json());+ return result as T | ServiceError;+}+-export const getCommit = async (queryParams: { repo: string; ref: string }): Promise<CommitDetail | ServiceError> => {- const url = new URL("/api/commit", window.location.origin);- for (const [key, value] of Object.entries(queryParams)) {- url.searchParams.set(key, value);- }-- const result = await fetch(url, {- method: "GET",- headers: {- "X-Sourcebot-Client-Source": "sourcebot-web-client",- },- }).then(response => response.json());-- return result as CommitDetail | ServiceError;-}+export const getCommit = (queryParams: { repo: string; ref: string }) =>+ fetchJson<CommitDetail>("/api/commit", queryParams);

The same simplification applies to getDiff, getFolderContents, and getFileBlame. The undefined filter in the helper covers both required and optional param types uniformly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/api/`(client)/client.ts around lines 173 - 240, The four
API client functions repeat the same URL-building and fetch/json/header logic,
so extract that common flow into a shared helper in client.ts and have
getCommit, getDiff, getFolderContents, and getFileBlame delegate to it. Keep the
helper responsible for appending query params, setting the
X-Sourcebot-Client-Source header, performing fetch, and returning parsed JSON so
future header or request changes only need one update.
packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx (1)

42-57: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: extract the shared pending/error UI.

These pending and error blocks are duplicated verbatim in fullCommitDiffPanel.tsx (lines 25-40). Consider a small shared wrapper (e.g. <CommitDiffState isPending error>) to avoid divergence as the panels evolve.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/web/src/app/`(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx
around lines 42 - 57, The pending and error UI in focusedCommitDiffPanelClient
is duplicated in fullCommitDiffPanel, so extract the shared loading/error
rendering into a reusable component or wrapper (for example, a CommitDiffState
in the shared commit diff panel area) and use it from both panels. Keep the
existing behavior of the isPending and error/data checks, but centralize the
Loader2/loading text and error message markup so both
focusedCommitDiffPanelClient and fullCommitDiffPanel stay in sync as they
evolve.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In
`@packages/web/src/app/`(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx:
- Around line 42-57: The pending and error UI in focusedCommitDiffPanelClient is
duplicated in fullCommitDiffPanel, so extract the shared loading/error rendering
into a reusable component or wrapper (for example, a CommitDiffState in the
shared commit diff panel area) and use it from both panels. Keep the existing
behavior of the isPending and error/data checks, but centralize the
Loader2/loading text and error message markup so both
focusedCommitDiffPanelClient and fullCommitDiffPanel stay in sync as they
evolve.
In `@packages/web/src/app/api/`(client)/client.ts:
- Around line 173-240: The four API client functions repeat the same
URL-building and fetch/json/header logic, so extract that common flow into a
shared helper in client.ts and have getCommit, getDiff, getFolderContents, and
getFileBlame delegate to it. Keep the helper responsible for appending query
params, setting the X-Sourcebot-Client-Source header, performing fetch, and
returning parsed JSON so future header or request changes only need one update.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c02f0a2b-b454-4983-964c-cb35fa6d5583

📥 Commits

Reviewing files that changed from the base of the PR and between 94b2af6 and 80ee7fa.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanelClient.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/fullCommitDiffPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/useCommitDiff.ts
  • packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanelClient.tsx
  • packages/web/src/app/api/(client)/client.ts
  • packages/web/src/app/api/(server)/folder_contents/route.ts
  • packages/web/src/app/robots.ts

brendan-kellamand others added 2 commits July 7, 2026 20:00
Render the path header, separator, and file toolbar (blame toggle + age
legend) immediately from props, and scope the loading spinner / error to
the body below. The line-count/size stat shows a skeleton while the file
source is pending, then the real value once loaded.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@brendan-kellam
brendan-kellam merged commit 2afc265 into mainJul 8, 2026
10 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/browse-csr branch July 8, 2026 03:53
@github-actionsgithub-actionsBot mentioned this pull request Jul 8, 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): fetch /browse data client-side and disallow crawlers via robots.txt - #1426

Merged
brendan-kellam merged 5 commits into
mainfrom
brendan/browse-csr
Jul 8, 2026
Merged

feat(web): fetch /browse data client-side and disallow crawlers via robots.txt#1426
brendan-kellam merged 5 commits into
mainfrom
brendan/browse-csr

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Problem

app.sourcebot.dev was repeatedly killed by its liveness probe (6 restarts in 24h). Investigation traced the cause to /browse pages: blob/tree/commit panels fetched git data (file sources, folder contents, full commit diffs) in server components and passed it as props across the client-component boundary. React flight-encodes those props into the document on the main thread — a single large commit page measured 38MB of RSC payload (~8s of encoding), and a binary blob produced a 62MB document. Crawler traffic (GPTBot, Applebot, meta-externalagent, AhrefsBot — ~97% of all server processing time) walked the unbounded @<sha> URL space, stacking these renders until the event loop starved and health probes timed out.

Changes

  1. robots.txt (app/robots.ts): disallow all crawling, with an allowlist for link-preview fetchers (Slackbot, Twitterbot, LinkedInBot, Discordbot, facebookexternalhit, TelegramBot, WhatsApp) so shared links — e.g. chat pages — keep their OpenGraph unfurls. Applebot/GPTBot/meta-externalagent are deliberately not allowlisted (search/AI-training crawlers, not preview fetchers).

  2. Client-side data fetching for /browse panels: CodePreviewPanel, TreePreviewPanel, FullCommitDiffPanel, and FocusedCommitDiffPanel now render a shell and fetch via JSON API routes with react-query (per repo convention). Server components only fetch repoInfo for the path header.

    • New /api/folder_contents route (internal, not registered in the public API) wrapping the existing getFolderContents.
    • New client fetchers: getCommit, getDiff, getFolderContents, getFileBlame.
    • Shared useCommitDiff hook preserving the initial-commit empty-tree fallback.
    • CommitsPanel left server-side intentionally (bounded at 35 commits/page).

Documents drop to the ~70KB app shell regardless of content size, JSON serialization replaces flight encoding (~50-100x less main-thread time for large payloads), and JS-less crawlers never trigger data fetches at all.

Verification

Exercised in an isolated dev environment against an indexed repo: blob, tree, commit, blame-mode, and focused-diff pages all render correctly via client fetching; blob documents contain zero file content and commit documents zero hunk payloads (previously embedded); all API routes return 200; robots.txt serves the intended rules. Typecheck clean for all touched files.

Follow-ups (not in this PR)

  • Payload caps on the JSON APIs themselves (/api/diff on a mega-commit still returns MBs of JSON).
  • Liveness probe timeout bump + event-loop load shedding (infra/chart).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Browse pages now fetch file sources, folder contents, diffs, and blame in the browser via API routes for a smoother experience.
    • Commit and tree preview panels were updated to render client-side loading/error states and interactive UI.
  • Bug Fixes
    • Improved diff handling for initial commits (files show as added by default).
    • More reliable matching for file renames and deletions when viewing diffs.
  • Chores
    • Added a robots.txt route that blocks crawlers by default while allowing common link-preview bots to keep OpenGraph previews working.

brendan-kellamand others added 2 commits July 7, 2026 18:49
…w bots
Sourcebot exposes an unbounded URL space (every file x revision x commit)
and crawler traffic (GPTBot, Applebot, meta-externalagent, AhrefsBot)
generates heavy SSR load. Deny all crawling while keeping unfurl bots
(Slack, X, LinkedIn, etc.) allowed so shared links keep their OpenGraph
previews.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…C props
Blob, tree, and commit-diff panels previously fetched git data (file
source, folder contents, full commit diffs) in server components and
passed it as props across the client boundary. React flight-encodes
those props into the document at seconds of main-thread time for large
payloads (a large commit page measured 38MB / ~8s of encoding), which
starves the event loop and health probes under crawler load.
The panels now render a shell and fetch via existing JSON API routes
with react-query (per repo convention), plus a new /api/folder_contents
route. Documents drop to the ~70KB shell regardless of content size,
and JS-less crawlers never trigger the data fetch at all.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@coderabbitai

coderabbitaiBot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5786f4f6-5cd0-4eb5-a948-d2d82fccae7d

📥 Commits

Reviewing files that changed from the base of the PR and between 98aa8a7 and badd362.

📒 Files selected for processing (1)
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanelClient.tsx

Walkthrough

Browse preview and commit-diff panels now fetch only repository metadata on the server and load file, folder, commit, diff, and blame data in client components. New client API helpers, a folder contents API route, changelog notes, and a robots policy accompany the refactor.

Changes

Browse panel client-side refactor

Layer / File(s)Summary
Client API wrappers and folder_contents route
packages/web/src/app/api/(client)/client.ts, packages/web/src/app/api/(server)/folder_contents/route.ts
Adds getCommit, getDiff, getFolderContents, and getFileBlame client helpers, plus a validated GET route for folder contents.
Code preview panel client migration
packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanelClient.tsx
CodePreviewPanel now fetches only repo info and delegates to CodePreviewPanelClient, which fetches file source and blame, computes file metadata, and renders preview and header controls.
Tree preview panel client migration
packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanelClient.tsx
TreePreviewPanel now fetches only repo info and delegates to TreePreviewPanelClient, which fetches folder contents and renders the tree header and items.
Commit diff panels client migration
packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx, packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/fullCommitDiffPanel.tsx, packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/useCommitDiff.ts
FocusedCommitDiffPanel and FullCommitDiffPanel now delegate commit and diff loading to useCommitDiff, and FocusedCommitDiffPanelClient renders the matching file diff, metadata, and viewer.
Changelog updates
CHANGELOG.md
Adds entries for the browse-page client-side fetch refactor and the robots route.

Estimated code review effort: 4 (Complex) | ~60 minutes

Robots.txt Crawler Policy

Layer / File(s)Summary
Robots configuration
packages/web/src/app/robots.ts
Introduces robots() returning MetadataRoute.Robots with an allowlist for preview bots and a catch-all disallow rule.

Sequence Diagram(s)

sequenceDiagram
participant CodePreviewPanel
participant CodePreviewPanelClient
participant ClientAPI
CodePreviewPanel->>ClientAPI: getRepoInfoByName(repoName)
CodePreviewPanel->>CodePreviewPanelClient: render with repo, path, revisionName
CodePreviewPanelClient->>ClientAPI: getFileSource(...)
CodePreviewPanelClient->>ClientAPI: getFileBlame(...)
ClientAPI-->>CodePreviewPanelClient: source, blame data
CodePreviewPanelClient-->>CodePreviewPanelClient: render PureCodePreviewPanel
Loading
sequenceDiagram
participant FocusedCommitDiffPanel
participant FocusedCommitDiffPanelClient
participant useCommitDiff
participant ClientAPI
FocusedCommitDiffPanel->>ClientAPI: getRepoInfoByName(repoName)
FocusedCommitDiffPanel->>FocusedCommitDiffPanelClient: render with repo, commitSha, path
FocusedCommitDiffPanelClient->>useCommitDiff: useCommitDiff(repoName, commitSha, path)
useCommitDiff->>ClientAPI: getCommit(...)
useCommitDiff->>ClientAPI: getDiff(...)
ClientAPI-->>useCommitDiff: commit, diff
useCommitDiff-->>FocusedCommitDiffPanelClient: commit, diff
FocusedCommitDiffPanelClient-->>FocusedCommitDiffPanelClient: render file header and LightweightDiffViewer
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly captures the two main changes: client-side /browse data fetching and crawler blocking via robots.txt.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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/browse-csr

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.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@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.

🧹 Nitpick comments (2)
packages/web/src/app/api/(client)/client.ts (1)

173-240: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Optional: Extract a shared fetch helper to reduce duplication.

The four new functions (getCommit, getDiff, getFolderContents, getFileBlame) share the same structure: URL construction, param iteration, fetch with the X-Sourcebot-Client-Source header, JSON parsing, and type casting. A small shared helper would centralize the pattern and make future additions or header changes a one-line edit.

♻️ Suggested shared helper
+async function fetchJson<T>(endpoint: string, queryParams: Record<string, string | undefined>): Promise<T | ServiceError> {+ const url = new URL(endpoint, window.location.origin);+ for (const [key, value] of Object.entries(queryParams)) {+ if (value !== undefined) {+ url.searchParams.set(key, value);+ }+ }+ const result = await fetch(url, {+ method: "GET",+ headers: {+ "X-Sourcebot-Client-Source": "sourcebot-web-client",+ },+ }).then(response => response.json());+ return result as T | ServiceError;+}+-export const getCommit = async (queryParams: { repo: string; ref: string }): Promise<CommitDetail | ServiceError> => {- const url = new URL("/api/commit", window.location.origin);- for (const [key, value] of Object.entries(queryParams)) {- url.searchParams.set(key, value);- }-- const result = await fetch(url, {- method: "GET",- headers: {- "X-Sourcebot-Client-Source": "sourcebot-web-client",- },- }).then(response => response.json());-- return result as CommitDetail | ServiceError;-}+export const getCommit = (queryParams: { repo: string; ref: string }) =>+ fetchJson<CommitDetail>("/api/commit", queryParams);

The same simplification applies to getDiff, getFolderContents, and getFileBlame. The undefined filter in the helper covers both required and optional param types uniformly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/api/`(client)/client.ts around lines 173 - 240, The four
API client functions repeat the same URL-building and fetch/json/header logic,
so extract that common flow into a shared helper in client.ts and have
getCommit, getDiff, getFolderContents, and getFileBlame delegate to it. Keep the
helper responsible for appending query params, setting the
X-Sourcebot-Client-Source header, performing fetch, and returning parsed JSON so
future header or request changes only need one update.
packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx (1)

42-57: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: extract the shared pending/error UI.

These pending and error blocks are duplicated verbatim in fullCommitDiffPanel.tsx (lines 25-40). Consider a small shared wrapper (e.g. <CommitDiffState isPending error>) to avoid divergence as the panels evolve.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/web/src/app/`(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx
around lines 42 - 57, The pending and error UI in focusedCommitDiffPanelClient
is duplicated in fullCommitDiffPanel, so extract the shared loading/error
rendering into a reusable component or wrapper (for example, a CommitDiffState
in the shared commit diff panel area) and use it from both panels. Keep the
existing behavior of the isPending and error/data checks, but centralize the
Loader2/loading text and error message markup so both
focusedCommitDiffPanelClient and fullCommitDiffPanel stay in sync as they
evolve.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In
`@packages/web/src/app/`(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx:
- Around line 42-57: The pending and error UI in focusedCommitDiffPanelClient is
duplicated in fullCommitDiffPanel, so extract the shared loading/error rendering
into a reusable component or wrapper (for example, a CommitDiffState in the
shared commit diff panel area) and use it from both panels. Keep the existing
behavior of the isPending and error/data checks, but centralize the
Loader2/loading text and error message markup so both
focusedCommitDiffPanelClient and fullCommitDiffPanel stay in sync as they
evolve.
In `@packages/web/src/app/api/`(client)/client.ts:
- Around line 173-240: The four API client functions repeat the same
URL-building and fetch/json/header logic, so extract that common flow into a
shared helper in client.ts and have getCommit, getDiff, getFolderContents, and
getFileBlame delegate to it. Keep the helper responsible for appending query
params, setting the X-Sourcebot-Client-Source header, performing fetch, and
returning parsed JSON so future header or request changes only need one update.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c02f0a2b-b454-4983-964c-cb35fa6d5583

📥 Commits

Reviewing files that changed from the base of the PR and between 94b2af6 and 80ee7fa.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/codePreviewPanel/codePreviewPanelClient.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/focusedCommitDiffPanelClient.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/fullCommitDiffPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/commitDiffPanel/useCommitDiff.ts
  • packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanel.tsx
  • packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanelClient.tsx
  • packages/web/src/app/api/(client)/client.ts
  • packages/web/src/app/api/(server)/folder_contents/route.ts
  • packages/web/src/app/robots.ts

brendan-kellamand others added 2 commits July 7, 2026 20:00
Render the path header, separator, and file toolbar (blame toggle + age
legend) immediately from props, and scope the loading spinner / error to
the body below. The line-count/size stat shows a skeleton while the file
source is pending, then the real value once loaded.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@brendan-kellam
brendan-kellam merged commit 2afc265 into mainJul 8, 2026
10 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/browse-csr branch July 8, 2026 03:53
@github-actionsgithub-actionsBot mentioned this pull request Jul 8, 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