fix(settings): remove duplicate breadcrumbs and system sidebar badges (#1080)

This commit is contained in:
Carlos Florêncio
2026-05-26 10:27:07 +01:00
committed by GitHub
parent 1a467b969f
commit 9f0b8f6bc6
4 changed files with 57 additions and 67 deletions
@@ -8,7 +8,7 @@ import { Input } from "@kandev/ui/input";
import { Label } from "@kandev/ui/label";
import { Switch } from "@kandev/ui/switch";
import { Separator } from "@kandev/ui/separator";
import { IconArrowLeft, IconTrash } from "@tabler/icons-react";
import { IconTrash } from "@tabler/icons-react";
import { useAutomations } from "@/hooks/domains/settings/use-automations";
import {
addTrigger,
@@ -352,7 +352,6 @@ export function AutomationEditor({ workspaceId, automationId }: AutomationEditor
return (
<div className="max-w-3xl space-y-6" data-testid="automation-editor">
<EditorHeader workspaceId={workspaceId} />
<div className="space-y-2">
<Label htmlFor="automation-name">Name</Label>
<Input
@@ -519,23 +518,6 @@ function ThenSection({
);
}
function EditorHeader({ workspaceId }: { workspaceId: string }) {
const router = useRouter();
return (
<div className="flex items-center gap-3">
<Button
variant="ghost"
size="icon-sm"
className="cursor-pointer"
onClick={() => router.push(`/settings/workspace/${workspaceId}/automations`)}
>
<IconArrowLeft className="h-4 w-4" />
</Button>
<span className="text-sm text-muted-foreground">Automations</span>
</div>
);
}
function SettingsSection({
form,
updateField,
+21 -23
View File
@@ -24,11 +24,11 @@ type PageTopbarProps = {
/** Label for the parent breadcrumb (default: "Kandev") */
backLabel?: string;
/**
* Optional middle crumb inserted between the back link and the title — use
* for nested sections (e.g. Home > Settings > Prompts). When omitted the
* breadcrumb is two segments wide.
* Optional middle crumbs inserted between the back link and the title — use
* for nested sections (e.g. Home > Settings > Automations > New). When
* omitted the breadcrumb is two segments wide.
*/
parent?: { label: string; href: string };
parents?: Array<{ label: string; href: string }>;
/** Optional content rendered before the breadcrumb */
leading?: ReactNode;
/** Optional content rendered at the visual center of the topbar */
@@ -66,14 +66,14 @@ function BackLink({ href, label }: { href: string; label: string }) {
function TopbarBreadcrumb({
backHref,
backLabel,
parent,
parents,
title,
subtitle,
icon,
}: {
backHref: string;
backLabel: string;
parent: { label: string; href: string } | undefined;
parents: Array<{ label: string; href: string }> | undefined;
title: string;
subtitle?: string;
icon?: ReactNode;
@@ -87,21 +87,19 @@ function TopbarBreadcrumb({
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator className="shrink-0" />
{parent && (
<>
<BreadcrumbItem className="shrink-0">
<BreadcrumbLink asChild>
<Link
href={parent.href}
className="cursor-pointer text-muted-foreground hover:text-foreground transition-colors"
>
{parent.label}
</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator className="shrink-0" />
</>
)}
{parents?.flatMap((p) => [
<BreadcrumbItem key={`${p.href}-item`} className="shrink-0">
<BreadcrumbLink asChild>
<Link
href={p.href}
className="cursor-pointer text-muted-foreground hover:text-foreground transition-colors"
>
{p.label}
</Link>
</BreadcrumbLink>
</BreadcrumbItem>,
<BreadcrumbSeparator key={`${p.href}-sep`} className="shrink-0" />,
])}
<BreadcrumbItem className="min-w-0">
<BreadcrumbPage className="flex min-w-0 items-center gap-2">
{icon}
@@ -128,7 +126,7 @@ export const PageTopbar = forwardRef<HTMLElement, PageTopbarProps>(function Page
icon,
backHref = "/",
backLabel = "Kandev",
parent,
parents,
leading,
center,
leftActions,
@@ -154,7 +152,7 @@ export const PageTopbar = forwardRef<HTMLElement, PageTopbarProps>(function Page
<TopbarBreadcrumb
backHref={backHref}
backLabel={backLabel}
parent={parent}
parents={parents}
title={title}
subtitle={subtitle}
icon={icon}
@@ -38,7 +38,6 @@ import {
SidebarGroupLabel,
SidebarGroupContent,
SidebarMenu,
SidebarMenuBadge,
SidebarMenuItem,
SidebarMenuButton,
SidebarMenuSub,
@@ -51,7 +50,7 @@ import { ScrollArea } from "@kandev/ui/scroll-area";
import { ScrollOnOverflow } from "@kandev/ui/scroll-on-overflow";
import { useAppStore } from "@/components/state-provider";
import { useAvailableAgents } from "@/hooks/domains/settings/use-available-agents";
import { useSystemBadgeCount } from "@/hooks/domains/system/use-system-badge-count";
import { AgentLogo } from "@/components/agent-logo";
import { getExecutorIcon } from "@/lib/executor-icons";
import { getCapabilityWarning } from "@/lib/capability-warning";
@@ -173,10 +172,6 @@ function SystemSidebarSection({ pathname }: { pathname: string }) {
{ href: "/settings/system/licenses", label: "Licenses", Icon: IconScale },
];
const isSystem = pathname.startsWith("/settings/system");
// Sum of non-info health issues + 1 when an update is available. Surfaced
// on the group row and the Status child entry so the user gets a hint
// without having to open System.
const badgeCount = useSystemBadgeCount();
return (
<SidebarMenuItem>
<SidebarMenuButton asChild tooltip="System" isActive={isSystem}>
@@ -185,9 +180,6 @@ function SystemSidebarSection({ pathname }: { pathname: string }) {
<span>System</span>
</Link>
</SidebarMenuButton>
{badgeCount > 0 && (
<SidebarMenuBadge data-testid="system-sidebar-badge">{badgeCount}</SidebarMenuBadge>
)}
<SidebarMenuSub className="ml-3 mt-1">
{items.map(({ href, label, Icon }) => (
<SidebarMenuSubItem key={href}>
@@ -197,11 +189,6 @@ function SystemSidebarSection({ pathname }: { pathname: string }) {
<span>{label}</span>
</Link>
</SidebarMenuSubButton>
{href === "/settings/system/status" && badgeCount > 0 && (
<SidebarMenuBadge data-testid="system-sidebar-status-badge">
{badgeCount}
</SidebarMenuBadge>
)}
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
@@ -42,18 +42,41 @@ function deriveCurrentPageLabel(pathname: string): string | null {
return null;
}
// Build the intermediate breadcrumb crumbs between the back link and the
// current page title. For workspace-scoped automation pages, inject an
// "Automations" crumb so the breadcrumb reads e.g.
// Home > Settings > Automations > New.
function deriveParents(pathname: string): Array<{ label: string; href: string }> {
const segments = pathname.split("/").filter(Boolean);
if (segments.length <= 1) return [];
const parents: Array<{ label: string; href: string }> = [
{ label: "Settings", href: "/settings" },
];
const automationsMatch = pathname.match(
/^\/settings\/workspace\/([^/]+)\/automations(?:\/(.+))?/,
);
if (automationsMatch && automationsMatch[2]) {
// Only inject the Automations crumb when we're on a sub-page (new or
// edit), not on the listing page itself — the listing page title is
// already "Automations".
parents.push({
label: "Automations",
href: `/settings/workspace/${automationsMatch[1]}/automations`,
});
}
return parents;
}
export function SettingsLayoutClient({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
const isAgentDetail = pathname.startsWith("/settings/agents/") && pathname !== "/settings/agents";
if (isAgentDetail) {
return (
<SettingsShell
title="Agent"
backHref="/settings/agents"
backLabel="Agents"
parent={undefined}
>
<SettingsShell title="Agent" backHref="/settings/agents" backLabel="Agents" parents={[]}>
{children}
</SettingsShell>
);
@@ -61,10 +84,10 @@ export function SettingsLayoutClient({ children }: { children: React.ReactNode }
const pageLabel = deriveCurrentPageLabel(pathname);
const title = pageLabel ?? "Settings";
const parent = pageLabel ? { label: "Settings", href: "/settings" } : undefined;
const parents = deriveParents(pathname);
return (
<SettingsShell title={title} backHref="/" backLabel="Kandev" parent={parent}>
<SettingsShell title={title} backHref="/" backLabel="Kandev" parents={parents}>
{children}
</SettingsShell>
);
@@ -74,13 +97,13 @@ function SettingsShell({
title,
backHref,
backLabel,
parent,
parents,
children,
}: {
title: string;
backHref: string;
backLabel: string;
parent: { label: string; href: string } | undefined;
parents: Array<{ label: string; href: string }>;
children: React.ReactNode;
}) {
return (
@@ -92,7 +115,7 @@ function SettingsShell({
title={title}
backHref={backHref}
backLabel={backLabel}
parent={parent}
parents={parents}
className="h-16 border-b-0"
leading={<SidebarTrigger size="lg" className="md:hidden h-10 w-10 cursor-pointer" />}
/>