Skip to content

Commit 5b09757

Browse files
feat(browse): Implement dynamic tab titles for files and folders (#560)
* feat(metadata): Enhance metadata generation for repository browsing feat(utils): Add parseRepoPath function to extract repository name and revision from URL path * feat(metadata): update tab title with appropriate file name, path or repository name. * fix: remove left-over console logs and Async Params resolution. * feat: refactor parsePathForTitle to utilize getBrowseParamsFromPathParam for cleaner code. * minor refactoring and adding changelog. * Remove unused import * refactor: change parsePathForTitle to a non-exported function --------- Co-authored-by: Brendan Kellam <[email protected]>
1 parent c3fae1a commit 5b09757

File tree

3 files changed

+73
-3
lines changed

3 files changed

+73
-3
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
<!-- @NOTE: On next release, please bump the MCP pacakge as there are breaking changes in this! -->
1111

12+
### Added
13+
- Implement dynamic tab titles for files and folders in browse tab. [#560](https://github.com/sourcebot-dev/sourcebot/pull/560)
14+
1215
### Fixed
1316
- Fixed "dubious ownership" errors when cloning / fetching repos. [#553](https://github.com/sourcebot-dev/sourcebot/pull/553)
1417

packages/web/src/app/[domain]/browse/[...path]/page.tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,67 @@ import { getBrowseParamsFromPathParam } from "../hooks/utils";
33
import { CodePreviewPanel } from "./components/codePreviewPanel";
44
import { Loader2 } from "lucide-react";
55
import { TreePreviewPanel } from "./components/treePreviewPanel";
6+
import { Metadata } from "next";
7+
8+
/**
9+
* Parses the URL path to generate a descriptive title.
10+
* It handles three cases:
11+
* 1. File view (`blob`): "filename.ts - owner/repo"
12+
* 2. Directory view (`tree`): "directory/ - owner/repo"
13+
* 3. Repository root: "owner/repo"
14+
*
15+
* @param path The array of path segments from Next.js params.
16+
* @returns A formatted title string.
17+
*/
18+
const parsePathForTitle = (path: string[]): string => {
19+
const pathParam = path.join('/');
20+
21+
const { repoName, revisionName, path: filePath, pathType } = getBrowseParamsFromPathParam(pathParam);
22+
23+
// Build the base repository and revision string.
24+
const cleanRepoName = repoName.split('/').slice(1).join('/'); // Remove the version control system prefix
25+
const repoAndRevision = `${cleanRepoName}${revisionName ? ` @ ${revisionName}` : ''}`;
26+
27+
switch (pathType) {
28+
case 'blob': {
29+
// For blobs, get the filename from the end of the path.
30+
const fileName = filePath.split('/').pop() || filePath;
31+
return `${fileName} - ${repoAndRevision}`;
32+
}
33+
case 'tree': {
34+
// If the path is empty, it's the repo root.
35+
if (filePath === '' || filePath === '/') {
36+
return repoAndRevision;
37+
}
38+
// Otherwise, show the directory path.
39+
const directoryPath = filePath.endsWith('/') ? filePath : `${filePath}/`;
40+
return `${directoryPath} - ${repoAndRevision}`;
41+
}
42+
}
43+
}
44+
45+
type Props = {
46+
params: Promise<{
47+
domain: string;
48+
path: string[];
49+
}>;
50+
};
51+
52+
export async function generateMetadata({ params: paramsPromise }: Props): Promise<Metadata> {
53+
let title = 'Browse'; // Default Fallback
54+
55+
try {
56+
const params = await paramsPromise;
57+
title = parsePathForTitle(params.path);
58+
59+
} catch (error) {
60+
console.error("Failed to generate metadata title from path:", error);
61+
}
62+
63+
return {
64+
title,
65+
};
66+
}
667

768
interface BrowsePageProps {
869
params: Promise<{

packages/web/src/app/layout.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,15 @@ import { PlanProvider } from "@/features/entitlements/planProvider";
1111
import { getEntitlements } from "@sourcebot/shared";
1212

1313
export const metadata: Metadata = {
14-
title: "Sourcebot",
15-
description: "Sourcebot is a self-hosted code understanding tool. Ask questions about your codebase and get rich Markdown answers with inline citations.",
16-
manifest: "/manifest.json",
14+
// Using the title.template will allow child pages to set the title
15+
// while keeping a consistent suffix.
16+
title: {
17+
default: "Sourcebot",
18+
template: "%s | Sourcebot",
19+
},
20+
description:
21+
"Sourcebot is a self-hosted code understanding tool. Ask questions about your codebase and get rich Markdown answers with inline citations.",
22+
manifest: "/manifest.json",
1723
};
1824

1925
export default function RootLayout({

0 commit comments

Comments
 (0)