Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

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

### Added
- Implement dynamic tab titles for files and folders in browse tab. [#560](https://github.com/sourcebot-dev/sourcebot/pull/560)

### Fixed
- Fixed "dubious ownership" errors when cloning / fetching repos. [#553](https://github.com/sourcebot-dev/sourcebot/pull/553)

Expand Down
61 changes: 61 additions & 0 deletions packages/web/src/app/[domain]/browse/[...path]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,67 @@ import { getBrowseParamsFromPathParam } from "../hooks/utils";
import { CodePreviewPanel } from "./components/codePreviewPanel";
import { Loader2 } from "lucide-react";
import { TreePreviewPanel } from "./components/treePreviewPanel";
import { Metadata } from "next";

/**
* Parses the URL path to generate a descriptive title.
* It handles three cases:
* 1. File view (`blob`): "filename.ts - owner/repo"
* 2. Directory view (`tree`): "directory/ - owner/repo"
* 3. Repository root: "owner/repo"
*
* @param path The array of path segments from Next.js params.
* @returns A formatted title string.
*/
export const parsePathForTitle = (path: string[]): string => {
const pathParam = path.join('/');

const { repoName, revisionName, path: filePath, pathType } = getBrowseParamsFromPathParam(pathParam);

// Build the base repository and revision string.
const cleanRepoName = repoName.split('/').slice(1).join('/'); // Remove the version control system prefix
const repoAndRevision = `${cleanRepoName}${revisionName ? ` @ ${revisionName}` : ''}`;

switch (pathType) {
case 'blob': {
// For blobs, get the filename from the end of the path.
const fileName = filePath.split('/').pop() || filePath;
return `${fileName} - ${repoAndRevision}`;
}
case 'tree': {
// If the path is empty, it's the repo root.
if (filePath === '' || filePath === '/') {
return repoAndRevision;
}
// Otherwise, show the directory path.
const directoryPath = filePath.endsWith('/') ? filePath : `${filePath}/`;
return `${directoryPath} - ${repoAndRevision}`;
}
}
}

type Props = {
params: Promise<{
domain: string;
path: string[];
}>;
};

export async function generateMetadata({ params: paramsPromise }: Props): Promise<Metadata> {
let title = 'Browse'; // Default Fallback

try {
const params = await paramsPromise;
title = parsePathForTitle(params.path);

} catch (error) {
console.error("Failed to generate metadata title from path:", error);
}

return {
title,
};
}

interface BrowsePageProps {
params: Promise<{
Expand Down
12 changes: 9 additions & 3 deletions packages/web/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,15 @@ import { PlanProvider } from "@/features/entitlements/planProvider";
import { getEntitlements } from "@sourcebot/shared";

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

export default function RootLayout({
Expand Down
Loading