mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-23 18:17:44 +02:00
* 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>
68 lines
2.4 KiB
TypeScript
68 lines
2.4 KiB
TypeScript
"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>
|
||
);
|
||
}
|