diff --git a/apps/web/src/components/WorkspaceBreadcrumb.tsx b/apps/web/src/components/WorkspaceBreadcrumb.tsx
new file mode 100644
index 00000000000..3c67e4d3269
--- /dev/null
+++ b/apps/web/src/components/WorkspaceBreadcrumb.tsx
@@ -0,0 +1,54 @@
+import type { ReactNode } from "react";
+
+import { cn } from "../lib/utils";
+
+interface WorkspaceBreadcrumbProps {
+ readonly ariaLabel: string;
+ readonly children: ReactNode;
+ readonly className?: string;
+}
+
+export function WorkspaceBreadcrumb({ ariaLabel, children, className }: WorkspaceBreadcrumbProps) {
+ return (
+
+ );
+}
+
+interface WorkspaceBreadcrumbItemProps {
+ readonly children: ReactNode;
+ readonly className?: string;
+ readonly current?: boolean;
+}
+
+export function WorkspaceBreadcrumbItem({
+ children,
+ className,
+ current = false,
+}: WorkspaceBreadcrumbItemProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+export function WorkspaceBreadcrumbSeparator() {
+ return (
+
+ /
+
+ );
+}
diff --git a/apps/web/src/components/chat/ChatHeader.tsx b/apps/web/src/components/chat/ChatHeader.tsx
index c5307ee7482..58d5ba5395d 100644
--- a/apps/web/src/components/chat/ChatHeader.tsx
+++ b/apps/web/src/components/chat/ChatHeader.tsx
@@ -36,6 +36,11 @@ import { useThreadActionMenu } from "~/hooks/useThreadActionMenu";
import { threadEnvironment } from "../../state/threads";
import { useAtomCommand } from "../../state/use-atom-command";
import { ProjectFavicon } from "../ProjectFavicon";
+import {
+ WorkspaceBreadcrumb,
+ WorkspaceBreadcrumbItem,
+ WorkspaceBreadcrumbSeparator,
+} from "../WorkspaceBreadcrumb";
import { cn } from "~/lib/utils";
interface ChatHeaderProps {
@@ -211,91 +216,88 @@ export const ChatHeader = memo(function ChatHeader({
className="@container/header-actions flex min-w-0 flex-1 items-center gap-2 sm:gap-3"
onContextMenu={handleHeaderContextMenu}
>
-
+ {activeThreadTitle}
+
+ )}
+
+
group.projectKey === projectKey) ?? null;
+ const openProjectMenu = (event: ReactMouseEvent) => {
+ const api = readLocalApi();
+ if (!api) return;
+
+ const rect = event.currentTarget.getBoundingClientRect();
+ const items: ContextMenuItem[] = groups.map((group) => ({
+ id: group.projectKey,
+ label: group.displayName,
+ }));
+ void settlePromise(() =>
+ api.contextMenu.show(items, { x: rect.left, y: rect.bottom + 4 }),
+ ).then((clicked) => {
+ if (clicked._tag === "Failure" || clicked.value === null) return;
+ void navigate({
+ to: "/projects/$projectKey",
+ params: { projectKey: clicked.value },
+ replace: true,
+ hashScrollIntoView: false,
+ });
+ });
+ };
return (
-
+ {selected.displayName}
+
+
+ ) : (
+ Unavailable project
+ )}
+
+
);
}
diff --git a/apps/web/src/components/settings/SettingsBreadcrumb.tsx b/apps/web/src/components/settings/SettingsBreadcrumb.tsx
new file mode 100644
index 00000000000..bb631187cb1
--- /dev/null
+++ b/apps/web/src/components/settings/SettingsBreadcrumb.tsx
@@ -0,0 +1,34 @@
+import {
+ WorkspaceBreadcrumb,
+ WorkspaceBreadcrumbItem,
+ WorkspaceBreadcrumbSeparator,
+} from "../WorkspaceBreadcrumb";
+import { SETTINGS_SECTION_LABELS } from "./settingsSearch";
+
+const SETTINGS_BREADCRUMB_LABELS: Readonly> = {
+ ...SETTINGS_SECTION_LABELS,
+ "/settings/diagnostics": "Diagnostics",
+};
+
+function settingsBreadcrumbLabel(pathname: string): string | null {
+ const normalizedPathname = pathname.replace(/\/+$/, "") || "/";
+ return SETTINGS_BREADCRUMB_LABELS[normalizedPathname] ?? null;
+}
+
+export function SettingsBreadcrumb({ pathname }: { pathname: string }) {
+ const sectionLabel = settingsBreadcrumbLabel(pathname);
+
+ return (
+
+ {sectionLabel ? (
+ <>
+ Settings
+
+ >
+ ) : null}
+
+ {sectionLabel ?? "Settings"}
+
+
+ );
+}
diff --git a/apps/web/src/components/usage/UsagePage.tsx b/apps/web/src/components/usage/UsagePage.tsx
index 4357bbd7e00..b550d673f6e 100644
--- a/apps/web/src/components/usage/UsagePage.tsx
+++ b/apps/web/src/components/usage/UsagePage.tsx
@@ -16,6 +16,7 @@ import {
} from "@t3tools/shared/usageFormat";
import { ScrollArea } from "../ui/scroll-area";
import { SidebarInset } from "../ui/sidebar";
+import { WorkspaceBreadcrumb, WorkspaceBreadcrumbItem } from "../WorkspaceBreadcrumb";
import { COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS } from "../../workspaceTitlebar";
import { UsageChartLegend, UsageProviderChart, type UsageChartMetric } from "./UsageProviderChart";
import { PROVIDER_COLOR, PROVIDER_LABEL, PROVIDER_MARK, PROVIDER_ORDER } from "./usageProviders";
@@ -71,7 +72,9 @@ export function UsagePage() {
COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS,
)}
>
- Usage
+
+ Usage
+
)}
@@ -82,7 +85,9 @@ export function UsagePage() {
COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS,
)}
>
- Usage
+
+ Usage
+
)}
diff --git a/apps/web/src/routes/settings.tsx b/apps/web/src/routes/settings.tsx
index ed2c132aae4..f14793ba544 100644
--- a/apps/web/src/routes/settings.tsx
+++ b/apps/web/src/routes/settings.tsx
@@ -10,6 +10,7 @@ import {
import { useCallback, useEffect, useState } from "react";
import { useSettingsRestore } from "../components/settings/SettingsPanels";
+import { SettingsBreadcrumb } from "../components/settings/SettingsBreadcrumb";
import { Button } from "../components/ui/button";
import { SidebarInset } from "../components/ui/sidebar";
import { isElectron } from "../env";
@@ -79,7 +80,7 @@ function SettingsContentLayout() {
)}
>
-
Settings
+
{showRestoreDefaults ? (
@@ -96,14 +97,14 @@ function SettingsContentLayout() {
COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS,
)}
>
-
- Settings
-
- {showRestoreDefaults ? (
-
-
-
- ) : null}
+
+
+ {showRestoreDefaults ? (
+
+
+
+ ) : null}
+
)}