Files
multica/packages/views/layout/breadcrumb-header.tsx
Naiyuan Qing 645af40ed9 refactor(views): unify detail/list headers into shared BreadcrumbHeader (#3510)
* refactor(views): unify detail/list headers into shared BreadcrumbHeader

Replace four hand-rolled, divergent header styles (workspace-name root,
"/" separator, back-arrow, raw div) with one shared BreadcrumbHeader
component. The mental model is now identical everywhere: leading crumbs
are the thing's real containers and clicking one navigates up.

- New packages/views/layout/breadcrumb-header.tsx (segments/leaf/actions)
- Detail pages (issue, project, runtime, skill, autopilot, agent, squad)
  now render `{Section} › name`; org name removed as a breadcrumb root
- Issue breadcrumb shows the single most-direct container only (parent
  wins over project; they are orthogonal columns), never a fabricated
  chain; bare issue shows just its title
- Issue leaf (identifier + title) is now a clickable link to the issue
  detail page with a subtle hover:opacity-80
- Issues / My Issues list headers drop the workspace prefix, matching the
  icon + title style of the other list pages

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(views): update breadcrumb tests for unified header behavior

The header unification changed three observable behaviors the tests
asserted against:
- issue detail no longer renders the workspace name as a breadcrumb root
- bare issue shows only its (now clickable) title leaf, no ancestor crumbs
- the project "Unknown project" error placeholder was removed

Rewrite the two affected issue-detail tests to assert the new leaf-link
and no-project-crumb behavior, drop the obsolete Unknown-project test, and
update the issues-page header test to assert the workspace prefix is gone.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 14:27:51 +08:00

68 lines
2.4 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { Fragment, type ReactNode } from "react";
import { ChevronRight } from "lucide-react";
import { cn } from "@multica/ui/lib/utils";
import { PageHeader } from "./page-header";
import { AppLink } from "../navigation";
/**
* One ancestor crumb. Always a clickable link to the segment's container — the
* breadcrumb expresses a containment chain, so every segment must navigate
* somewhere. Non-navigable chrome (skeletons, "unknown" states) does NOT belong
* here; omit the segment instead.
*/
export interface BreadcrumbSegment {
href: string;
/** Plain text, or a composed node (e.g. icon + label). */
label: ReactNode;
/**
* Overrides the default `shrink-0`. Pass `flex items-center gap-1 min-w-0
* max-w-72` for a truncating segment (e.g. a long project title).
*/
className?: string;
}
interface BreadcrumbHeaderProps {
/** Ancestor links, rendered left-to-right with chevron separators. */
segments: BreadcrumbSegment[];
/** The current page — non-clickable leaf. Caller controls styling/adornments. */
leaf: ReactNode;
/** Right-side actions. Wrapped in a `shrink-0` flex row; omit for none. */
actions?: ReactNode;
className?: string;
}
/**
* Unified detail-page header: `{ancestor ancestor …} leaf [actions]`.
*
* Replaces the per-page hand-rolled breadcrumbs that had drifted into four
* different styles (workspace-name root, `/` separator, back-arrow, raw div).
* The mental model is identical everywhere: the leading crumbs are the thing's
* real containers and clicking one navigates up to it.
*/
export function BreadcrumbHeader({ segments, leaf, actions, className }: BreadcrumbHeaderProps) {
return (
<PageHeader className={cn("gap-2 bg-background text-sm", className)}>
<div className="flex flex-1 items-center gap-1.5 min-w-0">
{segments.map((segment) => (
<Fragment key={segment.href}>
<AppLink
href={segment.href}
className={cn(
"text-muted-foreground hover:text-foreground transition-colors",
segment.className ?? "shrink-0",
)}
>
{segment.label}
</AppLink>
<ChevronRight className="h-3 w-3 text-muted-foreground/50 shrink-0" />
</Fragment>
))}
{leaf}
</div>
{actions ? <div className="flex items-center gap-1 shrink-0">{actions}</div> : null}
</PageHeader>
);
}